* feat(watchsync): add plugin-backed providers * fix(watchsync): address plugin review findings * fix(watchsync): harden plugin provider failures * feat(watchsync): complete plugin provider contract * fix(watchsync): address provider review feedback * fix(watchsync): keep device state host-private * fix(watchsync): build reconciliation index concurrently * fix(watchsync): preserve empty device state updates * chore(deps): use released watch-sync SDK --------- Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com>
102 lines
2.6 KiB
Go
102 lines
2.6 KiB
Go
package plugins
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1"
|
|
)
|
|
|
|
// WatchSyncProviderConfig returns the installation's global configuration in
|
|
// the transient, field-classified shape used by watch-sync RPCs. Undeclared
|
|
// fields are treated as secret so manifest drift cannot expose credentials.
|
|
func (s *Service) WatchSyncProviderConfig(
|
|
ctx context.Context,
|
|
installationID int,
|
|
) (*pluginv1.WatchSyncProviderConfig, error) {
|
|
manifest, err := s.manifestForInstallation(ctx, installationID, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if s.configs == nil {
|
|
return &pluginv1.WatchSyncProviderConfig{}, nil
|
|
}
|
|
configs, err := s.configs.ListGlobalConfigs(ctx, installationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list watch sync plugin config: %w", err)
|
|
}
|
|
return watchSyncProviderConfig(manifest, configs)
|
|
}
|
|
|
|
func watchSyncProviderConfig(
|
|
manifest *pluginv1.PluginManifest,
|
|
configs []*RuntimeConfig,
|
|
) (*pluginv1.WatchSyncProviderConfig, error) {
|
|
result := &pluginv1.WatchSyncProviderConfig{
|
|
Values: make(map[string]string),
|
|
SecretValues: make(map[string]string),
|
|
}
|
|
sort.Slice(configs, func(i, j int) bool {
|
|
if configs[i] == nil {
|
|
return false
|
|
}
|
|
if configs[j] == nil {
|
|
return true
|
|
}
|
|
return configs[i].Key < configs[j].Key
|
|
})
|
|
for _, config := range configs {
|
|
if config == nil {
|
|
continue
|
|
}
|
|
configKey := strings.TrimSpace(config.Key)
|
|
if configKey == "" {
|
|
continue
|
|
}
|
|
publicFields, _ := GlobalConfigFieldSets(manifest, configKey)
|
|
public := stringSet(publicFields)
|
|
for field, raw := range config.Value {
|
|
field = strings.TrimSpace(field)
|
|
if field == "" {
|
|
continue
|
|
}
|
|
value, err := watchSyncConfigString(raw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encode watch sync plugin config %q.%s: %w", configKey, field, err)
|
|
}
|
|
key := configKey + "." + field
|
|
if _, isPublic := public[field]; isPublic {
|
|
result.Values[key] = value
|
|
continue
|
|
}
|
|
// Explicitly secret and undeclared fields both take the protected path.
|
|
result.SecretValues[key] = value
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func stringSet(values []string) map[string]struct{} {
|
|
result := make(map[string]struct{}, len(values))
|
|
for _, value := range values {
|
|
if value = strings.TrimSpace(value); value != "" {
|
|
result[value] = struct{}{}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func watchSyncConfigString(value any) (string, error) {
|
|
if text, ok := value.(string); ok {
|
|
return text, nil
|
|
}
|
|
encoded, err := json.Marshal(value)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(encoded), nil
|
|
}
|