add yaml include feature

This commit is contained in:
2025-07-06 12:27:49 +00:00
parent 9e0469e016
commit 8198b90e60
7 changed files with 453 additions and 111 deletions

View File

@ -9,6 +9,8 @@ import (
"net"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/spf13/cobra"
@ -180,15 +182,15 @@ func (rb *RouterBackup) Disconnect() {
}
}
// loadConfig loads the YAML configuration file
// loadConfig loads the YAML configuration file with !include support
func loadConfig(configPath string) (*Config, error) {
data, err := ioutil.ReadFile(configPath)
processedYAML, err := processIncludes(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read config file %s: %v", configPath, err)
return nil, fmt.Errorf("failed to process includes: %v", err)
}
var config Config
err = yaml.Unmarshal(data, &config)
err = yaml.Unmarshal([]byte(processedYAML), &config)
if err != nil {
return nil, fmt.Errorf("failed to parse YAML: %v", err)
}
@ -196,6 +198,75 @@ func loadConfig(configPath string) (*Config, error) {
return &config, nil
}
// processIncludes processes YAML files with !include directives (one level deep)
func processIncludes(filePath string) (string, error) {
// Read the file
data, err := ioutil.ReadFile(filePath)
if err != nil {
return "", fmt.Errorf("failed to read file %s: %v", filePath, err)
}
content := string(data)
// Process !include directives
// Match patterns like: !include path/to/file.yaml (excluding commented lines)
includeRegex := regexp.MustCompile(`(?m)^(\s*)!include\s+(.+)$`)
baseDir := filepath.Dir(filePath)
// Process includes line by line to avoid conflicts
lines := strings.Split(content, "\n")
var resultLines []string
for _, line := range lines {
// Check if this line matches our include pattern
if match := includeRegex.FindStringSubmatch(line); match != nil {
leadingWhitespace := match[1]
includePath := strings.TrimSpace(match[2])
// Skip commented lines
if strings.Contains(strings.TrimSpace(line), "#") && strings.Index(strings.TrimSpace(line), "#") < strings.Index(strings.TrimSpace(line), "!include") {
resultLines = append(resultLines, line)
continue
}
// Remove quotes if present
includePath = strings.Trim(includePath, "\"'")
// Make path relative to current config file
if !filepath.IsAbs(includePath) {
includePath = filepath.Join(baseDir, includePath)
}
// Read the included file
includedData, err := ioutil.ReadFile(includePath)
if err != nil {
return "", fmt.Errorf("failed to read include file %s: %v", includePath, err)
}
// Use the captured leading whitespace as indentation prefix
indentPrefix := leadingWhitespace
// Indent each line of included content to match the !include line's indentation
includedLines := strings.Split(string(includedData), "\n")
for _, includeLine := range includedLines {
if strings.TrimSpace(includeLine) == "" {
resultLines = append(resultLines, "")
} else {
resultLines = append(resultLines, indentPrefix+includeLine)
}
}
} else {
// Regular line, just copy it
resultLines = append(resultLines, line)
}
}
content = strings.Join(resultLines, "\n")
return content, nil
}
// findDefaultSSHKey looks for default SSH keys
func findDefaultSSHKey() string {
homeDir, err := os.UserHomeDir()