Skip to content

Commit ac068f8

Browse files
authored
Merge pull request #112 from LiorLieberman/per-provider-ingress-fetching-isolation
Change ingress fetching to be isolated per provider
2 parents 654e76f + ffba1d3 commit ac068f8

15 files changed

+365
-154
lines changed

pkg/i2gw/ingress2gateway.go

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,30 +45,19 @@ func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile stri
4545
}
4646
cl = client.NewNamespacedClient(cl, namespace)
4747

48-
var ingresses networkingv1.IngressList
49-
5048
providerByName, err := constructProviders(&ProviderConf{
51-
Client: cl,
49+
Client: cl,
50+
Namespace: namespace,
5251
}, providers)
5352
if err != nil {
5453
return nil, err
5554
}
5655

57-
resources := InputResources{}
58-
5956
if inputFile != "" {
60-
if err = ConstructIngressesFromFile(&ingresses, inputFile, namespace); err != nil {
61-
return nil, fmt.Errorf("failed to read ingresses from file: %w", err)
62-
}
63-
resources.Ingresses = ingresses.Items
6457
if err = readProviderResourcesFromFile(ctx, providerByName, inputFile); err != nil {
6558
return nil, err
6659
}
6760
} else {
68-
if err = ConstructIngressesFromCluster(ctx, cl, &ingresses); err != nil {
69-
return nil, fmt.Errorf("failed to read ingresses from cluster: %w", err)
70-
}
71-
resources.Ingresses = ingresses.Items
7261
if err = readProviderResourcesFromCluster(ctx, providerByName); err != nil {
7362
return nil, err
7463
}
@@ -79,7 +68,8 @@ func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile stri
7968
errs field.ErrorList
8069
)
8170
for _, provider := range providerByName {
82-
providerGatewayResources, conversionErrs := provider.ToGatewayAPI(resources)
71+
// TODO(#113) Remove input resources from ToGatewayAPI function
72+
providerGatewayResources, conversionErrs := provider.ToGatewayAPI(InputResources{})
8373
errs = append(errs, conversionErrs...)
8474
gatewayResources = append(gatewayResources, providerGatewayResources)
8575
}
@@ -136,7 +126,8 @@ func constructProviders(conf *ProviderConf, providers []string) (map[ProviderNam
136126
// ExtractObjectsFromReader extracts all objects from a reader,
137127
// which is created from YAML or JSON input files.
138128
// It retrieves all objects, including nested ones if they are contained within a list.
139-
func ExtractObjectsFromReader(reader io.Reader) ([]*unstructured.Unstructured, error) {
129+
// The function takes a namespace parameter to optionally return only namespaced resources.
130+
func ExtractObjectsFromReader(reader io.Reader, namespace string) ([]*unstructured.Unstructured, error) {
140131
d := kubeyaml.NewYAMLOrJSONDecoder(reader, 4096)
141132
var objs []*unstructured.Unstructured
142133
for {
@@ -150,6 +141,9 @@ func ExtractObjectsFromReader(reader io.Reader) ([]*unstructured.Unstructured, e
150141
if u == nil {
151142
continue
152143
}
144+
if namespace != "" && u.GetNamespace() != namespace {
145+
continue
146+
}
153147
objs = append(objs, u)
154148
}
155149

@@ -187,15 +181,12 @@ func ConstructIngressesFromFile(l *networkingv1.IngressList, inputFile string, n
187181
}
188182

189183
reader := bytes.NewReader(stream)
190-
objs, err := ExtractObjectsFromReader(reader)
184+
objs, err := ExtractObjectsFromReader(reader, namespace)
191185
if err != nil {
192186
return err
193187
}
194188

195189
for _, f := range objs {
196-
if namespace != "" && f.GetNamespace() != namespace {
197-
continue
198-
}
199190
if !f.GroupVersionKind().Empty() && f.GroupVersionKind().Kind == "Ingress" {
200191
var i networkingv1.Ingress
201192
err = runtime.DefaultUnstructuredConverter.

pkg/i2gw/ingress2gateway_test.go

Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,21 @@ limitations under the License.
1717
package i2gw
1818

1919
import (
20-
"context"
20+
"bytes"
2121
"fmt"
22+
"os"
2223
"testing"
2324

2425
"github.com/google/go-cmp/cmp"
2526
networkingv1 "k8s.io/api/networking/v1"
2627
apiequality "k8s.io/apimachinery/pkg/api/equality"
2728
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2830
"k8s.io/apimachinery/pkg/runtime"
2931
"sigs.k8s.io/controller-runtime/pkg/client/fake"
3032
)
3133

32-
func Test_constructIngressesFromFile(t *testing.T) {
34+
func Test_ExtractObjectsFromReader(t *testing.T) {
3335
ingress1 := ingress(443, "ingress1", "namespace1")
3436
ingress2 := ingress(80, "ingress2", "namespace2")
3537
ingressNoNamespace := ingress(80, "ingress-no-namespace", "")
@@ -60,10 +62,17 @@ func Test_constructIngressesFromFile(t *testing.T) {
6062

6163
for _, tc := range testCases {
6264
t.Run(tc.name, func(t *testing.T) {
63-
gotIngressList := &networkingv1.IngressList{}
64-
err := ConstructIngressesFromFile(gotIngressList, tc.filePath, tc.namespace)
65+
stream, err := os.ReadFile(tc.filePath)
6566
if err != nil {
66-
t.Errorf("Failed to open test file: %v", err)
67+
t.Errorf("failed to read file %s: %v", tc.filePath, err)
68+
}
69+
unstructuredObjects, err := ExtractObjectsFromReader(bytes.NewReader(stream), tc.namespace)
70+
if err != nil {
71+
t.Errorf("failed to extract objects: %s", err)
72+
}
73+
gotIngressList, err := ingressListFromUnstructured(unstructuredObjects)
74+
if err != nil {
75+
t.Errorf("got unexpected error: %v", err)
6776
}
6877
compareIngressLists(t, gotIngressList, tc.wantIngressList)
6978
})
@@ -111,6 +120,21 @@ func ingress(port int32, name, namespace string) networkingv1.Ingress {
111120
return ing
112121
}
113122

123+
func ingressListFromUnstructured(unstructuredObjects []*unstructured.Unstructured) (*networkingv1.IngressList, error) {
124+
ingressList := &networkingv1.IngressList{}
125+
for _, f := range unstructuredObjects {
126+
if !f.GroupVersionKind().Empty() && f.GroupVersionKind().Kind == "Ingress" {
127+
var i networkingv1.Ingress
128+
err := runtime.DefaultUnstructuredConverter.
129+
FromUnstructured(f.UnstructuredContent(), &i)
130+
if err != nil {
131+
return nil, err
132+
}
133+
ingressList.Items = append(ingressList.Items, i)
134+
}
135+
}
136+
return ingressList, nil
137+
}
114138
func compareIngressLists(t *testing.T, gotIngressList *networkingv1.IngressList, wantIngressList []networkingv1.Ingress) {
115139
for i, got := range gotIngressList.Items {
116140
want := wantIngressList[i]
@@ -120,37 +144,6 @@ func compareIngressLists(t *testing.T, gotIngressList *networkingv1.IngressList,
120144
}
121145
}
122146

123-
func Test_constructIngressesFromCluster(t *testing.T) {
124-
ingress1 := ingress(443, "ingress1", "namespace1")
125-
ingress2 := ingress(80, "ingress2", "namespace2")
126-
testCases := []struct {
127-
name string
128-
runtimeObjs []runtime.Object
129-
wantIngresses []networkingv1.Ingress
130-
}{{
131-
name: "Test cluster client with 2 resources",
132-
runtimeObjs: []runtime.Object{&ingress1, &ingress2},
133-
wantIngresses: []networkingv1.Ingress{ingress1, ingress2},
134-
}, {
135-
name: "Test cluster client without resources",
136-
runtimeObjs: []runtime.Object{},
137-
wantIngresses: []networkingv1.Ingress{},
138-
},
139-
}
140-
141-
for _, tc := range testCases {
142-
t.Run(tc.name, func(t *testing.T) {
143-
gotIngresses := &networkingv1.IngressList{}
144-
cl := fake.NewClientBuilder().WithRuntimeObjects(tc.runtimeObjs...).Build()
145-
err := ConstructIngressesFromCluster(context.Background(), cl, gotIngresses)
146-
if err != nil {
147-
t.Errorf("test failed unexpectedly: %v", err)
148-
}
149-
compareIngressLists(t, gotIngresses, tc.wantIngresses)
150-
})
151-
}
152-
}
153-
154147
func Test_constructProviders(t *testing.T) {
155148
supportProviders := []string{"ingress-nginx"}
156149
for _, provider := range supportProviders {

pkg/i2gw/provider.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ type ProviderConstructor func(conf *ProviderConf) Provider
4242
// ProviderConf contains all the configuration required for every concrete
4343
// Provider implementation.
4444
type ProviderConf struct {
45-
Client client.Client
45+
Client client.Client
46+
Namespace string
4647
}
4748

4849
// The Provider interface specifies the required functionality which needs to be

pkg/i2gw/providers/ingressnginx/converter.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,40 +19,42 @@ package ingressnginx
1919
import (
2020
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
2121
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common"
22+
networkingv1 "k8s.io/api/networking/v1"
2223
"k8s.io/apimachinery/pkg/util/validation/field"
2324
)
2425

2526
// converter implements the ToGatewayAPI function of i2gw.ResourceConverter interface.
2627
type converter struct {
27-
conf *i2gw.ProviderConf
28-
2928
featureParsers []i2gw.FeatureParser
3029
}
3130

3231
// newConverter returns an ingress-nginx converter instance.
33-
func newConverter(conf *i2gw.ProviderConf) *converter {
32+
func newConverter() *converter {
3433
return &converter{
35-
conf: conf,
3634
featureParsers: []i2gw.FeatureParser{
3735
canaryFeature,
3836
},
3937
}
4038
}
4139

42-
// ToGatewayAPI converts the received i2gw.InputResources to i2gw.GatewayResources
43-
// including the ingress-nginx specific features.
44-
func (c *converter) ToGatewayAPI(resources i2gw.InputResources) (i2gw.GatewayResources, field.ErrorList) {
40+
func (c *converter) convert(storage *storage) (i2gw.GatewayResources, field.ErrorList) {
41+
42+
// TODO(liorliberman) temporary until we decide to change ToGateway and featureParsers to get a map of [types.NamespacedName]*networkingv1.Ingress instead of a list
43+
ingressList := []networkingv1.Ingress{}
44+
for _, ing := range storage.Ingresses {
45+
ingressList = append(ingressList, *ing)
46+
}
4547

4648
// Convert plain ingress resources to gateway resources, ignoring all
4749
// provider-specific features.
48-
gatewayResources, errs := common.ToGateway(resources.Ingresses, i2gw.ProviderImplementationSpecificOptions{})
50+
gatewayResources, errs := common.ToGateway(ingressList, i2gw.ProviderImplementationSpecificOptions{})
4951
if len(errs) > 0 {
5052
return i2gw.GatewayResources{}, errs
5153
}
5254

5355
for _, parseFeatureFunc := range c.featureParsers {
5456
// Apply the feature parsing function to the gateway resources, one by one.
55-
parseErrs := parseFeatureFunc(resources, &gatewayResources)
57+
parseErrs := parseFeatureFunc(i2gw.InputResources{Ingresses: ingressList}, &gatewayResources)
5658
// Append the parsing errors to the error list.
5759
errs = append(errs, parseErrs...)
5860
}

pkg/i2gw/providers/ingressnginx/converter_test.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ func Test_ToGateway(t *testing.T) {
4242

4343
testCases := []struct {
4444
name string
45-
ingresses []networkingv1.Ingress
45+
ingresses map[types.NamespacedName]*networkingv1.Ingress
4646
expectedGatewayResources i2gw.GatewayResources
4747
expectedErrors field.ErrorList
4848
}{
4949
{
5050
name: "canary deployment",
51-
ingresses: []networkingv1.Ingress{
52-
{
51+
ingresses: map[types.NamespacedName]*networkingv1.Ingress{
52+
{Namespace: "default", Name: "production"}: {
5353
ObjectMeta: metav1.ObjectMeta{Name: "production", Namespace: "default"},
5454
Spec: networkingv1.IngressSpec{
5555
IngressClassName: ptrTo("ingress-nginx"),
@@ -73,7 +73,7 @@ func Test_ToGateway(t *testing.T) {
7373
}},
7474
},
7575
},
76-
{
76+
{Namespace: "default", Name: "canary"}: {
7777
ObjectMeta: metav1.ObjectMeta{
7878
Name: "canary",
7979
Namespace: "default",
@@ -168,8 +168,8 @@ func Test_ToGateway(t *testing.T) {
168168
},
169169
{
170170
name: "ImplementationSpecific HTTPRouteMatching",
171-
ingresses: []networkingv1.Ingress{
172-
{
171+
ingresses: map[types.NamespacedName]*networkingv1.Ingress{
172+
{Namespace: "default", Name: "implementation-specific-regex"}: {
173173
ObjectMeta: metav1.ObjectMeta{
174174
Name: "implementation-specific-regex",
175175
Namespace: "default",
@@ -215,11 +215,21 @@ func Test_ToGateway(t *testing.T) {
215215

216216
provider := NewProvider(&i2gw.ProviderConf{})
217217

218-
resources := i2gw.InputResources{
219-
Ingresses: tc.ingresses,
220-
}
218+
nginxProvider := provider.(*Provider)
219+
nginxProvider.storage.Ingresses = tc.ingresses
220+
221+
// TODO(#113) we pass an empty i2gw.InputResources temporarily until we change ToGatewayAPI function on the interface
222+
gatewayResources, errs := provider.ToGatewayAPI(i2gw.InputResources{})
221223

222-
gatewayResources, errs := provider.ToGatewayAPI(resources)
224+
if len(errs) != len(tc.expectedErrors) {
225+
t.Errorf("Expected %d errors, got %d: %+v", len(tc.expectedErrors), len(errs), errs)
226+
} else {
227+
for i, e := range errs {
228+
if errors.Is(e, tc.expectedErrors[i]) {
229+
t.Errorf("Unexpected error message at %d index. Got %s, want: %s", i, e, tc.expectedErrors[i])
230+
}
231+
}
232+
}
223233

224234
if len(gatewayResources.HTTPRoutes) != len(tc.expectedGatewayResources.HTTPRoutes) {
225235
t.Errorf("Expected %d HTTPRoutes, got %d: %+v",
@@ -248,16 +258,6 @@ func Test_ToGateway(t *testing.T) {
248258
}
249259
}
250260
}
251-
252-
if len(errs) != len(tc.expectedErrors) {
253-
t.Errorf("Expected %d errors, got %d: %+v", len(tc.expectedErrors), len(errs), errs)
254-
} else {
255-
for i, e := range errs {
256-
if errors.Is(e, tc.expectedErrors[i]) {
257-
t.Errorf("Unexpected error message at %d index. Got %s, want: %s", i, e, tc.expectedErrors[i])
258-
}
259-
}
260-
}
261261
})
262262
}
263263
}

pkg/i2gw/providers/ingressnginx/ingressnginx.go

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,59 @@ limitations under the License.
1717
package ingressnginx
1818

1919
import (
20+
"context"
21+
"fmt"
22+
2023
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
24+
"k8s.io/apimachinery/pkg/util/validation/field"
2125
)
2226

2327
// The Name of the provider.
2428
const Name = "ingress-nginx"
29+
const NginxIngressClass = "nginx"
2530

2631
func init() {
2732
i2gw.ProviderConstructorByName[Name] = NewProvider
2833
}
2934

3035
// Provider implements the i2gw.Provider interface.
3136
type Provider struct {
32-
conf *i2gw.ProviderConf
33-
34-
*resourceReader
35-
*converter
37+
storage *storage
38+
resourceReader *resourceReader
39+
converter *converter
3640
}
3741

3842
// NewProvider constructs and returns the ingress-nginx implementation of i2gw.Provider.
3943
func NewProvider(conf *i2gw.ProviderConf) i2gw.Provider {
4044
return &Provider{
41-
conf: conf,
45+
storage: newResourcesStorage(),
4246
resourceReader: newResourceReader(conf),
43-
converter: newConverter(conf),
47+
converter: newConverter(),
48+
}
49+
}
50+
51+
// ToGatewayAPI converts the received i2gw.InputResources to i2gw.GatewayResources
52+
// including the ingress-nginx specific features.
53+
func (p *Provider) ToGatewayAPI(_ i2gw.InputResources) (i2gw.GatewayResources, field.ErrorList) {
54+
return p.converter.convert(p.storage)
55+
}
56+
57+
func (p *Provider) ReadResourcesFromCluster(ctx context.Context) error {
58+
storage, err := p.resourceReader.readResourcesFromCluster(ctx)
59+
if err != nil {
60+
return fmt.Errorf("failed to read resources from cluster: %w", err)
61+
}
62+
63+
p.storage = storage
64+
return nil
65+
}
66+
67+
func (p *Provider) ReadResourcesFromFile(ctx context.Context, filename string) error {
68+
storage, err := p.resourceReader.readResourcesFromFile(ctx, filename)
69+
if err != nil {
70+
return fmt.Errorf("failed to read resources from file: %w", err)
4471
}
72+
73+
p.storage = storage
74+
return nil
4575
}

0 commit comments

Comments
 (0)