package main import ( "context" "fmt" "io" "strings" "time" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/cache" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/config" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/netbox" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/notes" "github.com/spf13/cobra" ) func notesCmd() *cobra.Command { var remote bool var kind string cmd := &cobra.Command{Use: "notes", Short: "Read and write server notes (local by default)"} cmd.PersistentFlags().BoolVar(&remote, "netbox", false, "Use the NetBox comments field instead of local notes") cmd.PersistentFlags().StringVar(&kind, "kind", "", "Host kind: device or vm (required for uncached local hosts)") for _, action := range []string{"show", "set", "append"} { sub := &cobra.Command{Use: action + " [text]", Short: action + " a server note", Args: cobra.RangeArgs(1, 2)} if action == "show" { sub.Args = cobra.ExactArgs(1) } sub.RunE = func(cmd *cobra.Command, args []string) error { if kind != "" && kind != "vm" && kind != "device" { return fmt.Errorf("invalid kind %q", kind) } cfg, err := config.Load() if err != nil { return err } var client *netbox.Client if cfg.HasNetBox() { client = newNetBoxClient(cfg) } host := netbox.HostEntry{Name: args[0], Kind: kind} c := cache.New(cfg.CachePath(), cfg.Cache.TTL) if err := c.Load(); err != nil { return err } var cached []cache.Entry if !remote { cached = c.All() } query := host.Name source, rawName := netbox.SplitTarget(query) var matches []cache.Entry for _, entry := range cached { _, entryName := netbox.SplitTarget(entry.Name) if (kind == "" || kind == entry.Kind) && (strings.EqualFold(entry.Name, query) || (source == "" && strings.EqualFold(entryName, rawName))) { matches = append(matches, entry) } } if len(matches) > 1 { return fmt.Errorf("ambiguous hostname %q; use instance/hostname", query) } if len(matches) == 1 { host.Name = matches[0].Name host.Kind = matches[0].Kind } if !remote && host.Kind == "" { return fmt.Errorf("host is not cached; specify --kind device or --kind vm") } ctx, cancel := context.WithTimeout(cmd.Context(), 20*time.Second) defer cancel() service := notes.New(cfg.NetBox.URL, client) note, err := service.Read(ctx, host, remote) if err != nil { return err } if action == "show" { _, err := fmt.Fprint(cmd.OutOrStdout(), note.Text) return err } var text string if len(args) == 2 { text = args[1] } else { data, err := io.ReadAll(io.LimitReader(cmd.InOrStdin(), 1024*1024+1)) if err != nil { return err } if len(data) > 1024*1024 { return fmt.Errorf("note exceeds 1 MiB") } text = string(data) } if action == "append" && note.Text != "" { note.Text = strings.TrimRight(note.Text, "\n") + "\n" + text } else { note.Text = text } return service.Write(ctx, note, remote) } cmd.AddCommand(sub) } return cmd }