Files
egutierrez ab7317b0c0 feat: add Go SSH config management functions and type
7 funciones Go del dominio infra para gestion programatica de ~/.ssh/config:
ssh_config_parse (parser de bloques Host/Match), ssh_config_read (lectura del
archivo), ssh_config_find (busqueda por host), ssh_config_add_entry y
ssh_config_remove_entry (CRUD), ssh_config_render (serializacion a texto),
ssh_config_write (escritura atomica). Incluye tipo SshConfigEntry (product type)
y tests unitarios del parser.
2026-04-12 13:54:43 +02:00

85 lines
1.9 KiB
Go

package infra
import (
"bufio"
"strconv"
"strings"
)
// SSHConfigParse parsea el contenido de un archivo ~/.ssh/config y retorna
// una lista de SSHConfigEntry. Ignora comentarios y lineas vacias.
// Los bloques Host con wildcards (* o ?) se ignoran.
func SSHConfigParse(content string) []SSHConfigEntry {
var entries []SSHConfigEntry
var current *SSHConfigEntry
scanner := bufio.NewScanner(strings.NewReader(content))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
key, value := splitDirective(line)
if key == "" {
continue
}
if strings.EqualFold(key, "Host") {
if current != nil && current.Alias != "" {
entries = append(entries, *current)
}
if strings.ContainsAny(value, "*?") {
current = nil
continue
}
current = &SSHConfigEntry{
Alias: value,
Options: make(map[string]string),
}
continue
}
if current == nil {
continue
}
switch strings.ToLower(key) {
case "hostname":
current.HostName = value
case "user":
current.User = value
case "port":
if p, err := strconv.Atoi(value); err == nil {
current.Port = p
}
case "identityfile":
current.IdentityFile = value
default:
current.Options[key] = value
}
}
if current != nil && current.Alias != "" {
entries = append(entries, *current)
}
return entries
}
// splitDirective separa una linea "Key Value" o "Key=Value" en clave y valor.
func splitDirective(line string) (string, string) {
// Intentar separar por =
if idx := strings.IndexByte(line, '='); idx >= 0 {
return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+1:])
}
// Separar por primer espacio/tab
fields := strings.SplitN(line, " ", 2)
if len(fields) < 2 {
fields = strings.SplitN(line, "\t", 2)
}
if len(fields) < 2 {
return "", ""
}
return strings.TrimSpace(fields[0]), strings.TrimSpace(fields[1])
}