f2753e6fff
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.
85 lines
1.9 KiB
Go
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])
|
|
}
|