diff --git a/cmd/print.go b/cmd/print.go index 9be4713e..dc3257ec 100644 --- a/cmd/print.go +++ b/cmd/print.go @@ -24,10 +24,10 @@ import ( "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw" "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/printers" "k8s.io/client-go/tools/clientcmd" - "sigs.k8s.io/controller-runtime/pkg/client" gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" // Call init function for the providers @@ -84,9 +84,9 @@ func (pr *PrintRunner) PrintResources(cmd *cobra.Command, _ []string) error { return err } - var additionalResources []client.Object + var additionalResources []*unstructured.Unstructured if pr.allResources { - additionalResources, err = i2gw.FetchAllResources(cmd.Context(), pr.namespaceFilter, pr.inputFile) + additionalResources, err = i2gw.ConstructOtherResourcesFromFile(pr.namespaceFilter, pr.inputFile, pr.providers) if err != nil { return err } @@ -96,7 +96,7 @@ func (pr *PrintRunner) PrintResources(cmd *cobra.Command, _ []string) error { return nil } -func (pr *PrintRunner) outputResult(httpRoutes []gatewayv1beta1.HTTPRoute, gateways []gatewayv1beta1.Gateway, additionalResources ...client.Object) { +func (pr *PrintRunner) outputResult(httpRoutes []gatewayv1beta1.HTTPRoute, gateways []gatewayv1beta1.Gateway, additionalResources ...*unstructured.Unstructured) { for i := range additionalResources { err := pr.resourcePrinter.PrintObj(additionalResources[i], os.Stdout) if err != nil { @@ -120,14 +120,14 @@ func (pr *PrintRunner) outputResult(httpRoutes []gatewayv1beta1.HTTPRoute, gatew for i := range gateways { err := pr.resourcePrinter.PrintObj(&gateways[i], os.Stdout) if err != nil { - fmt.Printf("# Error printing %s HTTPRoute: %v\n", gateways[i].Name, err) + fmt.Printf("# Error printing %s/%s Gateway: %v\n", gateways[i].Namespace, gateways[i].Name, err) } } for i := range httpRoutes { err := pr.resourcePrinter.PrintObj(&httpRoutes[i], os.Stdout) if err != nil { - fmt.Printf("# Error printing %s HTTPRoute: %v\n", httpRoutes[i].Name, err) + fmt.Printf("# Error printing %s/%s HTTPRoute: %v\n", httpRoutes[i].Namespace, httpRoutes[i].Name, err) } } } diff --git a/pkg/i2gw/ingress2gateway.go b/pkg/i2gw/ingress2gateway.go index 727a3939..0a890a3d 100644 --- a/pkg/i2gw/ingress2gateway.go +++ b/pkg/i2gw/ingress2gateway.go @@ -35,24 +35,11 @@ import ( ) func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile string, providers []string) ([]gatewayv1beta1.HTTPRoute, []gatewayv1beta1.Gateway, error) { - conf, err := config.GetConfig() - if err != nil { - return nil, nil, fmt.Errorf("failed to get client config: %w", err) - } - - cl, err := client.New(conf, client.Options{}) - if err != nil { - return nil, nil, fmt.Errorf("failed to create client: %w", err) - } - cl = client.NewNamespacedClient(cl, namespace) - var ingresses networkingv1.IngressList var gateways []gatewayv1beta1.Gateway var httpRoutes []gatewayv1beta1.HTTPRoute - providerByName, err := constructProviders(&ProviderConf{ - Client: cl, - }, providers) + providerByName, err := constructProviders(providers) if err != nil { return nil, nil, err } @@ -60,7 +47,7 @@ func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile stri resources := InputResources{} if inputFile != "" { - if err = ConstructResourcesFromFile(&ingresses, inputFile, namespace, false); err != nil { + if err = ConstructIngressesFromFile(&ingresses, inputFile, namespace); err != nil { return nil, nil, fmt.Errorf("failed to read ingresses from file: %w", err) } resources.Ingresses = ingresses.Items @@ -68,11 +55,21 @@ func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile stri return nil, nil, err } } else { + conf, err := config.GetConfig() + if err != nil { + return nil, nil, fmt.Errorf("failed to get client config: %w", err) + } + + cl, err := client.New(conf, client.Options{}) + if err != nil { + return nil, nil, fmt.Errorf("failed to create client: %w", err) + } + cl = client.NewNamespacedClient(cl, namespace) if err = ConstructIngressesFromCluster(ctx, cl, &ingresses); err != nil { return nil, nil, fmt.Errorf("failed to read ingresses from cluster: %w", err) } resources.Ingresses = ingresses.Items - if err = readProviderResourcesFromCluster(ctx, providerByName, &resources); err != nil { + if err = readProviderResourcesFromCluster(ctx, cl, providerByName, &resources); err != nil { return nil, nil, err } } @@ -95,19 +92,6 @@ func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile stri return httpRoutes, gateways, nil } -// TODO: comment -// inputFile is always set here, as we perform valdiation in the Cobra PreRun phase. -func FetchAllResources(ctx context.Context, namespace string, inputFile string) ([]client.Object, error) { - if err = ConstructResourcesFromFile(&ingresses, inputFile, namespace, true); err != nil { - return nil, fmt.Errorf("failed to read ingresses from file: %w", err) - } - resources.Ingresses = ingresses.Items - if err = readProviderResourcesFromFile(ctx, providerByName, &resources, inputFile); err != nil { - return nil, nil, err - } - return nil, nil -} - func readProviderResourcesFromFile(ctx context.Context, providerByName map[ProviderName]Provider, resources *InputResources, inputFile string) error { for name, provider := range providerByName { if err := provider.ReadResourcesFromFiles(ctx, resources.CustomResources, inputFile); err != nil { @@ -117,9 +101,9 @@ func readProviderResourcesFromFile(ctx context.Context, providerByName map[Provi return nil } -func readProviderResourcesFromCluster(ctx context.Context, providerByName map[ProviderName]Provider, resources *InputResources) error { +func readProviderResourcesFromCluster(ctx context.Context, cl client.Client, providerByName map[ProviderName]Provider, resources *InputResources) error { for name, provider := range providerByName { - if err := provider.ReadResourcesFromCluster(ctx, resources.CustomResources); err != nil { + if err := provider.ReadResourcesFromCluster(ctx, cl, resources.CustomResources); err != nil { return fmt.Errorf("failed to read %s resources from the cluster: %w", name, err) } } @@ -136,7 +120,7 @@ func ConstructIngressesFromCluster(ctx context.Context, cl client.Client, ingres // constructProviders constructs a map of concrete Provider implementations // by their ProviderName. -func constructProviders(conf *ProviderConf, providers []string) (map[ProviderName]Provider, error) { +func constructProviders(providers []string) (map[ProviderName]Provider, error) { providerByName := make(map[ProviderName]Provider, len(ProviderConstructorByName)) for _, requestedProvider := range providers { @@ -145,7 +129,8 @@ func constructProviders(conf *ProviderConf, providers []string) (map[ProviderNam if !ok { return nil, fmt.Errorf("%s is not a supported provider", requestedProvider) } - providerByName[requestedProviderName] = newProviderFunc(conf) + conf := ProviderConfByName[requestedProviderName] + providerByName[requestedProviderName] = newProviderFunc(&conf) } return providerByName, nil @@ -195,10 +180,10 @@ func extractObjectsFromReader(reader io.Reader) ([]*unstructured.Unstructured, e return finalObjs, nil } -// ConstructResourcesFromFile reads the inputFile in either json/yaml formats, +// 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 ConstructResourcesFromFile(l *networkingv1.IngressList, inputFile string, namespace string, allResource bool) error { +func ConstructIngressesFromFile(l *networkingv1.IngressList, inputFile string, namespace string) error { stream, err := os.ReadFile(inputFile) if err != nil { return err @@ -228,6 +213,31 @@ func ConstructResourcesFromFile(l *networkingv1.IngressList, inputFile string, n return nil } +// ConstructOtherResourcesFromFile reads the inputFile in either json/yaml formats, +// then deserialize the file into client.object resources. +func ConstructOtherResourcesFromFile(namespace string, inputFile string, providers []string) ([]*unstructured.Unstructured, error) { + providerByName, err := constructProviders(providers) + if err != nil { + return nil, err + } + + stream, err := os.ReadFile(inputFile) + if err != nil { + return nil, err + } + + reader := bytes.NewReader(stream) + objs, err := extractObjectsFromReader(reader) + if err != nil { + return nil, err + } + + for _, p := range providerByName { + objs = p.Filter(objs) + } + return objs, err +} + func aggregatedErrs(errs field.ErrorList) error { errMsg := fmt.Errorf("\n# Encountered %d errors", len(errs)) for _, err := range errs { diff --git a/pkg/i2gw/ingress2gateway_test.go b/pkg/i2gw/ingress2gateway_test.go index 3ef8ce78..7b9fc13b 100644 --- a/pkg/i2gw/ingress2gateway_test.go +++ b/pkg/i2gw/ingress2gateway_test.go @@ -61,7 +61,7 @@ func Test_constructIngressesFromFile(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { gotIngressList := &networkingv1.IngressList{} - err := ConstructResourcesFromFile(gotIngressList, tc.filePath, tc.namespace) + err := ConstructIngressesFromFile(gotIngressList, tc.filePath, tc.namespace) if err != nil { t.Errorf("Failed to open test file: %v", err) } @@ -174,10 +174,7 @@ func Test_constructProviders(t *testing.T) { }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - cl := fake.NewClientBuilder().WithRuntimeObjects([]runtime.Object{}...).Build() - providerByName, err := constructProviders(&ProviderConf{ - Client: cl, - }, tc.providers) + providerByName, err := constructProviders(tc.providers) if tc.expectedError != nil { if err == nil { t.Errorf("Expected error but got none") diff --git a/pkg/i2gw/provider.go b/pkg/i2gw/provider.go index 6dcf9e8b..6e6ef2fd 100644 --- a/pkg/i2gw/provider.go +++ b/pkg/i2gw/provider.go @@ -20,6 +20,8 @@ import ( "context" networkingv1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation/field" "sigs.k8s.io/controller-runtime/pkg/client" @@ -32,6 +34,9 @@ import ( // func at startup. var ProviderConstructorByName = map[ProviderName]ProviderConstructor{} +// TODO: comment +var ProviderConfByName = map[ProviderName]ProviderConf{} + // ProviderName is a string alias that stores the concrete Provider name. type ProviderName string @@ -42,7 +47,14 @@ type ProviderConstructor func(conf *ProviderConf) Provider // ProviderConf contains all the configuration required for every concrete // Provider implementation. type ProviderConf struct { - Client client.Client + FilteredObjects []schema.GroupKind +} + +var DefaultFilteredObjects = []schema.GroupKind{ + { + Group: networkingv1.SchemeGroupVersion.Group, + Kind: "Ingress", + }, } // The Provider interface specifies the required functionality which needs to be @@ -51,13 +63,14 @@ type ProviderConf struct { type Provider interface { CustomResourceReader ResourceConverter + ResourceFilter } type CustomResourceReader interface { // ReadResourcesFromCluster reads custom resources associated with // the underlying Provider implementation from the kubernetes cluster. - ReadResourcesFromCluster(ctx context.Context, customResources interface{}) error + ReadResourcesFromCluster(ctx context.Context, cl client.Client, customResources interface{}) error // ReadResourcesFromFiles reads custom resources associated with // the underlying Provider implementation from the files. @@ -73,6 +86,11 @@ type ResourceConverter interface { ToGatewayAPI(resources InputResources) (GatewayResources, field.ErrorList) } +type ResourceFilter interface { + // Filter filters out the objects that must not be printed. + Filter([]*unstructured.Unstructured) []*unstructured.Unstructured +} + // InputResources contains all Ingress objects, and Provider specific // custom resources. type InputResources struct { diff --git a/pkg/i2gw/providers/ingressnginx/filter.go b/pkg/i2gw/providers/ingressnginx/filter.go new file mode 100644 index 00000000..c3dd233a --- /dev/null +++ b/pkg/i2gw/providers/ingressnginx/filter.go @@ -0,0 +1,32 @@ +package ingressnginx + +import ( + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func init() { + i2gw.ProviderConfByName[Name] = i2gw.ProviderConf{ + FilteredObjects: i2gw.DefaultFilteredObjects, + } +} + +func (c *converter) Filter(objects []*unstructured.Unstructured) []*unstructured.Unstructured { + filteredObjects := []*unstructured.Unstructured{} + + for _, o := range objects { + var filterOut bool + for _, f := range c.conf.FilteredObjects { + if o.GetObjectKind().GroupVersionKind().Group == f.Group && + o.GetObjectKind().GroupVersionKind().Kind == f.Kind { + filterOut = true + break + } + } + if !filterOut { + filteredObjects = append(filteredObjects, o) + } + } + + return filteredObjects +} diff --git a/pkg/i2gw/providers/ingressnginx/resource_reader.go b/pkg/i2gw/providers/ingressnginx/resource_reader.go index 71dac831..e9b0b813 100644 --- a/pkg/i2gw/providers/ingressnginx/resource_reader.go +++ b/pkg/i2gw/providers/ingressnginx/resource_reader.go @@ -20,6 +20,7 @@ import ( "context" "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw" + "sigs.k8s.io/controller-runtime/pkg/client" ) // converter implements the i2gw.CustomResourceReader interface. @@ -34,7 +35,7 @@ func newResourceReader(conf *i2gw.ProviderConf) *resourceReader { } } -func (r *resourceReader) ReadResourcesFromCluster(ctx context.Context, customResources interface{}) error { +func (r *resourceReader) ReadResourcesFromCluster(ctx context.Context, cl client.Client, customResources interface{}) error { // ingress-nginx does not have any CRDs. return nil } diff --git a/pkg/i2gw/providers/kong/filter.go b/pkg/i2gw/providers/kong/filter.go new file mode 100644 index 00000000..5f1942d0 --- /dev/null +++ b/pkg/i2gw/providers/kong/filter.go @@ -0,0 +1,44 @@ +package kong + +import ( + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var filteredObjects = []schema.GroupKind{ + { + Group: "configuration.konghq.com", + Kind: "TCPIngress", + }, + { + Group: "configuration.konghq.com", + Kind: "UDPIngress", + }, +} + +func init() { + i2gw.ProviderConfByName[Name] = i2gw.ProviderConf{ + FilteredObjects: append(i2gw.DefaultFilteredObjects, filteredObjects...), + } +} + +func (c *converter) Filter(objects []*unstructured.Unstructured) []*unstructured.Unstructured { + filteredObjects := []*unstructured.Unstructured{} + + for _, o := range objects { + var filterOut bool + for _, f := range c.conf.FilteredObjects { + if o.GetObjectKind().GroupVersionKind().Group == f.Group && + o.GetObjectKind().GroupVersionKind().Kind == f.Kind { + filterOut = true + break + } + } + if !filterOut { + filteredObjects = append(filteredObjects, o) + } + } + + return filteredObjects +} diff --git a/pkg/i2gw/providers/kong/resource_reader.go b/pkg/i2gw/providers/kong/resource_reader.go index 39a951d5..a97aab46 100644 --- a/pkg/i2gw/providers/kong/resource_reader.go +++ b/pkg/i2gw/providers/kong/resource_reader.go @@ -20,6 +20,7 @@ import ( "context" "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw" + "sigs.k8s.io/controller-runtime/pkg/client" ) // converter implements the i2gw.CustomResourceReader interface. @@ -34,7 +35,7 @@ func newResourceReader(conf *i2gw.ProviderConf) *resourceReader { } } -func (r *resourceReader) ReadResourcesFromCluster(ctx context.Context, customResources interface{}) error { +func (r *resourceReader) ReadResourcesFromCluster(ctx context.Context, cl client.Client, customResources interface{}) error { return nil }