Skip to content

Commit a6c06ed

Browse files
authored
add new test upstream cluster support (#270)
See #267 Signed-off-by: wbpcode <wbphub@gmail.com>
1 parent 7c71897 commit a6c06ed

11 files changed

Lines changed: 354 additions & 83 deletions

File tree

cli/cmd/genconfig.go

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ type GenConfig struct {
3434
// sep:"none" disables Kong's default comma-separated splitting for []string flags.
3535
// JSON config values contain commas (e.g. {"a":"1","b":"2"}) which would otherwise
3636
// be split into separate invalid fragments, causing protobuf unmarshal failures.
37-
Configs []string `name:"config" sep:"none" help:"Optional JSON config string for extensions. Applied in order to combined --extension and --local flags."`
38-
Clusters ClusterFlags `embed:""`
39-
OCI OCIFlags `embed:""`
40-
TestUpstreamHost string `name:"test-upstream-host" help:"Hostname for the test upstream cluster." default:"httpbin.org"`
41-
Output string `name:"output" help:"Directory to put the generated config into. Use \"-\" to print it to the standard output." default:"-" type:"path"`
37+
Configs []string `name:"config" sep:"none" help:"Optional JSON config string for extensions. Applied in order to combined --extension and --local flags."`
38+
Clusters ClusterFlags `embed:""`
39+
OCI OCIFlags `embed:""`
40+
TestUpstreamHost string `name:"test-upstream-host" help:"Hostname for the test upstream cluster. Mutually exclusive with --test-upstream-cluster. Defaults to \"httpbin.org\"."`
41+
TestUpstreamCluster string `name:"test-upstream-cluster" help:"Name of an existing configured cluster to use as the test upstream. The cluster must be configured via --cluster, --cluster-insecure, or --cluster-json. Mutually exclusive with --test-upstream-host."`
42+
Output string `name:"output" help:"Directory to put the generated config into. Use \"-\" to print it to the standard output." default:"-" type:"path"`
4243

4344
extensionPositions extensionPositions `kong:"-"` // Internal field: tracks the original position of extensions specified via both --extension and --local flags
4445
stdout io.Writer `kong:"-"` // Internal field for testing
@@ -58,6 +59,14 @@ func (g *GenConfig) BeforeResolve() error {
5859
return err
5960
}
6061

62+
// Validate is called by Kong after parsing to validate the command arguments.
63+
func (g *GenConfig) Validate() error {
64+
if g.TestUpstreamHost != "" && g.TestUpstreamCluster != "" {
65+
return fmt.Errorf("--test-upstream-host and --test-upstream-cluster are mutually exclusive")
66+
}
67+
return nil
68+
}
69+
6170
// Run executes the GenConfig command.
6271
func (g *GenConfig) Run(ctx context.Context, dirs *xdg.Directories, logger *slog.Logger) error {
6372
logger.Debug("handling genconfig command", "cmd", internal.RedactSensitive(g))
@@ -124,16 +133,17 @@ func (g *GenConfig) Run(ctx context.Context, dirs *xdg.Directories, logger *slog
124133
}
125134

126135
config, err := envoy.RenderConfig(&envoy.ConfigGenerationParams{
127-
Logger: logger,
128-
AdminPort: g.AdminPort,
129-
ListenerPort: g.ListenPort,
130-
Dirs: dirs,
131-
Extensions: resolvedExtensions,
132-
Configs: g.Configs,
133-
Clusters: g.Clusters.Secure,
134-
ClustersInsecure: g.Clusters.Insecure,
135-
ClustersJSON: g.Clusters.JSONSpec,
136-
TestUpstreamHost: g.TestUpstreamHost,
136+
Logger: logger,
137+
AdminPort: g.AdminPort,
138+
ListenerPort: g.ListenPort,
139+
Dirs: dirs,
140+
Extensions: resolvedExtensions,
141+
Configs: g.Configs,
142+
Clusters: g.Clusters.Secure,
143+
ClustersInsecure: g.Clusters.Insecure,
144+
ClustersJSON: g.Clusters.JSONSpec,
145+
TestUpstreamHost: g.TestUpstreamHost,
146+
TestUpstreamCluster: g.TestUpstreamCluster,
137147
}, renderer)
138148
if err != nil {
139149
return err

cli/cmd/genconfig_test.go

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,17 @@ Flags:
7979
($BOE_REGISTRY_USERNAME).
8080
--password=STRING Password for the OCI registry
8181
($BOE_REGISTRY_PASSWORD).
82-
--test-upstream-host="httpbin.org"
83-
Hostname for the test upstream cluster.
82+
--test-upstream-host=STRING
83+
Hostname for the test upstream
84+
cluster. Mutually exclusive with
85+
--test-upstream-cluster. Defaults to
86+
"httpbin.org".
87+
--test-upstream-cluster=STRING
88+
Name of an existing configured cluster to
89+
use as the test upstream. The cluster must be
90+
configured via --cluster, --cluster-insecure,
91+
or --cluster-json. Mutually exclusive with
92+
--test-upstream-host.
8493
--output="-" Directory to put the generated config into.
8594
Use "-" to print it to the standard output.
8695
`, internaltesting.WrapHelp(genConfigHelp))
@@ -94,13 +103,14 @@ func TestGenConfig(t *testing.T) {
94103
clusterInsecureShort := `example.com:80`
95104

96105
tests := []struct {
97-
name string
98-
minimal bool
99-
local []string
100-
clusters []string
101-
clustersInsecure []string
102-
clustersJSON []string
103-
wantFile string
106+
name string
107+
minimal bool
108+
local []string
109+
clusters []string
110+
clustersInsecure []string
111+
clustersJSON []string
112+
testUpstreamCluster string
113+
wantFile string
104114
}{
105115
{
106116
name: "only filters",
@@ -172,6 +182,14 @@ func TestGenConfig(t *testing.T) {
172182
clustersInsecure: []string{clusterInsecureShort},
173183
wantFile: "testdata/output_only_filters_with_shorthand_cluster_and_insecure_cluster.yaml",
174184
},
185+
{
186+
name: "full config with test upstream cluster",
187+
minimal: false,
188+
local: []string{"testdata/input_lua_inline"},
189+
clusters: []string{clusterShort},
190+
testUpstreamCluster: clusterShort,
191+
wantFile: "testdata/output_full_config_with_test_upstream_cluster.yaml",
192+
},
175193
}
176194

177195
for _, tt := range tests {
@@ -187,8 +205,9 @@ func TestGenConfig(t *testing.T) {
187205
Insecure: tt.clustersInsecure,
188206
JSONSpec: tt.clustersJSON,
189207
},
190-
Output: "-",
191-
stdout: &buf,
208+
TestUpstreamCluster: tt.testUpstreamCluster,
209+
Output: "-",
210+
stdout: &buf,
192211
}
193212

194213
var args []string
@@ -211,6 +230,14 @@ func TestGenConfig(t *testing.T) {
211230
}
212231
}
213232

233+
func TestGenConfigValidateMutualExclusion(t *testing.T) {
234+
cmd := &GenConfig{
235+
TestUpstreamHost: "example.com",
236+
TestUpstreamCluster: "example.com:443",
237+
}
238+
require.ErrorContains(t, cmd.Validate(), "--test-upstream-host and --test-upstream-cluster are mutually exclusive")
239+
}
240+
214241
func TestGenConfigMultipleArgsWithCommas(t *testing.T) {
215242
config1 := `{"header":"value1","header2":"value2"}`
216243
config2 := `{"another_config":"value3","yet_another_config":"value4"}`

cli/cmd/run.go

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,13 @@ type Run struct {
4141
// sep:"none" disables Kong's default comma-separated splitting for []string flags.
4242
// JSON config values contain commas (e.g. {"a":"1","b":"2"}) which would otherwise
4343
// be split into separate invalid fragments, causing protobuf unmarshal failures.
44-
Configs []string `name:"config" sep:"none" help:"Optional JSON config string for extensions. Applied in order to combined --extension and --local flags."`
45-
Clusters ClusterFlags `embed:""`
46-
TestUpstreamHost string `name:"test-upstream-host" help:"Hostname for the test upstream cluster." default:"httpbin.org"`
47-
Docker bool `help:"Run Envoy as a Docker container instead of using func-e." default:"false" env:"BOE_RUN_DOCKER"`
48-
Pull string `name:"pull" help:"Pull policy for the BOE Docker image (missing, always, never). Only applicable when running with --docker." enum:"missing,always,never" default:"missing"`
49-
OCI OCIFlags `embed:""`
44+
Configs []string `name:"config" sep:"none" help:"Optional JSON config string for extensions. Applied in order to combined --extension and --local flags."`
45+
Clusters ClusterFlags `embed:""`
46+
TestUpstreamHost string `name:"test-upstream-host" help:"Hostname for the test upstream cluster. Mutually exclusive with --test-upstream-cluster. Defaults to \"httpbin.org\"."`
47+
TestUpstreamCluster string `name:"test-upstream-cluster" help:"Name of an existing configured cluster to use as the test upstream. The cluster must be configured via --cluster, --cluster-insecure, or --cluster-json. Mutually exclusive with --test-upstream-host."`
48+
Docker bool `help:"Run Envoy as a Docker container instead of using func-e." default:"false" env:"BOE_RUN_DOCKER"`
49+
Pull string `name:"pull" help:"Pull policy for the BOE Docker image (missing, always, never). Only applicable when running with --docker." enum:"missing,always,never" default:"missing"`
50+
OCI OCIFlags `embed:""`
5051

5152
extensionPositions extensionPositions `kong:"-"` // Internal field: tracks the original position of extensions specified via both --extension and --local flags
5253
defaultLogLevel string `kong:"-"` // Internal field: parsed defaut log level
@@ -98,6 +99,9 @@ func (r *Run) Validate() error {
9899
if err != nil {
99100
return err
100101
}
102+
if r.TestUpstreamHost != "" && r.TestUpstreamCluster != "" {
103+
return fmt.Errorf("--test-upstream-host and --test-upstream-cluster are mutually exclusive")
104+
}
101105
return nil
102106
}
103107

@@ -166,20 +170,21 @@ func (r *Run) Run(ctx context.Context, dirs *xdg.Directories, logger *slog.Logge
166170
}
167171

168172
runner := &envoy.RunnerFuncE{
169-
Logger: logger,
170-
EnvoyVersion: r.EnvoyVersion,
171-
DefaultLogLevel: r.defaultLogLevel,
172-
ComponentLogLevel: r.componentLogLevel,
173-
Dirs: dirs,
174-
RunID: r.RunID,
175-
ListenPort: r.ListenPort,
176-
AdminPort: r.AdminPort,
177-
Extensions: extensionsToRun,
178-
Configs: r.Configs,
179-
Clusters: r.Clusters.Secure,
180-
ClustersInsecure: r.Clusters.Insecure,
181-
ClustersJSON: r.Clusters.JSONSpec,
182-
TestUpstreamHost: r.TestUpstreamHost,
173+
Logger: logger,
174+
EnvoyVersion: r.EnvoyVersion,
175+
DefaultLogLevel: r.defaultLogLevel,
176+
ComponentLogLevel: r.componentLogLevel,
177+
Dirs: dirs,
178+
RunID: r.RunID,
179+
ListenPort: r.ListenPort,
180+
AdminPort: r.AdminPort,
181+
Extensions: extensionsToRun,
182+
Configs: r.Configs,
183+
Clusters: r.Clusters.Secure,
184+
ClustersInsecure: r.Clusters.Insecure,
185+
ClustersJSON: r.Clusters.JSONSpec,
186+
TestUpstreamHost: r.TestUpstreamHost,
187+
TestUpstreamCluster: r.TestUpstreamCluster,
183188
}
184189

185190
return runner.Run(ctx)

cli/cmd/run_test.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,17 @@ Flags:
7878
--cluster-json=CLUSTER-JSON
7979
Optional additional Envoy cluster providing
8080
the complete cluster config in JSON format.
81-
--test-upstream-host="httpbin.org"
82-
Hostname for the test upstream cluster.
81+
--test-upstream-host=STRING
82+
Hostname for the test upstream
83+
cluster. Mutually exclusive with
84+
--test-upstream-cluster. Defaults to
85+
"httpbin.org".
86+
--test-upstream-cluster=STRING
87+
Name of an existing configured cluster to
88+
use as the test upstream. The cluster must be
89+
configured via --cluster, --cluster-insecure,
90+
or --cluster-json. Mutually exclusive with
91+
--test-upstream-host.
8392
--docker Run Envoy as a Docker container instead of
8493
using func-e ($BOE_RUN_DOCKER).
8594
--pull="missing" Pull policy for the BOE Docker image
@@ -169,6 +178,15 @@ func TestParseCmdRunCustomValues(t *testing.T) {
169178
require.True(t, cli.Run.OCI.Insecure)
170179
}
171180

181+
func TestRunValidateMutualExclusion(t *testing.T) {
182+
r := &Run{
183+
LogLevel: "all:error",
184+
TestUpstreamHost: "example.com",
185+
TestUpstreamCluster: "example.com:443",
186+
}
187+
require.ErrorContains(t, r.Validate(), "--test-upstream-host and --test-upstream-cluster are mutually exclusive")
188+
}
189+
172190
func TestValidateLogLevel(t *testing.T) {
173191
tests := []struct {
174192
name string
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Copyright Built On Envoy
2+
# SPDX-License-Identifier: Apache-2.0
3+
# The full text of the Apache license is available in the LICENSE file at
4+
# the root of the repo.
5+
6+
admin:
7+
address:
8+
socket_address:
9+
address: 127.0.0.1
10+
port_value: 9901
11+
static_resources:
12+
clusters:
13+
- dns_lookup_family: V4_ONLY
14+
load_assignment:
15+
cluster_name: example.com:443
16+
endpoints:
17+
- lb_endpoints:
18+
- endpoint:
19+
address:
20+
socket_address:
21+
address: example.com
22+
port_value: 443
23+
name: example.com:443
24+
transport_socket:
25+
name: envoy.transport_sockets.tls
26+
typed_config:
27+
'@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
28+
sni: example.com
29+
type: STRICT_DNS
30+
listeners:
31+
- address:
32+
socket_address:
33+
address: 0.0.0.0
34+
port_value: 10000
35+
filter_chains:
36+
- filters:
37+
- name: envoy.filters.network.http_connection_manager
38+
typed_config:
39+
'@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
40+
access_log:
41+
- name: envoy.access_loggers.stdout
42+
typed_config:
43+
'@type': type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
44+
http_filters:
45+
- name: test-lua
46+
typed_config:
47+
'@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
48+
default_source_code:
49+
inline_string: |
50+
function envoy_on_request(request_handle)
51+
request_handle:logInfo("Hello, World!")
52+
end
53+
- name: envoy.filters.http.router
54+
typed_config:
55+
'@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
56+
route_config:
57+
name: default_route
58+
virtual_hosts:
59+
- domains:
60+
- '*'
61+
name: default_service
62+
routes:
63+
- match:
64+
prefix: /
65+
route:
66+
cluster: example.com:443
67+
stat_prefix: ingress_http
68+
name: main

0 commit comments

Comments
 (0)