Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Revert the revert] Change ingress fetching to be isolated per provider #116

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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