Skip to content

feat: Add Gloo Edge provider for basic VirtualService to HTTPRoute translation - #108

Open
shivansh-source wants to merge 17 commits into
kgateway-dev:mainfrom
shivansh-source:shivansh#3
Open

feat: Add Gloo Edge provider for basic VirtualService to HTTPRoute translation#108
shivansh-source wants to merge 17 commits into
kgateway-dev:mainfrom
shivansh-source:shivansh#3

Conversation

@shivansh-source

Copy link
Copy Markdown

What type of PR is this?

/kind feature

What this PR does / why we need it:

This PR implements a new Gloo Edge provider for ingress2gateway that enables conversion of basic Gloo Edge VirtualService resources into Kubernetes Gateway API HTTPRoute manifests.

Features Implemented:

  • Read VirtualService CRDs from cluster or YAML file
  • Extract spec.hosts[] → map to HTTPRoute spec.hostnames[]
  • Extract spec.virtualHost.routes[] → map to HTTPRoute spec.rules[]
  • Support prefix-based path matching (matchers[].prefixPathPrefix)
  • Reference upstreams in routeAction.single.upstream
  • Provider registration following ingress-nginx pattern
  • unit tests validating end-to-end conversion
  • Wired to cli
  • added test file to test the reading virtual service from yaml

Example Conversion

Input (Gloo Edge VirtualService):

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: example-vs
  namespace: default
spec:
  hosts:
    - example.com
  virtualHost:
    routes:
      - matchers:
          - prefix: /api
        routeAction:
          single:
            upstream:
              name: my-service
              namespace: default

Output (Gateway API HTTPRoute):

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: example-vs-example-com
  namespace: default
spec:
  hostnames:
    - example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: my-service
          namespace: default

Files Changed:
Added pkg/i2gw/providers/glooedge/glooedge.go - Provider initialization
Added pkg/i2gw/providers/glooedge/types.go - Gloo Edge type definitions
Added pkg/i2gw/providers/glooedge/storage.go - Resource storage
Added pkg/i2gw/providers/glooedge/resource_reader.go - Read VirtualServices
Added pkg/i2gw/providers/glooedge/converter.go - Convert to Provider IR
Added pkg/i2gw/providers/glooedge/util.go - Helper utilities
Added pkg/i2gw/providers/glooedge/converter_test.go - Unit tests
Added pkg/i2gw/providers/glooedge/README.md - Provider documentation
Added test_virtualservice.yaml - Example VirtualService (delete later)

Which issue(s) this PR fixes:

Fixes #102

Add new Gloo Edge provider to ingress2gateway for converting Gloo Edge VirtualService resources to Kubernetes Gateway API HTTPRoute manifests. Supports basic routing with hosts, prefix-based path matching, and single upstream references.

Does this PR introduce a user-facing change?:

Add new Gloo Edge provider to ingress2gateway for converting Gloo Edge VirtualService resources to Kubernetes Gateway API HTTPRoute manifests. Supports basic routing with hosts, prefix-based path matching, and single upstream references.

Screenshot 2026-04-02 005214

…anslation

Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
…tual service yaml for testing

Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
@shivansh-source

Copy link
Copy Markdown
Author

parent issue #107

Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
@shivansh-source

Copy link
Copy Markdown
Author

/cc @danehans

@shivansh-source

Copy link
Copy Markdown
Author

all the previous problem from last pr has been solved in this pr

Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
@danehans

Copy link
Copy Markdown
Collaborator

This PR does not properly fix issue #102. The new tests pass but it only covers the PR’s invented hosts model. In local end-to-end CLI checks, a real VirtualService fails to parse, and a non-default sample produces an unbindable HTTPRoute. Here are my suggestions to fix this PR:

  • Parse the real schema: spec.virtualHost.domains.
  • Read and resolve Upstream resources so single.upstream becomes Service name/namespace/port.
  • Keep Gateway and HTTPRoute namespace semantics consistent.
  • Add fixtures/tests for real Gloo manifests, discovered upstream names, and non-default namespaces.
  • Clean up docs: this PR adds pkg/i2gw/providers/glooedge/README .md with a stray space (should be pkg/i2gw/providers/glooedge/README.md,. The usage example says --provider and --output-file, but the actual CLI is --providers and has no --output-file; it prints to stdout instead.

Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
@shivansh-source

Copy link
Copy Markdown
Author

/cc @danehans ✅ Fixed README filename (no space) ✅ Fixed CLI docs (--providers, no --output-file) ✅ Parse spec.virtualHost.domains correctly ✅ Read and resolve Upstream resources ✅ Handle namespaces correctly (production, default) ✅ Comprehensive test cases with fixtures

@shivansh-source

Copy link
Copy Markdown
Author

made example file inexamples/glooedge/ u can test in with them also added the test cases

Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
@shivansh-source

Copy link
Copy Markdown
Author

@danehans removed the shadow err mistake

Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
…sult

Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
@danehans

Copy link
Copy Markdown
Collaborator

The current CI failure is listener-order nondeterminism. The cause is in converter.go. The code stores listeners in a map and then materializes the slice with:

for _, listener := range listenersByNamespaceHost[namespace] {
    listeners = append(listeners, *listener)
}

Since listenersByNamespaceHost[namespace] is a Go map, that iteration order is undefined.

What I’d suggest:

  • Sort listeners before assigning gateway.Spec.Listeners, ideally by listener name or hostname.
  • Optionally also make the test sort both got and want before DeepEqual, but that should be secondary.

A simple fix would be:

hostKeys := make([]string, 0, len(listenersByNamespaceHost[namespace]))
for host := range listenersByNamespaceHost[namespace] {
    hostKeys = append(hostKeys, host)
}
slices.Sort(hostKeys)

listeners := make([]gatewayv1.Listener, 0, len(hostKeys))
for _, host := range hostKeys {
    listeners = append(listeners, *listenersByNamespaceHost[namespace][host])
}
gateway.Spec.Listeners = listeners

That matches our general pattern: the common converter already sorts map-derived keys before building output.

}

func readUpstreamsFromFile(_ io.Reader, _ string) ([]*Upstream, error) {
// TODO: Implement reading Upstreams from file

@danehans danehans Apr 29, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upstream resolution needs to be implemented. Without it, storage.Upstreams is always empty, so we always fallback to emitting the upstream object’s own name with port: 0.

The reason this issue slips through tests is that the tests are encoding the broken behavior. The setup only adds a VirtualService to storage, not any Upstream. And several expectations explicitly assert port: 0, so the suite passes even though discovery/resolution never happens.

The fix I suggest is:

  • Implement real Upstream readers for file and optionally cluster (create a tracker issue if left unimplemented).
  • Extend types.go so Upstream carries the resolved Service identity, not just the upstream resource name. It should have fields like ServiceName, ServiceNamespace, and ServicePort.
  • Parse spec.kube.serviceName, serviceNamespace, and servicePort from Upstream resources.
  • In the converter, emit backendRefs from those resolved Service fields. If an Upstream can’t be resolved, return a conversion error or at least a warning instead of silently emitting port: 0.
  • Update tests to seed real Upstreams or use fixture-driven file reads, and assert outputs like petstore/default:8080 and users-api/production:9090.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it i thought it could be implemented later

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is implemented now

// NEW FUNCTIONS TO READ UPSTREAMS (like nginx reads Services)

func readUpstreamsFromCluster(_ context.Context, _ interface{}) ([]*Upstream, error) {
// TODO: Implement reading Upstreams from cluster

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made a trackker issue for this #112

… Fixes: file-based Upstream reading for Gloo Edge migration

Tracker: kgateway-dev#112 (cluster reading)

Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
// TODO: Implement cluster reading for Upstreams using dynamic client
// For now, return empty list as fallback
// This would use client.Resource(upstreamGVR).List(ctx, metav1.ListOptions{})
return []*Upstream{}, nil

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a stub return an error until it's implemented. Something like "“not supported yet, see issue #112”.

Comment on lines +162 to +169
if !exists {
// Fallback: create basic upstream reference with port 0
upstream = &Upstream{
Name: route.RouteAction.Single.Upstream.Name,
Namespace: route.RouteAction.Single.Upstream.Namespace,
Port: 0,
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fail fast on malformed Upstreams instead of producing port: 0.


func (r *resourceReader) readResourcesFromFile(reader io.Reader) (*storage, error) {
storage := newResourcesStorage()
allResources, err := common.ExtractObjectsFromReader(reader, "")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing "" disables namespace filtering, so --namespace default still lets production objects through.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a regression test for this:

  • Input file contains one default VirtualService and one production VirtualService.
  • Run with ProviderConf.Namespace = "default".
  • Assert only the default resources make it into storage.

Signed-off-by: shivansh-source <shivanshsiddhi1234@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add initial Gloo Edge provider: VirtualService → HTTPRoute (basic translation)

2 participants