mirror of
https://github.com/fatedier/frp.git
synced 2025-05-24 16:48:58 +00:00
feat: support YAML merge in strict configuration mode (#4809)
This commit is contained in:
parent
3be6efdd28
commit
3128350dd6
@ -1,3 +1,3 @@
|
|||||||
### Bug Fixes
|
## Features
|
||||||
|
|
||||||
* **VirtualNet:** Resolved various issues related to connection handling, TUN device management, and stability in the virtual network feature.
|
* Support for YAML merge functionality (anchors and references with dot-prefixed fields) in strict configuration mode without requiring `--strict-config=false` parameter.
|
@ -111,6 +111,33 @@ func LoadConfigureFromFile(path string, c any, strict bool) error {
|
|||||||
return LoadConfigure(content, c, strict)
|
return LoadConfigure(content, c, strict)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseYAMLWithDotFieldsHandling parses YAML with dot-prefixed fields handling
|
||||||
|
// This function handles both cases efficiently: with or without dot fields
|
||||||
|
func parseYAMLWithDotFieldsHandling(content []byte, target any) error {
|
||||||
|
var temp any
|
||||||
|
if err := yaml.Unmarshal(content, &temp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove dot fields if it's a map
|
||||||
|
if tempMap, ok := temp.(map[string]any); ok {
|
||||||
|
for key := range tempMap {
|
||||||
|
if strings.HasPrefix(key, ".") {
|
||||||
|
delete(tempMap, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to JSON and decode with strict validation
|
||||||
|
jsonBytes, err := json.Marshal(temp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||||
|
decoder.DisallowUnknownFields()
|
||||||
|
return decoder.Decode(target)
|
||||||
|
}
|
||||||
|
|
||||||
// LoadConfigure loads configuration from bytes and unmarshal into c.
|
// LoadConfigure loads configuration from bytes and unmarshal into c.
|
||||||
// Now it supports json, yaml and toml format.
|
// Now it supports json, yaml and toml format.
|
||||||
func LoadConfigure(b []byte, c any, strict bool) error {
|
func LoadConfigure(b []byte, c any, strict bool) error {
|
||||||
@ -134,10 +161,13 @@ func LoadConfigure(b []byte, c any, strict bool) error {
|
|||||||
}
|
}
|
||||||
return decoder.Decode(c)
|
return decoder.Decode(c)
|
||||||
}
|
}
|
||||||
// It wasn't JSON. Unmarshal as YAML.
|
|
||||||
|
// Handle YAML content
|
||||||
if strict {
|
if strict {
|
||||||
return yaml.UnmarshalStrict(b, c)
|
// In strict mode, always use our custom handler to support YAML merge
|
||||||
|
return parseYAMLWithDotFieldsHandling(b, c)
|
||||||
}
|
}
|
||||||
|
// Non-strict mode, parse normally
|
||||||
return yaml.Unmarshal(b, c)
|
return yaml.Unmarshal(b, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,3 +187,122 @@ unixPath = "/tmp/uds.sock"
|
|||||||
err = LoadConfigure([]byte(pluginStr), &clientCfg, true)
|
err = LoadConfigure([]byte(pluginStr), &clientCfg, true)
|
||||||
require.Error(err)
|
require.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestYAMLMergeInStrictMode tests that YAML merge functionality works
|
||||||
|
// even in strict mode by properly handling dot-prefixed fields
|
||||||
|
func TestYAMLMergeInStrictMode(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
yamlContent := `
|
||||||
|
serverAddr: "127.0.0.1"
|
||||||
|
serverPort: 7000
|
||||||
|
|
||||||
|
.common: &common
|
||||||
|
type: stcp
|
||||||
|
secretKey: "test-secret"
|
||||||
|
localIP: 127.0.0.1
|
||||||
|
transport:
|
||||||
|
useEncryption: true
|
||||||
|
useCompression: true
|
||||||
|
|
||||||
|
proxies:
|
||||||
|
- name: ssh
|
||||||
|
localPort: 22
|
||||||
|
<<: *common
|
||||||
|
- name: web
|
||||||
|
localPort: 80
|
||||||
|
<<: *common
|
||||||
|
`
|
||||||
|
|
||||||
|
clientCfg := v1.ClientConfig{}
|
||||||
|
// This should work in strict mode
|
||||||
|
err := LoadConfigure([]byte(yamlContent), &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// Verify the merge worked correctly
|
||||||
|
require.Equal("127.0.0.1", clientCfg.ServerAddr)
|
||||||
|
require.Equal(7000, clientCfg.ServerPort)
|
||||||
|
require.Len(clientCfg.Proxies, 2)
|
||||||
|
|
||||||
|
// Check first proxy
|
||||||
|
sshProxy := clientCfg.Proxies[0].ProxyConfigurer
|
||||||
|
require.Equal("ssh", sshProxy.GetBaseConfig().Name)
|
||||||
|
require.Equal("stcp", sshProxy.GetBaseConfig().Type)
|
||||||
|
|
||||||
|
// Check second proxy
|
||||||
|
webProxy := clientCfg.Proxies[1].ProxyConfigurer
|
||||||
|
require.Equal("web", webProxy.GetBaseConfig().Name)
|
||||||
|
require.Equal("stcp", webProxy.GetBaseConfig().Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOptimizedYAMLProcessing tests the optimization logic for YAML processing
|
||||||
|
func TestOptimizedYAMLProcessing(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
yamlWithDotFields := []byte(`
|
||||||
|
serverAddr: "127.0.0.1"
|
||||||
|
.common: &common
|
||||||
|
type: stcp
|
||||||
|
proxies:
|
||||||
|
- name: test
|
||||||
|
<<: *common
|
||||||
|
`)
|
||||||
|
|
||||||
|
yamlWithoutDotFields := []byte(`
|
||||||
|
serverAddr: "127.0.0.1"
|
||||||
|
proxies:
|
||||||
|
- name: test
|
||||||
|
type: tcp
|
||||||
|
localPort: 22
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Test that YAML without dot fields works in strict mode
|
||||||
|
clientCfg := v1.ClientConfig{}
|
||||||
|
err := LoadConfigure(yamlWithoutDotFields, &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("127.0.0.1", clientCfg.ServerAddr)
|
||||||
|
require.Len(clientCfg.Proxies, 1)
|
||||||
|
require.Equal("test", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Name)
|
||||||
|
|
||||||
|
// Test that YAML with dot fields still works in strict mode
|
||||||
|
err = LoadConfigure(yamlWithDotFields, &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("127.0.0.1", clientCfg.ServerAddr)
|
||||||
|
require.Len(clientCfg.Proxies, 1)
|
||||||
|
require.Equal("test", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Name)
|
||||||
|
require.Equal("stcp", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestYAMLEdgeCases tests edge cases for YAML parsing, including non-map types
|
||||||
|
func TestYAMLEdgeCases(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
// Test array at root (should fail for frp config)
|
||||||
|
arrayYAML := []byte(`
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
`)
|
||||||
|
clientCfg := v1.ClientConfig{}
|
||||||
|
err := LoadConfigure(arrayYAML, &clientCfg, true)
|
||||||
|
require.Error(err) // Should fail because ClientConfig expects an object
|
||||||
|
|
||||||
|
// Test scalar at root (should fail for frp config)
|
||||||
|
scalarYAML := []byte(`"just a string"`)
|
||||||
|
err = LoadConfigure(scalarYAML, &clientCfg, true)
|
||||||
|
require.Error(err) // Should fail because ClientConfig expects an object
|
||||||
|
|
||||||
|
// Test empty object (should work)
|
||||||
|
emptyYAML := []byte(`{}`)
|
||||||
|
err = LoadConfigure(emptyYAML, &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// Test nested structure without dots (should work)
|
||||||
|
nestedYAML := []byte(`
|
||||||
|
serverAddr: "127.0.0.1"
|
||||||
|
serverPort: 7000
|
||||||
|
`)
|
||||||
|
err = LoadConfigure(nestedYAML, &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("127.0.0.1", clientCfg.ServerAddr)
|
||||||
|
require.Equal(7000, clientCfg.ServerPort)
|
||||||
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
package version
|
package version
|
||||||
|
|
||||||
var version = "0.62.1"
|
var version = "0.63.0"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
Loading…
x
Reference in New Issue
Block a user