mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 13:00:52 +08:00
The `ReconcileProviders` function was incorrectly including the default inline provider (`access.teleport.dev`) in the lists of added, updated, and removed providers. The inline provider is a special case managed directly by the access controller and does not correspond to a separate, reloadable resource. Including it in the change lists could lead to errors when attempting to perform lifecycle operations on it. This commit modifies the reconciliation logic to explicitly ignore the inline provider when calculating changes. This ensures that only external, reloadable providers are reported as changed, preventing incorrect lifecycle management.
253 lines
6.2 KiB
Go
253 lines
6.2 KiB
Go
package access
|
|
|
|
import (
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
|
)
|
|
|
|
// ReconcileProviders builds the desired provider list by reusing existing providers when possible
|
|
// and creating or removing providers only when their configuration changed. It returns the final
|
|
// ordered provider slice along with the identifiers of providers that were added, updated, or
|
|
// removed compared to the previous configuration.
|
|
func ReconcileProviders(oldCfg, newCfg *config.Config, existing []Provider) (result []Provider, added, updated, removed []string, err error) {
|
|
if newCfg == nil {
|
|
return nil, nil, nil, nil, nil
|
|
}
|
|
|
|
existingMap := make(map[string]Provider, len(existing))
|
|
for _, provider := range existing {
|
|
if provider == nil {
|
|
continue
|
|
}
|
|
existingMap[provider.Identifier()] = provider
|
|
}
|
|
|
|
oldCfgMap := accessProviderMap(oldCfg)
|
|
newEntries := collectProviderEntries(newCfg)
|
|
|
|
result = make([]Provider, 0, len(newEntries))
|
|
finalIDs := make(map[string]struct{}, len(newEntries))
|
|
|
|
isInlineProvider := func(id string) bool {
|
|
return strings.EqualFold(id, config.DefaultAccessProviderName)
|
|
}
|
|
appendChange := func(list *[]string, id string) {
|
|
if isInlineProvider(id) {
|
|
return
|
|
}
|
|
*list = append(*list, id)
|
|
}
|
|
|
|
for _, providerCfg := range newEntries {
|
|
key := providerIdentifier(providerCfg)
|
|
if key == "" {
|
|
continue
|
|
}
|
|
|
|
if oldCfgProvider, ok := oldCfgMap[key]; ok {
|
|
isAliased := oldCfgProvider == providerCfg
|
|
if !isAliased && providerConfigEqual(oldCfgProvider, providerCfg) {
|
|
if existingProvider, okExisting := existingMap[key]; okExisting {
|
|
result = append(result, existingProvider)
|
|
finalIDs[key] = struct{}{}
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
provider, buildErr := buildProvider(providerCfg, newCfg)
|
|
if buildErr != nil {
|
|
return nil, nil, nil, nil, buildErr
|
|
}
|
|
if _, ok := oldCfgMap[key]; ok {
|
|
if _, existed := existingMap[key]; existed {
|
|
appendChange(&updated, key)
|
|
} else {
|
|
appendChange(&added, key)
|
|
}
|
|
} else {
|
|
appendChange(&added, key)
|
|
}
|
|
result = append(result, provider)
|
|
finalIDs[key] = struct{}{}
|
|
}
|
|
|
|
if len(result) == 0 && len(newCfg.APIKeys) > 0 {
|
|
config.SyncInlineAPIKeys(newCfg, newCfg.APIKeys)
|
|
if providerCfg := newCfg.ConfigAPIKeyProvider(); providerCfg != nil {
|
|
key := providerIdentifier(providerCfg)
|
|
if key != "" {
|
|
if oldCfgProvider, ok := oldCfgMap[key]; ok {
|
|
isAliased := oldCfgProvider == providerCfg
|
|
if !isAliased && providerConfigEqual(oldCfgProvider, providerCfg) {
|
|
if existingProvider, okExisting := existingMap[key]; okExisting {
|
|
result = append(result, existingProvider)
|
|
} else {
|
|
provider, buildErr := buildProvider(providerCfg, newCfg)
|
|
if buildErr != nil {
|
|
return nil, nil, nil, nil, buildErr
|
|
}
|
|
if _, existed := existingMap[key]; existed {
|
|
appendChange(&updated, key)
|
|
} else {
|
|
appendChange(&added, key)
|
|
}
|
|
result = append(result, provider)
|
|
}
|
|
} else {
|
|
provider, buildErr := buildProvider(providerCfg, newCfg)
|
|
if buildErr != nil {
|
|
return nil, nil, nil, nil, buildErr
|
|
}
|
|
if _, existed := existingMap[key]; existed {
|
|
appendChange(&updated, key)
|
|
} else {
|
|
appendChange(&added, key)
|
|
}
|
|
result = append(result, provider)
|
|
}
|
|
} else {
|
|
provider, buildErr := buildProvider(providerCfg, newCfg)
|
|
if buildErr != nil {
|
|
return nil, nil, nil, nil, buildErr
|
|
}
|
|
appendChange(&added, key)
|
|
result = append(result, provider)
|
|
}
|
|
finalIDs[key] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
removedSet := make(map[string]struct{})
|
|
for id := range existingMap {
|
|
if _, ok := finalIDs[id]; !ok {
|
|
if isInlineProvider(id) {
|
|
continue
|
|
}
|
|
removedSet[id] = struct{}{}
|
|
}
|
|
}
|
|
|
|
removed = make([]string, 0, len(removedSet))
|
|
for id := range removedSet {
|
|
removed = append(removed, id)
|
|
}
|
|
|
|
sort.Strings(added)
|
|
sort.Strings(updated)
|
|
sort.Strings(removed)
|
|
|
|
return result, added, updated, removed, nil
|
|
}
|
|
|
|
func accessProviderMap(cfg *config.Config) map[string]*config.AccessProvider {
|
|
result := make(map[string]*config.AccessProvider)
|
|
if cfg == nil {
|
|
return result
|
|
}
|
|
for i := range cfg.Access.Providers {
|
|
providerCfg := &cfg.Access.Providers[i]
|
|
if providerCfg.Type == "" {
|
|
continue
|
|
}
|
|
key := providerIdentifier(providerCfg)
|
|
if key == "" {
|
|
continue
|
|
}
|
|
result[key] = providerCfg
|
|
}
|
|
if len(result) == 0 && len(cfg.APIKeys) > 0 {
|
|
if provider := cfg.ConfigAPIKeyProvider(); provider != nil {
|
|
if key := providerIdentifier(provider); key != "" {
|
|
result[key] = provider
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func collectProviderEntries(cfg *config.Config) []*config.AccessProvider {
|
|
entries := make([]*config.AccessProvider, 0, len(cfg.Access.Providers))
|
|
if cfg == nil {
|
|
return entries
|
|
}
|
|
for i := range cfg.Access.Providers {
|
|
providerCfg := &cfg.Access.Providers[i]
|
|
if providerCfg.Type == "" {
|
|
continue
|
|
}
|
|
if key := providerIdentifier(providerCfg); key != "" {
|
|
entries = append(entries, providerCfg)
|
|
}
|
|
}
|
|
return entries
|
|
}
|
|
|
|
func providerIdentifier(provider *config.AccessProvider) string {
|
|
if provider == nil {
|
|
return ""
|
|
}
|
|
if name := strings.TrimSpace(provider.Name); name != "" {
|
|
return name
|
|
}
|
|
typ := strings.TrimSpace(provider.Type)
|
|
if typ == "" {
|
|
return ""
|
|
}
|
|
if strings.EqualFold(typ, config.AccessProviderTypeConfigAPIKey) {
|
|
return config.DefaultAccessProviderName
|
|
}
|
|
return typ
|
|
}
|
|
|
|
func providerConfigEqual(a, b *config.AccessProvider) bool {
|
|
if a == nil || b == nil {
|
|
return a == nil && b == nil
|
|
}
|
|
if !strings.EqualFold(strings.TrimSpace(a.Type), strings.TrimSpace(b.Type)) {
|
|
return false
|
|
}
|
|
if strings.TrimSpace(a.SDK) != strings.TrimSpace(b.SDK) {
|
|
return false
|
|
}
|
|
if !stringSetEqual(a.APIKeys, b.APIKeys) {
|
|
return false
|
|
}
|
|
if len(a.Config) != len(b.Config) {
|
|
return false
|
|
}
|
|
if len(a.Config) > 0 && !reflect.DeepEqual(a.Config, b.Config) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func stringSetEqual(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
if len(a) == 0 {
|
|
return true
|
|
}
|
|
seen := make(map[string]int, len(a))
|
|
for _, val := range a {
|
|
seen[val]++
|
|
}
|
|
for _, val := range b {
|
|
count := seen[val]
|
|
if count == 0 {
|
|
return false
|
|
}
|
|
if count == 1 {
|
|
delete(seen, val)
|
|
} else {
|
|
seen[val] = count - 1
|
|
}
|
|
}
|
|
return len(seen) == 0
|
|
}
|