feat: merge default proxy groups of all subscription
This commit is contained in:
@ -25,12 +25,22 @@ func PrefixProxyNames(subscriptionName string, data map[string]interface{}) []Pr
|
|||||||
|
|
||||||
refs := renameProxies(subscriptionName, data, rename)
|
refs := renameProxies(subscriptionName, data, rename)
|
||||||
renameProxyGroups(subscriptionName, data, rename)
|
renameProxyGroups(subscriptionName, data, rename)
|
||||||
rewriteGroupProxyReferences(data, rename)
|
RewriteReferences(data, rename)
|
||||||
rewriteRuleTargets(data, rename)
|
|
||||||
|
|
||||||
return refs
|
return refs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RewriteReferences rewrites every reference to a renamed proxy or
|
||||||
|
// proxy-group — inside other groups' "proxies" lists and inside
|
||||||
|
// data["rules"]' rule targets — to the new name. Exposed separately from
|
||||||
|
// PrefixProxyNames so callers that rename groups after the fact (e.g.
|
||||||
|
// collapsing several subscriptions' default selector group into one) can
|
||||||
|
// reuse the same rewrite logic without re-running the proxy/group renaming.
|
||||||
|
func RewriteReferences(data map[string]interface{}, rename map[string]string) {
|
||||||
|
rewriteGroupProxyReferences(data, rename)
|
||||||
|
rewriteRuleTargets(data, rename)
|
||||||
|
}
|
||||||
|
|
||||||
func renameProxies(subscriptionName string, data map[string]interface{}, rename map[string]string) []ProxyRef {
|
func renameProxies(subscriptionName string, data map[string]interface{}, rename map[string]string) []ProxyRef {
|
||||||
proxiesRaw, ok := data["proxies"].([]interface{})
|
proxiesRaw, ok := data["proxies"].([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@ -350,6 +350,7 @@ func (m *Manager) resolveSubscriptions() []*model.Subscription {
|
|||||||
func (m *Manager) mergeSubscriptions(subs []*model.Subscription) (map[string]interface{}, []automation.ProxyRef, bool) {
|
func (m *Manager) mergeSubscriptions(subs []*model.Subscription) (map[string]interface{}, []automation.ProxyRef, bool) {
|
||||||
combined := map[string]interface{}{}
|
combined := map[string]interface{}{}
|
||||||
var proxies []automation.ProxyRef
|
var proxies []automation.ProxyRef
|
||||||
|
var selectionGroups []map[string]interface{}
|
||||||
|
|
||||||
for _, sub := range subs {
|
for _, sub := range subs {
|
||||||
filePath := sub.GetFilePath(m.Storage.ConfigDir)
|
filePath := sub.GetFilePath(m.Storage.ConfigDir)
|
||||||
@ -374,10 +375,14 @@ func (m *Manager) mergeSubscriptions(subs []*model.Subscription) (map[string]int
|
|||||||
}
|
}
|
||||||
|
|
||||||
proxies = append(proxies, automation.PrefixProxyNames(sub.Name, data)...)
|
proxies = append(proxies, automation.PrefixProxyNames(sub.Name, data)...)
|
||||||
|
if group := popFirstProxyGroup(data); group != nil {
|
||||||
|
selectionGroups = append(selectionGroups, group)
|
||||||
|
}
|
||||||
combined = deepMerge(combined, data)
|
combined = deepMerge(combined, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
combined["rules"] = dedupeMatchRule(combined["rules"])
|
combined["rules"] = dedupeMatchRule(combined["rules"])
|
||||||
|
mergeProxySelection(combined, selectionGroups)
|
||||||
|
|
||||||
return combined, proxies, true
|
return combined, proxies, true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package corecfg
|
package corecfg
|
||||||
|
|
||||||
|
import "gitea.epss.net.cn/klesh/ss/internal/automation"
|
||||||
|
|
||||||
// deepMerge merges dict2 into dict1 in place and returns dict1, mirroring
|
// deepMerge merges dict2 into dict1 in place and returns dict1, mirroring
|
||||||
// corecfg_manager.py's module-level deep_merge(): nested maps recurse,
|
// corecfg_manager.py's module-level deep_merge(): nested maps recurse,
|
||||||
// matching lists are concatenated, everything else is overwritten by dict2.
|
// matching lists are concatenated, everything else is overwritten by dict2.
|
||||||
@ -25,3 +27,74 @@ func deepMerge(dict1, dict2 map[string]interface{}) map[string]interface{} {
|
|||||||
}
|
}
|
||||||
return dict1
|
return dict1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// popFirstProxyGroup removes and returns data["proxy-groups"]' first entry —
|
||||||
|
// by convention a subscription's default selector group, the one its own
|
||||||
|
// rules point at — so mergeProxySelection can fold every subscription's
|
||||||
|
// default selector into one combined group. Returns nil if there is none.
|
||||||
|
func popFirstProxyGroup(data map[string]interface{}) map[string]interface{} {
|
||||||
|
groupsRaw, ok := data["proxy-groups"].([]interface{})
|
||||||
|
if !ok || len(groupsRaw) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
group, ok := groupsRaw[0].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data["proxy-groups"] = groupsRaw[1:]
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectionGroupName is the name given to the proxy-group produced by
|
||||||
|
// folding every merged subscription's default selector group together.
|
||||||
|
const selectionGroupName = "Proxy Selection"
|
||||||
|
|
||||||
|
// mergeProxySelection collapses each subscription's default selector group
|
||||||
|
// (already popped off by popFirstProxyGroup) into a single combined group
|
||||||
|
// named selectionGroupName, covering every subscription's proxies, and
|
||||||
|
// rewrites every rule/group reference to the old per-subscription groups so
|
||||||
|
// they point at it instead.
|
||||||
|
func mergeProxySelection(combined map[string]interface{}, groups []map[string]interface{}) {
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rename := make(map[string]string, len(groups))
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
var groupType interface{}
|
||||||
|
var mergedProxies []interface{}
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
if name, ok := group["name"].(string); ok {
|
||||||
|
rename[name] = selectionGroupName
|
||||||
|
}
|
||||||
|
if groupType == nil {
|
||||||
|
groupType = group["type"]
|
||||||
|
}
|
||||||
|
if list, ok := group["proxies"].([]interface{}); ok {
|
||||||
|
for _, p := range list {
|
||||||
|
name, ok := p.(string)
|
||||||
|
if ok && seen[name] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
seen[name] = true
|
||||||
|
}
|
||||||
|
mergedProxies = append(mergedProxies, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
merged := map[string]interface{}{
|
||||||
|
"name": selectionGroupName,
|
||||||
|
"type": groupType,
|
||||||
|
"proxies": mergedProxies,
|
||||||
|
}
|
||||||
|
|
||||||
|
existing, _ := combined["proxy-groups"].([]interface{})
|
||||||
|
combined["proxy-groups"] = append([]interface{}{merged}, existing...)
|
||||||
|
|
||||||
|
automation.RewriteReferences(combined, rename)
|
||||||
|
}
|
||||||
|
|||||||
82
internal/corecfg/merge_test.go
Normal file
82
internal/corecfg/merge_test.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package corecfg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPopFirstProxyGroup(t *testing.T) {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"proxy-groups": []interface{}{
|
||||||
|
map[string]interface{}{"name": "Proxy", "type": "select", "proxies": []interface{}{"Auto"}},
|
||||||
|
map[string]interface{}{"name": "Auto", "type": "url-test", "proxies": []interface{}{"HK"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := popFirstProxyGroup(data)
|
||||||
|
|
||||||
|
want := map[string]interface{}{"name": "Proxy", "type": "select", "proxies": []interface{}{"Auto"}}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("got %#v, want %#v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining := data["proxy-groups"].([]interface{})
|
||||||
|
if len(remaining) != 1 || remaining[0].(map[string]interface{})["name"] != "Auto" {
|
||||||
|
t.Fatalf("expected only the Auto group to remain, got %#v", remaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPopFirstProxyGroupEmpty(t *testing.T) {
|
||||||
|
if got := popFirstProxyGroup(map[string]interface{}{}); got != nil {
|
||||||
|
t.Fatalf("expected nil, got %#v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeProxySelection(t *testing.T) {
|
||||||
|
combined := map[string]interface{}{
|
||||||
|
"proxy-groups": []interface{}{
|
||||||
|
map[string]interface{}{"name": "sub-a | Auto", "type": "url-test", "proxies": []interface{}{"sub-a | HK"}},
|
||||||
|
},
|
||||||
|
"rules": []interface{}{
|
||||||
|
"DOMAIN,example.com,sub-a | Proxy",
|
||||||
|
"DOMAIN,other.com,sub-b | Proxy",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
groups := []map[string]interface{}{
|
||||||
|
{"name": "sub-a | Proxy", "type": "select", "proxies": []interface{}{"sub-a | Auto", "DIRECT"}},
|
||||||
|
{"name": "sub-b | Proxy", "type": "select", "proxies": []interface{}{"sub-b | Auto", "DIRECT"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeProxySelection(combined, groups)
|
||||||
|
|
||||||
|
proxyGroups := combined["proxy-groups"].([]interface{})
|
||||||
|
if len(proxyGroups) != 2 {
|
||||||
|
t.Fatalf("expected 2 proxy-groups (merged selection + sub-a | Auto), got %#v", proxyGroups)
|
||||||
|
}
|
||||||
|
|
||||||
|
merged := proxyGroups[0].(map[string]interface{})
|
||||||
|
if merged["name"] != selectionGroupName {
|
||||||
|
t.Fatalf("expected first group to be %q, got %#v", selectionGroupName, merged["name"])
|
||||||
|
}
|
||||||
|
wantProxies := []interface{}{"sub-a | Auto", "DIRECT", "sub-b | Auto"}
|
||||||
|
if !reflect.DeepEqual(merged["proxies"], wantProxies) {
|
||||||
|
t.Fatalf("got proxies %#v, want %#v", merged["proxies"], wantProxies)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantRules := []interface{}{
|
||||||
|
"DOMAIN,example.com," + selectionGroupName,
|
||||||
|
"DOMAIN,other.com," + selectionGroupName,
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(combined["rules"], wantRules) {
|
||||||
|
t.Fatalf("got rules %#v, want %#v", combined["rules"], wantRules)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeProxySelectionNoGroups(t *testing.T) {
|
||||||
|
combined := map[string]interface{}{"proxy-groups": []interface{}{"unchanged"}}
|
||||||
|
mergeProxySelection(combined, nil)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(combined["proxy-groups"], []interface{}{"unchanged"}) {
|
||||||
|
t.Fatalf("expected combined to be untouched, got %#v", combined)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user