Skip to content

Commit

Permalink
[Revert the revert] Change ingress fetching to be isolated per provid…
Browse files Browse the repository at this point in the history
…er (kubernetes-sigs#116)

* pass namespace in ProviderConf and make read from file account for namepsaces

* move ingress-nginx to fetch its own ingresses

* move Kong to fetch its own ingresses

* Deprecate ingress fetching from the generic package

Also added issue numbers for TODOs

* add helper common functions for reading ingresses and extract them from file
  • Loading branch information
LiorLieberman authored and xtineskim committed Jan 4, 2024
1 parent 2399e8b commit 7446800
Show file tree
Hide file tree
Showing 19 changed files with 539 additions and 333 deletions.
111 changes: 4 additions & 107 deletions pkg/i2gw/ingress2gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,10 @@ limitations under the License.
package i2gw

import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os"

networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
kubeyaml "k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
Expand All @@ -45,30 +37,19 @@ func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile stri
}
cl = client.NewNamespacedClient(cl, namespace)

var ingresses networkingv1.IngressList

providerByName, err := constructProviders(&ProviderConf{
Client: cl,
Client: cl,
Namespace: namespace,
}, providers)
if err != nil {
return nil, err
}

resources := InputResources{}

if inputFile != "" {
if err = ConstructIngressesFromFile(&ingresses, inputFile, namespace); err != nil {
return nil, fmt.Errorf("failed to read ingresses from file: %w", err)
}
resources.Ingresses = ingresses.Items
if err = readProviderResourcesFromFile(ctx, providerByName, inputFile); err != nil {
return nil, err
}
} else {
if err = ConstructIngressesFromCluster(ctx, cl, &ingresses); err != nil {
return nil, fmt.Errorf("failed to read ingresses from cluster: %w", err)
}
resources.Ingresses = ingresses.Items
if err = readProviderResourcesFromCluster(ctx, providerByName); err != nil {
return nil, err
}
Expand All @@ -79,7 +60,8 @@ func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile stri
errs field.ErrorList
)
for _, provider := range providerByName {
providerGatewayResources, conversionErrs := provider.ToGatewayAPI(resources)
// TODO(#113) Remove input resources from ToGatewayAPI function
providerGatewayResources, conversionErrs := provider.ToGatewayAPI(InputResources{})
errs = append(errs, conversionErrs...)
gatewayResources = append(gatewayResources, providerGatewayResources)
}
Expand Down Expand Up @@ -108,14 +90,6 @@ func readProviderResourcesFromCluster(ctx context.Context, providerByName map[Pr
return nil
}

func ConstructIngressesFromCluster(ctx context.Context, cl client.Client, ingressList *networkingv1.IngressList) error {
err := cl.List(ctx, ingressList)
if err != nil {
return fmt.Errorf("failed to get ingresses from the cluster: %w", err)
}
return nil
}

// constructProviders constructs a map of concrete Provider implementations
// by their ProviderName.
func constructProviders(conf *ProviderConf, providers []string) (map[ProviderName]Provider, error) {
Expand All @@ -133,83 +107,6 @@ func constructProviders(conf *ProviderConf, providers []string) (map[ProviderNam
return providerByName, nil
}

// ExtractObjectsFromReader extracts all objects from a reader,
// which is created from YAML or JSON input files.
// It retrieves all objects, including nested ones if they are contained within a list.
func ExtractObjectsFromReader(reader io.Reader) ([]*unstructured.Unstructured, error) {
d := kubeyaml.NewYAMLOrJSONDecoder(reader, 4096)
var objs []*unstructured.Unstructured
for {
u := &unstructured.Unstructured{}
if err := d.Decode(&u); err != nil {
if errors.Is(err, io.EOF) {
break
}
return objs, fmt.Errorf("failed to unmarshal manifest: %w", err)
}
if u == nil {
continue
}
objs = append(objs, u)
}

finalObjs := []*unstructured.Unstructured{}
for _, obj := range objs {
tmpObjs := []*unstructured.Unstructured{}
if obj.IsList() {
err := obj.EachListItem(func(object runtime.Object) error {
unstructuredObj, ok := object.(*unstructured.Unstructured)
if ok {
tmpObjs = append(tmpObjs, unstructuredObj)
return nil
}
return fmt.Errorf("resource list item has unexpected type")
})
if err != nil {
return nil, err
}
} else {
tmpObjs = append(tmpObjs, obj)
}
finalObjs = append(finalObjs, tmpObjs...)
}

return finalObjs, nil
}

// ConstructIngressesFromFile reads the inputFile in either json/yaml formats,
// then deserialize the file into Ingresses resources.
// All ingresses will be pushed into the supplied IngressList for return.
func ConstructIngressesFromFile(l *networkingv1.IngressList, inputFile string, namespace string) error {
stream, err := os.ReadFile(inputFile)
if err != nil {
return err
}

reader := bytes.NewReader(stream)
objs, err := ExtractObjectsFromReader(reader)
if err != nil {
return err
}

for _, f := range objs {
if namespace != "" && f.GetNamespace() != namespace {
continue
}
if !f.GroupVersionKind().Empty() && f.GroupVersionKind().Kind == "Ingress" {
var i networkingv1.Ingress
err = runtime.DefaultUnstructuredConverter.
FromUnstructured(f.UnstructuredContent(), &i)
if err != nil {
return err
}
l.Items = append(l.Items, i)
}

}
return nil
}

func aggregatedErrs(errs field.ErrorList) error {
errMsg := fmt.Errorf("\n# Encountered %d errors", len(errs))
for _, err := range errs {
Expand Down
127 changes: 0 additions & 127 deletions pkg/i2gw/ingress2gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,140 +17,13 @@ limitations under the License.
package i2gw

import (
"context"
"fmt"
"testing"

"github.com/google/go-cmp/cmp"
networkingv1 "k8s.io/api/networking/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func Test_constructIngressesFromFile(t *testing.T) {
ingress1 := ingress(443, "ingress1", "namespace1")
ingress2 := ingress(80, "ingress2", "namespace2")
ingressNoNamespace := ingress(80, "ingress-no-namespace", "")

testCases := []struct {
name string
filePath string
namespace string
wantIngressList []networkingv1.Ingress
}{
{
name: "Test yaml input file with multiple resources with no namespace flag",
filePath: "testdata/input-file.yaml",
namespace: "",
wantIngressList: []networkingv1.Ingress{ingress1, ingress2, ingressNoNamespace},
}, {
name: "Test json input file with multiple resources with no namespace flag",
filePath: "testdata/input-file.json",
namespace: "",
wantIngressList: []networkingv1.Ingress{ingress1, ingress2, ingressNoNamespace},
}, {
name: "Test yaml input file with multiple resources with namespace1 flag",
filePath: "testdata/input-file.yaml",
namespace: "namespace1",
wantIngressList: []networkingv1.Ingress{ingress1},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
gotIngressList := &networkingv1.IngressList{}
err := ConstructIngressesFromFile(gotIngressList, tc.filePath, tc.namespace)
if err != nil {
t.Errorf("Failed to open test file: %v", err)
}
compareIngressLists(t, gotIngressList, tc.wantIngressList)
})
}
}

func ingress(port int32, name, namespace string) networkingv1.Ingress {
iPrefix := networkingv1.PathTypePrefix
ingressClassName := fmt.Sprintf("ingressClass-%s", name)
var objMeta metav1.ObjectMeta
if namespace != "" {
objMeta = metav1.ObjectMeta{Name: name, ResourceVersion: "999", Namespace: namespace}
} else {
objMeta = metav1.ObjectMeta{Name: name, ResourceVersion: "999"}
}

ing := networkingv1.Ingress{
TypeMeta: metav1.TypeMeta{
Kind: "Ingress",
APIVersion: "networking.k8s.io/v1",
},
ObjectMeta: objMeta,
Spec: networkingv1.IngressSpec{
IngressClassName: &ingressClassName,
Rules: []networkingv1.IngressRule{{
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{{
Path: fmt.Sprintf("/path-%s", name),
PathType: &iPrefix,
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: fmt.Sprintf("service-%s", name),
Port: networkingv1.ServiceBackendPort{
Number: port,
},
},
},
}},
},
},
}},
},
}
return ing
}

func compareIngressLists(t *testing.T, gotIngressList *networkingv1.IngressList, wantIngressList []networkingv1.Ingress) {
for i, got := range gotIngressList.Items {
want := wantIngressList[i]
if !apiequality.Semantic.DeepEqual(got, want) {
t.Errorf("Expected Ingress %d to be %+v\n Got: %+v\n Diff: %s", i, want, got, cmp.Diff(want, got))
}
}
}

func Test_constructIngressesFromCluster(t *testing.T) {
ingress1 := ingress(443, "ingress1", "namespace1")
ingress2 := ingress(80, "ingress2", "namespace2")
testCases := []struct {
name string
runtimeObjs []runtime.Object
wantIngresses []networkingv1.Ingress
}{{
name: "Test cluster client with 2 resources",
runtimeObjs: []runtime.Object{&ingress1, &ingress2},
wantIngresses: []networkingv1.Ingress{ingress1, ingress2},
}, {
name: "Test cluster client without resources",
runtimeObjs: []runtime.Object{},
wantIngresses: []networkingv1.Ingress{},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
gotIngresses := &networkingv1.IngressList{}
cl := fake.NewClientBuilder().WithRuntimeObjects(tc.runtimeObjs...).Build()
err := ConstructIngressesFromCluster(context.Background(), cl, gotIngresses)
if err != nil {
t.Errorf("test failed unexpectedly: %v", err)
}
compareIngressLists(t, gotIngresses, tc.wantIngresses)
})
}
}

func Test_constructProviders(t *testing.T) {
supportProviders := []string{"ingress-nginx"}
for _, provider := range supportProviders {
Expand Down
3 changes: 2 additions & 1 deletion pkg/i2gw/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ type ProviderConstructor func(conf *ProviderConf) Provider
// ProviderConf contains all the configuration required for every concrete
// Provider implementation.
type ProviderConf struct {
Client client.Client
Client client.Client
Namespace string
}

// The Provider interface specifies the required functionality which needs to be
Expand Down
Loading

0 comments on commit 7446800

Please sign in to comment.