diff --git a/cmd/sic/addr.go b/cmd/sic/addr.go index f4a48e2..50d965a 100644 --- a/cmd/sic/addr.go +++ b/cmd/sic/addr.go @@ -1,6 +1,9 @@ package main -import "fmt" +import ( + "fmt" + "strings" +) func parseTarget(s string) (host string, hops []string) { for i := 0; i < len(s); i++ { @@ -61,3 +64,45 @@ func resolveVerbs(hops []string, nest []string) ([]string, error) { } return verbs, nil } + +func parseHostsConfig(data string) (map[string][]string, error) { + cfg := make(map[string][]string) + lines := strings.Split(data, "\n") + var currentHost string + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { + currentHost = line[1 : len(line)-1] + if _, ok := cfg[currentHost]; !ok { + cfg[currentHost] = nil + } + continue + } + if strings.HasPrefix(line, "nest = ") || strings.HasPrefix(line, "nest=") { + eqIdx := strings.Index(line, "=") + if eqIdx == -1 { + continue + } + rest := strings.TrimSpace(line[eqIdx+1:]) + if !strings.HasPrefix(rest, "[") || !strings.HasSuffix(rest, "]") { + continue + } + inner := rest[1 : len(rest)-1] + if strings.TrimSpace(inner) == "" { + cfg[currentHost] = []string{} + continue + } + var items []string + for _, item := range strings.Split(inner, ",") { + item = strings.TrimSpace(item) + item = strings.Trim(item, "\"") + items = append(items, item) + } + cfg[currentHost] = items + } + } + return cfg, nil +} diff --git a/cmd/sic/hostscfg_test.go b/cmd/sic/hostscfg_test.go new file mode 100644 index 0000000..9b7eb9c --- /dev/null +++ b/cmd/sic/hostscfg_test.go @@ -0,0 +1,38 @@ +package main + +import ( + "reflect" + "testing" +) + +// WP7: parseHostsConfig parses the minimal hosts.toml subset (no external dep — stdlib only): +// # comment +// [] +// nest = ["incus", "docker"] +// into map[host] -> ordered runtime types. Comments and blank lines are ignored; an empty nest +// list is a present host with zero hops. +func TestParseHostsConfig(t *testing.T) { + data := "# fleet nest config\n" + + "[boltzmann]\n" + + "nest = [\"incus\", \"docker\"]\n" + + "\n" + + "[data]\n" + + "nest = [\"pct\"]\n" + + "\n" + + "[bosch]\n" + + "nest = []\n" + cfg, err := parseHostsConfig(data) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if !reflect.DeepEqual(cfg["boltzmann"], []string{"incus", "docker"}) { + t.Fatalf("boltzmann nest = %#v, want [incus docker]", cfg["boltzmann"]) + } + if !reflect.DeepEqual(cfg["data"], []string{"pct"}) { + t.Fatalf("data nest = %#v, want [pct]", cfg["data"]) + } + b, ok := cfg["bosch"] + if !ok || len(b) != 0 { + t.Fatalf("bosch must be present with an empty nest; got %#v ok=%v", b, ok) + } +}