221 lines
4.0 KiB
Go
221 lines
4.0 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/AlexxIT/go2rtc/pkg/yaml"
|
|
)
|
|
|
|
var secrets = make(map[string]*Secret)
|
|
var secretsMu sync.Mutex
|
|
|
|
var templateRegex = regexp.MustCompile(`\{\{\s*([^\}]+)\s*\}\}`)
|
|
|
|
type Secrets interface {
|
|
Get(key string) any
|
|
Set(key string, value any)
|
|
Parse(template string) string
|
|
Marshal(v any) ([]byte, error)
|
|
Unmarshal(v any) error
|
|
Save() error
|
|
}
|
|
|
|
type Secret struct {
|
|
Secrets
|
|
|
|
Name string
|
|
Values map[string]any
|
|
}
|
|
|
|
func NewSecret(name string, values interface{}) *Secret {
|
|
secretsMu.Lock()
|
|
defer secretsMu.Unlock()
|
|
|
|
if s, exists := secrets[name]; exists {
|
|
return s
|
|
}
|
|
|
|
s := &Secret{Name: name, Values: make(map[string]any)}
|
|
|
|
switch v := values.(type) {
|
|
case map[string]any:
|
|
s.Values = v
|
|
default:
|
|
data, err := yaml.Encode(values, 2)
|
|
if err == nil {
|
|
var mapValues map[string]any
|
|
if err := yaml.Unmarshal(data, &mapValues); err == nil {
|
|
s.Values = mapValues
|
|
}
|
|
}
|
|
}
|
|
|
|
secrets[name] = s
|
|
return s
|
|
}
|
|
|
|
func (s *Secret) Get(key string) any {
|
|
secretsMu.Lock()
|
|
defer secretsMu.Unlock()
|
|
|
|
return s.Values[key]
|
|
}
|
|
|
|
func (s *Secret) Set(key string, value any) {
|
|
secretsMu.Lock()
|
|
defer secretsMu.Unlock()
|
|
|
|
if s.Values == nil {
|
|
s.Values = make(map[string]any)
|
|
}
|
|
|
|
s.Values[key] = value
|
|
secrets[s.Name] = s
|
|
}
|
|
|
|
func (s *Secret) Parse(template string) string {
|
|
if !templateRegex.MatchString(template) {
|
|
return template
|
|
}
|
|
|
|
secretsMu.Lock()
|
|
defer secretsMu.Unlock()
|
|
|
|
if _, exists := secrets[s.Name]; !exists {
|
|
return template
|
|
}
|
|
|
|
result := templateRegex.ReplaceAllStringFunc(template, func(match string) string {
|
|
varName := strings.TrimSpace(templateRegex.FindStringSubmatch(match)[1])
|
|
pathParts := strings.Split(varName, ".")
|
|
value := getNestedValue(s.Values, pathParts)
|
|
|
|
if value != nil {
|
|
return stringify(value)
|
|
}
|
|
|
|
return ""
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *Secret) Marshal(v any) ([]byte, error) {
|
|
secretsMu.Lock()
|
|
defer secretsMu.Unlock()
|
|
|
|
if s.Values == nil {
|
|
return nil, fmt.Errorf("no values in secret %s", s.Name)
|
|
}
|
|
|
|
data, err := yaml.Encode(s.Values, 2)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error encoding secret values: %w", err)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func (s *Secret) Unmarshal(v any) error {
|
|
secretsMu.Lock()
|
|
defer secretsMu.Unlock()
|
|
|
|
if s.Values == nil {
|
|
return fmt.Errorf("no values in secret %s", s.Name)
|
|
}
|
|
|
|
data, err := yaml.Encode(s.Values, 2)
|
|
if err != nil {
|
|
return fmt.Errorf("error encoding secret values: %w", err)
|
|
}
|
|
|
|
if err := yaml.Unmarshal(data, v); err != nil {
|
|
return fmt.Errorf("error unmarshaling secret values: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Secret) Save() error {
|
|
secretsMu.Lock()
|
|
defer secretsMu.Unlock()
|
|
return saveSecret(s.Name, s.Values)
|
|
}
|
|
|
|
func initSecrets() {
|
|
var cfg struct {
|
|
Secrets map[string]map[string]any `yaml:"secrets"`
|
|
}
|
|
|
|
/*
|
|
Example config:
|
|
secrets:
|
|
test_camera:
|
|
username: test
|
|
password: test
|
|
*/
|
|
|
|
LoadConfig(&cfg)
|
|
|
|
if cfg.Secrets == nil {
|
|
return
|
|
}
|
|
|
|
secretsMu.Lock()
|
|
defer secretsMu.Unlock()
|
|
|
|
for name, values := range cfg.Secrets {
|
|
secrets[name] = &Secret{Name: name, Values: values}
|
|
}
|
|
}
|
|
|
|
func saveSecret(name string, secret map[string]any) error {
|
|
return PatchConfig([]string{"secrets", name}, secret)
|
|
}
|
|
|
|
func getNestedValue(m map[string]any, path []string) interface{} {
|
|
if len(path) == 0 || m == nil {
|
|
return nil
|
|
}
|
|
|
|
key := path[0]
|
|
value, exists := m[key]
|
|
if !exists {
|
|
return nil
|
|
}
|
|
|
|
if len(path) == 1 {
|
|
return value
|
|
}
|
|
|
|
// Check nested maps
|
|
switch nextMap := value.(type) {
|
|
case map[string]any:
|
|
return getNestedValue(nextMap, path[1:])
|
|
case map[interface{}]interface{}:
|
|
stringMap := make(map[string]any)
|
|
for k, v := range nextMap {
|
|
if keyStr, ok := k.(string); ok {
|
|
stringMap[keyStr] = v
|
|
}
|
|
}
|
|
return getNestedValue(stringMap, path[1:])
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func stringify(value interface{}) string {
|
|
switch v := value.(type) {
|
|
case string:
|
|
return v
|
|
case int, int64, float64, bool:
|
|
return fmt.Sprintf("%v", v)
|
|
default:
|
|
return ""
|
|
}
|
|
}
|