feat: enhance host resolution, filtering, and cache management
Release / release (push) Successful in 49s
Release / release (push) Successful in 49s
- **Strategies**: Add resolver strategy input validation and parsing in setup wizard. Support comma-separated input with known strategy mapping. - **Client**: Extend Search and SearchAll to include kind and tag filters. Add pagination for full cache refresh handling large datasets. - **Cache**: Introduce `RecentlyUsed` and `MarkUsed`. Persist `LastUsed` timestamps for entries. - **TUI**: Add recent hosts view, tag/kind filters, and inline editor for user/port override. - **Tests**: Comprehensive unit tests for new features, including strategy validation, cache behavior, and client filtering. - **Docs**: Update README with new TUI features and cache subcommands.
This commit is contained in:
+40
-12
@@ -93,6 +93,8 @@ func runSSHWrapper(args []string) {
|
||||
|
||||
// Cache hit with a fresh TTL — connect directly without querying NetBox.
|
||||
if entry, fresh := c.Get(parsed.Host); fresh {
|
||||
c.MarkUsed(parsed.Host)
|
||||
_ = c.Save()
|
||||
connect(entry.IP, parsed, args)
|
||||
return
|
||||
}
|
||||
@@ -105,7 +107,7 @@ func runSSHWrapper(args []string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
entries, err := nbClient.Search(ctx, parsed.Host)
|
||||
entries, err := nbClient.Search(ctx, parsed.Host, netbox.SearchOptions{})
|
||||
if err != nil {
|
||||
fatalf("NetBox search failed: %v", err)
|
||||
}
|
||||
@@ -132,6 +134,7 @@ func runSSHWrapper(args []string) {
|
||||
}
|
||||
|
||||
c.Upsert(cache.Entry{Name: target.Name, IP: ip, Kind: target.Kind, Tags: target.Tags})
|
||||
c.MarkUsed(target.Name)
|
||||
_ = c.Save()
|
||||
|
||||
connect(ip, parsed, args)
|
||||
@@ -156,7 +159,7 @@ func runTUI() {
|
||||
nbClient = netbox.NewClient(cfg.NetBox.URL, cfg.NetBox.Token, cfg.NetBox.TokenVersion)
|
||||
}
|
||||
|
||||
m := tui.New(nbClient, c)
|
||||
m := tui.New(nbClient, c, cfg.SSH.DefaultUser)
|
||||
p := tea.NewProgram(m, tea.WithAltScreen())
|
||||
final, err := p.Run()
|
||||
if err != nil {
|
||||
@@ -175,11 +178,25 @@ func runTUI() {
|
||||
fmt.Fprintf(os.Stderr, "Connecting to %s (%s)…\n", host.Name, host.IP)
|
||||
|
||||
var sshArgs []string
|
||||
if cfg.SSH.DefaultUser != "" {
|
||||
sshArgs = append(sshArgs, "-l", cfg.SSH.DefaultUser)
|
||||
|
||||
// User override from TUI edit mode takes priority, then config default.
|
||||
user := host.User
|
||||
if user == "" {
|
||||
user = cfg.SSH.DefaultUser
|
||||
}
|
||||
if user != "" {
|
||||
sshArgs = append(sshArgs, "-l", user)
|
||||
}
|
||||
|
||||
if host.Port != "" {
|
||||
sshArgs = append(sshArgs, "-p", host.Port)
|
||||
}
|
||||
|
||||
sshArgs = append(sshArgs, host.IP)
|
||||
|
||||
c.MarkUsed(host.Name)
|
||||
_ = c.Save()
|
||||
|
||||
if err := internalssh.Exec(sshArgs); err != nil {
|
||||
fatalf("%v", err)
|
||||
}
|
||||
@@ -392,7 +409,8 @@ func cacheClearCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
func cacheRefreshCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
var filterTag, filterKind string
|
||||
cmd := &cobra.Command{
|
||||
Use: "refresh",
|
||||
Short: "Re-fetch all known hosts from NetBox",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -405,11 +423,11 @@ func cacheRefreshCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
nbClient := netbox.NewClient(cfg.NetBox.URL, cfg.NetBox.Token, cfg.NetBox.TokenVersion)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// An empty query returns up to 50 entries per type.
|
||||
entries, err := nbClient.Search(ctx, "")
|
||||
opts := netbox.SearchOptions{Tag: filterTag, Kind: filterKind}
|
||||
entries, err := nbClient.SearchAll(ctx, "", opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("NetBox: %w", err)
|
||||
}
|
||||
@@ -420,13 +438,20 @@ func cacheRefreshCmd() *cobra.Command {
|
||||
chain, _ := resolver.New(cfg.Resolver)
|
||||
for i := range entries {
|
||||
e := &entries[i]
|
||||
ip := e.PrimaryIP4
|
||||
if ip == "" && chain != nil {
|
||||
resolved, err := chain.Resolve(ctx, e, nbClient)
|
||||
if err == nil {
|
||||
|
||||
var ip string
|
||||
if chain != nil {
|
||||
if resolved, err := chain.Resolve(ctx, e, nbClient); err == nil {
|
||||
ip = resolved
|
||||
}
|
||||
}
|
||||
if ip == "" {
|
||||
ip = e.PrimaryIP4
|
||||
}
|
||||
if ip == "" {
|
||||
ip = e.PrimaryIP6
|
||||
}
|
||||
|
||||
if ip != "" {
|
||||
c.Upsert(cache.Entry{Name: e.Name, IP: ip, Kind: e.Kind, Tags: e.Tags})
|
||||
}
|
||||
@@ -439,6 +464,9 @@ func cacheRefreshCmd() *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&filterTag, "tag", "", "Filter by NetBox tag slug (e.g. prod)")
|
||||
cmd.Flags().StringVar(&filterKind, "kind", "", "Filter by kind: device or vm")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func fatalf(format string, args ...any) {
|
||||
|
||||
Reference in New Issue
Block a user