diff --git a/cmd/print.go b/cmd/print.go index e7cf3656..06d88910 100644 --- a/cmd/print.go +++ b/cmd/print.go @@ -89,16 +89,21 @@ func (pr *PrintRunner) PrintGatewayAPIObjects(cmd *cobra.Command, _ []string) er } gatewayResources, notificationTablesMap, err := i2gw.ToGatewayAPIResources(cmd.Context(), pr.namespaceFilter, pr.inputFile, pr.providers, pr.getProviderSpecificFlags()) - if err != nil { - return err - } for _, table := range notificationTablesMap { fmt.Fprintln(os.Stderr, table) } + // Output partial results even if there are conversion errors (best-effort conversion) pr.outputResult(gatewayResources) + // Return error after outputting partial results + if err != nil { + // Silence usage on conversion errors since these are not command usage errors + cmd.SilenceUsage = true + return err + } + return nil } diff --git a/pkg/i2gw/ingress2gateway.go b/pkg/i2gw/ingress2gateway.go index 7f7c663c..ff662a31 100644 --- a/pkg/i2gw/ingress2gateway.go +++ b/pkg/i2gw/ingress2gateway.go @@ -84,7 +84,8 @@ func ToGatewayAPIResources(ctx context.Context, namespace string, inputFile stri } notificationTablesMap := notifications.NotificationAggr.CreateNotificationTables() if len(errs) > 0 { - return nil, notificationTablesMap, aggregatedErrs(errs) + // Return partial results along with errors to support best-effort conversion + return gatewayResources, notificationTablesMap, aggregatedErrs(errs) } return gatewayResources, notificationTablesMap, nil diff --git a/pkg/i2gw/providers/apisix/converter.go b/pkg/i2gw/providers/apisix/converter.go index 81412b64..b3dc0b7e 100644 --- a/pkg/i2gw/providers/apisix/converter.go +++ b/pkg/i2gw/providers/apisix/converter.go @@ -50,9 +50,6 @@ func (c *resourcesToIRConverter) convertToIR(storage *storage) (intermediate.IR, // Convert plain ingress resources to gateway resources, ignoring all // provider-specific features. ir, errs := common.ToIR(ingressList, storage.ServicePorts, 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. diff --git a/pkg/i2gw/providers/cilium/converter.go b/pkg/i2gw/providers/cilium/converter.go index c72612f9..1ab1972a 100644 --- a/pkg/i2gw/providers/cilium/converter.go +++ b/pkg/i2gw/providers/cilium/converter.go @@ -50,9 +50,6 @@ func (c *resourcesToIRConverter) convertToIR(storage *storage) (intermediate.IR, // Convert plain ingress resources to gateway resources, ignoring all // provider-specific features. ir, errs := common.ToIR(ingressList, storage.ServicePorts, 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. diff --git a/pkg/i2gw/providers/common/converter.go b/pkg/i2gw/providers/common/converter.go index c217b940..8ee6b59b 100644 --- a/pkg/i2gw/providers/common/converter.go +++ b/pkg/i2gw/providers/common/converter.go @@ -51,9 +51,6 @@ func ToIR(ingresses []networkingv1.Ingress, servicePorts map[types.NamespacedNam } routes, gateways, errs := aggregator.toHTTPRoutesAndGateways(options) - if len(errs) > 0 { - return intermediate.IR{}, errs - } routeByKey := make(map[types.NamespacedName]intermediate.HTTPRouteContext) for _, routeWithSources := range routes { @@ -81,7 +78,7 @@ func ToIR(ingresses []networkingv1.Ingress, servicePorts map[types.NamespacedNam GRPCRoutes: make(map[types.NamespacedName]gatewayv1.GRPCRoute), BackendTLSPolicies: make(map[types.NamespacedName]gatewayv1.BackendTLSPolicy), ReferenceGrants: make(map[types.NamespacedName]gatewayv1beta1.ReferenceGrant), - }, nil + }, errs } var ( diff --git a/pkg/i2gw/providers/common/converter_test.go b/pkg/i2gw/providers/common/converter_test.go index 54c770aa..169fe996 100644 --- a/pkg/i2gw/providers/common/converter_test.go +++ b/pkg/i2gw/providers/common/converter_test.go @@ -446,8 +446,47 @@ func Test_ToIR(t *testing.T) { {Namespace: "test", Name: "example2"}: {"http": 8080}, }, expectedIR: intermediate.IR{ - Gateways: map[types.NamespacedName]intermediate.GatewayContext{}, - HTTPRoutes: map[types.NamespacedName]intermediate.HTTPRouteContext{}, + Gateways: map[types.NamespacedName]intermediate.GatewayContext{ + {Namespace: "test", Name: "named-ports"}: { + Gateway: gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{Name: "named-ports", Namespace: "test"}, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: "named-ports", + Listeners: []gatewayv1.Listener{{ + Name: "example-com-http", + Port: 80, + Protocol: gatewayv1.HTTPProtocolType, + Hostname: PtrTo(gatewayv1.Hostname("example.com")), + }}, + }, + }, + }, + }, + HTTPRoutes: map[types.NamespacedName]intermediate.HTTPRouteContext{ + {Namespace: "test", Name: "named-ports-example-com"}: { + HTTPRoute: gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{Name: "named-ports-example-com", Namespace: "test"}, + Spec: gatewayv1.HTTPRouteSpec{ + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: []gatewayv1.ParentReference{{ + Name: "named-ports", + }}, + }, + Hostnames: []gatewayv1.Hostname{"example.com"}, + Rules: []gatewayv1.HTTPRouteRule{{ + Matches: []gatewayv1.HTTPRouteMatch{{ + Path: &gatewayv1.HTTPPathMatch{ + Type: &gPathPrefix, + Value: PtrTo("/foo"), + }, + }}, + BackendRefs: []gatewayv1.HTTPBackendRef{}, + }}, + }, + }, + RuleBackendSources: [][]intermediate.BackendSource{{}}, + }, + }, }, expectedErrors: field.ErrorList{field.Invalid(field.NewPath(""), "", "")}, }, diff --git a/pkg/i2gw/providers/gce/ir_converter.go b/pkg/i2gw/providers/gce/ir_converter.go index c5f1052e..4d6ba03d 100644 --- a/pkg/i2gw/providers/gce/ir_converter.go +++ b/pkg/i2gw/providers/gce/ir_converter.go @@ -74,15 +74,12 @@ func (c *resourcesToIRConverter) convertToIR(storage *storage) (intermediate.IR, // Convert plain ingress resources to gateway resources, ignoring all // provider-specific features. + // Continue processing even with errors to support best-effort conversion ir, errs := common.ToIR(ingressList, storage.ServicePorts, c.implementationSpecificOptions) - if len(errs) > 0 { - return intermediate.IR{}, errs - } - errs = setGCEGatewayClasses(ingressList, ir.Gateways) - if len(errs) > 0 { - return intermediate.IR{}, errs - } + // Accumulate errors from setting GCE gateway classes but continue processing + setGCEErrs := setGCEGatewayClasses(ingressList, ir.Gateways) + errs = append(errs, setGCEErrs...) buildGceGatewayIR(c.ctx, storage, &ir) buildGceServiceIR(c.ctx, storage, &ir) return ir, errs diff --git a/pkg/i2gw/providers/ingressnginx/converter.go b/pkg/i2gw/providers/ingressnginx/converter.go index 911a4b73..3eba8590 100644 --- a/pkg/i2gw/providers/ingressnginx/converter.go +++ b/pkg/i2gw/providers/ingressnginx/converter.go @@ -45,9 +45,6 @@ func (c *resourcesToIRConverter) convert(storage *storage) (intermediate.IR, fie // Convert plain ingress resources to gateway resources, ignoring all // provider-specific features. ir, errs := common.ToIR(ingressList, storage.ServicePorts, i2gw.ProviderImplementationSpecificOptions{}) - 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. diff --git a/pkg/i2gw/providers/ingressnginx/converter_test.go b/pkg/i2gw/providers/ingressnginx/converter_test.go index 2b8e273d..f4033ae5 100644 --- a/pkg/i2gw/providers/ingressnginx/converter_test.go +++ b/pkg/i2gw/providers/ingressnginx/converter_test.go @@ -337,7 +337,42 @@ func Test_ToIR(t *testing.T) { }, }, }, - expectedIR: intermediate.IR{}, + expectedIR: intermediate.IR{ + Gateways: map[types.NamespacedName]intermediate.GatewayContext{ + {Namespace: "default", Name: "ingress-nginx"}: { + Gateway: gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{Name: "ingress-nginx", Namespace: "default"}, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: "ingress-nginx", + Listeners: []gatewayv1.Listener{{ + Name: "test-mydomain-com-http", + Port: 80, + Protocol: gatewayv1.HTTPProtocolType, + Hostname: ptrTo(gatewayv1.Hostname("test.mydomain.com")), + }}, + }, + }, + }, + }, + HTTPRoutes: map[types.NamespacedName]intermediate.HTTPRouteContext{ + {Namespace: "default", Name: "implementation-specific-regex-test-mydomain-com"}: { + HTTPRoute: gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{Name: "implementation-specific-regex-test-mydomain-com", Namespace: "default"}, + Spec: gatewayv1.HTTPRouteSpec{ + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: []gatewayv1.ParentReference{{ + Name: "ingress-nginx", + }}, + }, + Hostnames: []gatewayv1.Hostname{"test.mydomain.com"}, + // Rules is empty because the path conversion failed + Rules: []gatewayv1.HTTPRouteRule{}, + }, + }, + RuleBackendSources: [][]intermediate.BackendSource{}, + }, + }, + }, expectedErrors: field.ErrorList{ { Type: field.ErrorTypeInvalid, diff --git a/pkg/i2gw/providers/kong/converter.go b/pkg/i2gw/providers/kong/converter.go index 067ed4d1..c0de2b30 100644 --- a/pkg/i2gw/providers/kong/converter.go +++ b/pkg/i2gw/providers/kong/converter.go @@ -55,9 +55,6 @@ func (c *resourcesToIRConverter) convert(storage *storage) (intermediate.IR, fie // Convert plain ingress resources to gateway resources, ignoring all // provider-specific features. ir, errorList := common.ToIR(ingressList, storage.ServicePorts, c.implementationSpecificOptions) - if len(errorList) > 0 { - return intermediate.IR{}, errorList - } tcpGatewayIR, notificationsAggregator, errs := crds.TCPIngressToGatewayIR(storage.TCPIngresses) if len(errs) > 0 { @@ -66,15 +63,10 @@ func (c *resourcesToIRConverter) convert(storage *storage) (intermediate.IR, fie dispatchNotification(notificationsAggregator) - if len(errorList) > 0 { - return intermediate.IR{}, errorList - } - - ir, errs = intermediate.MergeIRs(ir, tcpGatewayIR) - - if len(errs) > 0 { - return intermediate.IR{}, errs - } + // Merge IRs and accumulate errors, continuing best-effort conversion + var mergeErrs field.ErrorList + ir, mergeErrs = intermediate.MergeIRs(ir, tcpGatewayIR) + errorList = append(errorList, mergeErrs...) for _, parseFeatureFunc := range c.featureParsers { // Apply the feature parsing function to the gateway resources, one by one. diff --git a/pkg/i2gw/providers/nginx/converter.go b/pkg/i2gw/providers/nginx/converter.go index 33b91f42..b99eb7af 100644 --- a/pkg/i2gw/providers/nginx/converter.go +++ b/pkg/i2gw/providers/nginx/converter.go @@ -57,9 +57,6 @@ func (c *resourcesToIRConverter) convert(storage *storage) (intermediate.IR, fie } ir, errorList := common.ToIR(ingressList, storage.ServicePorts, c.implementationSpecificOptions) - if len(errorList) > 0 { - return intermediate.IR{}, errorList - } for _, parseFeatureFunc := range c.featureParsers { errs := parseFeatureFunc(ingressList, storage.ServicePorts, &ir)