Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
500d32f
controller_offline.go: register extension-server policy kinds in clie…
daum3ns Oct 21, 2025
b5a11cd
controller_offline.go: add comments about possible missing extension …
daum3ns Oct 21, 2025
52f87ac
load.go: temporary treat all unknown kinds as extension server policy
daum3ns Oct 21, 2025
2656f7f
runner.go: pass extension policy GVKs in translator
daum3ns Oct 21, 2025
8be0086
file provider: persist ExtensionServerPolicies to store
daum3ns Oct 21, 2025
e81baf1
test: add offline controller test for custom extension resources
daum3ns Oct 22, 2025
3176745
config loader test: add testcase for standalone mode with extension
daum3ns Oct 22, 2025
6beec4b
test(config/decoder): Add a new test case to that verifies decoding o…
daum3ns Oct 22, 2025
271224a
set default namespace in custom resources if no namespace is provided
daum3ns Oct 23, 2025
acc84fa
test/controller_offline: extend test to verify the CR can be loaded from
daum3ns Oct 23, 2025
310229c
add missin yaml for test
daum3ns Oct 24, 2025
c7990fe
configloader test: fix race condition
daum3ns Oct 24, 2025
cc48acd
add configuration to provider struct, pass it along so that the loader
daum3ns Oct 24, 2025
73d2f3f
add missing yaml for decoder test
daum3ns Oct 24, 2025
f9076f0
fix linter warnings
daum3ns Oct 30, 2025
85d850e
controller_offline_test: extend test to show the issue also affects
daum3ns Oct 30, 2025
b34877d
fix testcase, register all extensions im scheme
daum3ns Oct 31, 2025
0bea998
load.go: store route filter resources and custom backend resources in…
daum3ns Nov 4, 2025
d16333f
file provider: persist ExtensionRefFilters to store
daum3ns Nov 4, 2025
6cca830
controller_offline_test: adapt test expectation
daum3ns Nov 4, 2025
73603d1
typo
daum3ns Nov 4, 2025
bfc83c6
adapt YAML file for controller_offline.test
daum3ns Nov 4, 2025
b87e5d8
extend decoder test with extensionmanager.resources and
daum3ns Nov 4, 2025
73d0323
revises from review: rename loop variable, use envoyGateway directly
daum3ns Nov 5, 2025
83e3b28
fix linter errors
daum3ns Nov 5, 2025
a0fb40d
Refactor loadKubernetesYAMLToResources to get rid of the default label
daum3ns Nov 6, 2025
361e745
controller_offline_test: add testcase that shows problem with
daum3ns Nov 7, 2025
d2847aa
controller_offline: register missing indices
daum3ns Nov 7, 2025
2e48e33
update helm module to fix vulnerability in containerd
daum3ns Nov 11, 2025
b426305
controller_offline_test: extend index registration test to check all
daum3ns Nov 11, 2025
a7478c4
Revert "update helm module to fix vulnerability in containerd"
daum3ns Nov 11, 2025
676c888
linter
daum3ns Nov 11, 2025
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
2 changes: 1 addition & 1 deletion internal/cmd/egctl/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func translate(w io.Writer, inFile, inType string, outTypes []string, output, re

if inType == gatewayAPIType {
// Unmarshal input
resources, err := resource.LoadResourcesFromYAMLBytes(inBytes, addMissingResources)
resources, err := resource.LoadResourcesFromYAMLBytes(inBytes, addMissingResources, nil)
if err != nil {
return fmt.Errorf("unable to unmarshal input: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/egctl/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func runValidate(w io.Writer, inFile string) error {
noErr := true
_ = resource.IterYAMLBytes(inBytes, func(yamlByte []byte) error {
// Passing each resource as YAML string and get all their errors from local validator.
_, err = resource.LoadResourcesFromYAMLBytes(yamlByte, false)
_, err = resource.LoadResourcesFromYAMLBytes(yamlByte, false, nil)
if err != nil {
noErr = false
yamlRows := bytes.Split(yamlByte, []byte("\n"))
Expand Down
82 changes: 82 additions & 0 deletions internal/envoygateway/config/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,88 @@ func TestDecode(t *testing.T) {
},
expect: true,
},
{
in: inPath + "standalone-extension-server.yaml",
out: &egv1a1.EnvoyGateway{
TypeMeta: metav1.TypeMeta{
Kind: egv1a1.KindEnvoyGateway,
APIVersion: egv1a1.GroupVersion.String(),
},
EnvoyGatewaySpec: egv1a1.EnvoyGatewaySpec{
Gateway: egv1a1.DefaultGateway(),
Provider: &egv1a1.EnvoyGatewayProvider{
Type: egv1a1.ProviderTypeCustom,
Custom: &egv1a1.EnvoyGatewayCustomProvider{
Resource: egv1a1.EnvoyGatewayResourceProvider{
Type: egv1a1.ResourceProviderTypeFile,
File: &egv1a1.EnvoyGatewayFileResourceProvider{
Paths: []string{
"/tmp/envoy-gateway-test",
},
},
},
Infrastructure: &egv1a1.EnvoyGatewayInfrastructureProvider{
Type: egv1a1.InfrastructureProviderTypeHost,
Host: &egv1a1.EnvoyGatewayHostInfrastructureProvider{},
},
},
},
Logging: egv1a1.DefaultEnvoyGatewayLogging(),
ExtensionManager: &egv1a1.ExtensionManager{
Resources: []egv1a1.GroupVersionKind{
{
Group: "gateway.example.io",
Version: "v1alpha1",
Kind: "CustomRouteFilterResource",
},
},
BackendResources: []egv1a1.GroupVersionKind{
{
Group: "storage.example.io",
Version: "v1",
Kind: "S3Bucket",
},
},
PolicyResources: []egv1a1.GroupVersionKind{
{
Group: "gateway.example.io",
Version: "v1alpha1",
Kind: "ExampleExtPolicy",
},
},
Hooks: &egv1a1.ExtensionHooks{
XDSTranslator: &egv1a1.XDSTranslatorHooks{
Post: []egv1a1.XDSTranslatorHook{
egv1a1.XDSHTTPListener,
egv1a1.XDSRoute,
egv1a1.XDSVirtualHost,
egv1a1.XDSCluster,
egv1a1.XDSTranslation,
},
},
},
Service: &egv1a1.ExtensionService{
BackendEndpoint: egv1a1.BackendEndpoint{
FQDN: &egv1a1.FQDNEndpoint{
Hostname: "127.0.0.1",
Port: 5005,
},
},
},
},
ExtensionAPIs: &egv1a1.ExtensionAPISettings{
EnableBackend: true,
EnableEnvoyPatchPolicy: false,
},
RuntimeFlags: &egv1a1.RuntimeFlags{
Enabled: []egv1a1.RuntimeFlag{
"XDSNameSchemeV2",
},
},
},
},
expect: true,
},
}

for _, tc := range testCases {
Expand Down
61 changes: 60 additions & 1 deletion internal/envoygateway/config/loader/configloader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import (
"context"
_ "embed"
"os"
"sync/atomic"
"testing"

"github.com/stretchr/testify/require"

egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
"github.com/envoyproxy/gateway/internal/envoygateway/config"
)

Expand All @@ -21,6 +23,10 @@ var (
defaultConfig string
//go:embed testdata/enable-redis.yaml
redisConfig string
//go:embed testdata/standalone.yaml
standaloneConfig string
//go:embed testdata/standalone-enable-extension-server.yaml
standaloneConfigWithExtensionServer string
)

func TestConfigLoader(t *testing.T) {
Expand All @@ -35,7 +41,7 @@ func TestConfigLoader(t *testing.T) {
s, err := config.New(os.Stdout, os.Stderr)
require.NoError(t, err)

ctx, cancel := context.WithCancel(context.TODO())
ctx, cancel := context.WithCancel(context.Background())
defer func() {
cancel()
}()
Expand All @@ -57,3 +63,56 @@ func TestConfigLoader(t *testing.T) {

<-ctx.Done()
}

func TestConfigLoaderStandaloneExtensionServerAndCustomResource(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "envoy-gateway-configloader-test")
require.NoError(t, err)
defer func(path string) {
_ = os.RemoveAll(path)
}(tmpDir)

cfgPath := tmpDir + "/config.yaml"
require.NoError(t, os.WriteFile(cfgPath, []byte(standaloneConfig), 0o600))
s, err := config.New(os.Stdout, os.Stderr)
require.NoError(t, err)

ctx, cancel := context.WithCancel(context.Background())
defer func() {
cancel()
}()

type testResult struct {
changed int32
extMgr *egv1a1.ExtensionManager
}
resultChannel := make(chan testResult, 1)

var changed int32
loader := New(cfgPath, s, func(_ context.Context, cfg *config.Server) error {
c := atomic.AddInt32(&changed, 1)
t.Logf("config changed %d times", c)
if c > 1 {
resultChannel <- testResult{changed: c, extMgr: cfg.EnvoyGateway.ExtensionManager}
cancel()
}
return nil
})

require.NoError(t, loader.Start(ctx, os.Stdout))
require.NotNil(t, loader.cfg.EnvoyGateway)
require.Nil(t, loader.cfg.EnvoyGateway.ExtensionManager)

go func() {
_ = os.WriteFile(cfgPath, []byte(standaloneConfigWithExtensionServer), 0o600)
}()

<-ctx.Done()
res := <-resultChannel // wait until second reload
require.Equal(t, int32(2), res.changed)
require.NotNil(t, res.extMgr)
require.NotNil(t, res.extMgr.PolicyResources)
require.Len(t, res.extMgr.PolicyResources, 1)
require.Equal(t, "gateway.example.io", res.extMgr.PolicyResources[0].Group)
require.Equal(t, "v1alpha1", res.extMgr.PolicyResources[0].Version)
require.Equal(t, "ExampleExtPolicy", res.extMgr.PolicyResources[0].Kind)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyGateway
gateway:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
logging:
level:
default: info
provider:
type: Custom
custom:
resource:
type: File
file:
paths: ["/tmp/envoy-gateway-test"]
infrastructure:
type: Host
host: {}
extensionApis:
enableBackend: true
extensionManager:
policyResources:
- group: gateway.example.io
version: v1alpha1
kind: ExampleExtPolicy
hooks:
xdsTranslator:
post:
- HTTPListener
- Route
- VirtualHost
- Cluster
- Translation
service:
fqdn:
hostname: 127.0.0.1
port: 5005
runtimeFlags:
enabled:
- XDSNameSchemeV2
22 changes: 22 additions & 0 deletions internal/envoygateway/config/loader/testdata/standalone.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyGateway
gateway:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
logging:
level:
default: info
provider:
type: Custom
custom:
resource:
type: File
file:
paths: ["/tmp/envoy-gateway-test"]
infrastructure:
type: Host
host: {}
extensionApis:
enableBackend: true
runtimeFlags:
enabled:
- XDSNameSchemeV2
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyGateway
gateway:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
logging:
level:
default: info
provider:
type: Custom
custom:
resource:
type: File
file:
paths: ["/tmp/envoy-gateway-test"]
infrastructure:
type: Host
host: {}
extensionApis:
enableBackend: true
extensionManager:
resources:
- group: gateway.example.io
version: v1alpha1
kind: CustomRouteFilterResource
backendResources:
- group: storage.example.io
version: v1
kind: S3Bucket
policyResources:
- group: gateway.example.io
version: v1alpha1
kind: ExampleExtPolicy
hooks:
xdsTranslator:
post:
- HTTPListener
- Route
- VirtualHost
- Cluster
- Translation
service:
fqdn:
hostname: 127.0.0.1
port: 5005
runtimeFlags:
enabled:
- XDSNameSchemeV2
45 changes: 42 additions & 3 deletions internal/gatewayapi/resource/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ const dummyClusterIP = "1.2.3.4"

// LoadResourcesFromYAMLBytes will load Resources from given Kubernetes YAML string.
// TODO: This function should be able to process arbitrary number of resources, tracked by https://github.com/envoyproxy/gateway/issues/3207.
func LoadResourcesFromYAMLBytes(yamlBytes []byte, addMissingResources bool) (*Resources, error) {
r, err := loadKubernetesYAMLToResources(yamlBytes, addMissingResources)
func LoadResourcesFromYAMLBytes(yamlBytes []byte, addMissingResources bool, envoyGateway *egv1a1.EnvoyGateway) (*Resources, error) {
r, err := loadKubernetesYAMLToResources(yamlBytes, addMissingResources, envoyGateway)
if err != nil {
return nil, err
}
Expand All @@ -53,7 +53,7 @@ func LoadResourcesFromYAMLBytes(yamlBytes []byte, addMissingResources bool) (*Re
}

// loadKubernetesYAMLToResources converts a Kubernetes YAML string into GatewayAPI Resources.
func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Resources, error) {
func loadKubernetesYAMLToResources(input []byte, addMissingResources bool, envoyGateway *egv1a1.EnvoyGateway) (*Resources, error) {
resources := NewResources()
var useDefaultNamespace bool
providedNamespaceMap := sets.New[string]()
Expand All @@ -62,6 +62,30 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
defaulter := GetGatewaySchemaDefaulter()
validator := GetDefaultValidator()

// Build a map of extension-managed resources (by Group/Version/Kind)
type extCategory int
const (
extNone extCategory = iota
extFilter
extPolicy
extBackend
)
extGVKMap := map[string]extCategory{}
if envoyGateway != nil && envoyGateway.ExtensionManager != nil {
for _, gvk := range envoyGateway.ExtensionManager.Resources {
key := fmt.Sprintf("%s/%s/%s", gvk.Group, gvk.Version, gvk.Kind)
extGVKMap[key] = extFilter
}
for _, gvk := range envoyGateway.ExtensionManager.PolicyResources {
key := fmt.Sprintf("%s/%s/%s", gvk.Group, gvk.Version, gvk.Kind)
extGVKMap[key] = extPolicy
}
for _, gvk := range envoyGateway.ExtensionManager.BackendResources {
key := fmt.Sprintf("%s/%s/%s", gvk.Group, gvk.Version, gvk.Kind)
extGVKMap[key] = extBackend
}
}

if err := IterYAMLBytes(input, func(yamlByte []byte) error {
var obj map[string]interface{}
err := yaml.Unmarshal(yamlByte, &obj)
Expand Down Expand Up @@ -117,6 +141,21 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
data := kobjVal.FieldByName("Data")
stringData := kobjVal.FieldByName("StringData")

// Check if this resource is managed by the ExtensionManager and if so, classify it
if len(extGVKMap) > 0 {
key := fmt.Sprintf("%s/%s/%s", gvk.Group, gvk.Version, gvk.Kind)
if category, ok := extGVKMap[key]; ok {
un.SetNamespace(namespace)
switch category {
case extFilter, extBackend:
resources.ExtensionRefFilters = append(resources.ExtensionRefFilters, *un)
Copy link
Contributor

Choose a reason for hiding this comment

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

is this logic right of adding the ext backend into ext ref flters ?
cc @Xunzhuo

Copy link
Author

Choose a reason for hiding this comment

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

i think so, the controller does it the same way
#7331 (comment)

case extPolicy:
resources.ExtensionServerPolicies = append(resources.ExtensionServerPolicies, *un)
}
return nil
}
}

switch gvk.Kind {
case KindEnvoyProxy:
typedSpec := spec.Interface()
Expand Down
2 changes: 1 addition & 1 deletion internal/gatewayapi/resource/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func TestLoadAllSupportedResourcesFromYAMLBytes(t *testing.T) {
t.Parallel() // this's used for race detection
data, err := os.ReadFile(inFile)
require.NoError(t, err)
got, err := LoadResourcesFromYAMLBytes(data, true)
got, err := LoadResourcesFromYAMLBytes(data, true, nil)
require.NoError(t, err)

outputFile := strings.Replace(inFile, ".in.yaml", ".out.yaml", 1)
Expand Down
Loading