package cli import ( "encoding/json" "fmt" "strings" "text/tabwriter" "time" "github.com/spf13/cobra" ) func newSearchCmd() *cobra.Command { var sel selectFlags var asJSON bool cmd := &cobra.Command{ Use: "search", Short: "Search the archive index for matching objects", Long: `search queries the ClickHouse archive index and lists the S3 objects whose subject/host/time-range match, with event counts and sizes. Use the object keys with 'logarchiver fetch' to retrieve and decrypt their contents.`, Example: ` logarchiver search --subject 'logs.k8s.vault.>' --host node-1 --from -24h`, RunE: func(cmd *cobra.Command, _ []string) error { q, err := sel.query(time.Now()) if err != nil { return err } idx, err := newIndexClient(cmd) if err != nil { return err } defer func() { _ = idx.Close() }() results, err := idx.Search(cmd.Context(), q) if err != nil { return err } out := cmd.OutOrStdout() if asJSON { enc := json.NewEncoder(out) enc.SetIndent("", " ") return enc.Encode(results) } if len(results) == 0 { _, _ = fmt.Fprintln(out, "no matching objects") return nil } tw := tabwriter.NewWriter(out, 0, 2, 2, ' ', 0) _, _ = fmt.Fprintln(tw, "OBJECT_KEY\tSUBJECT\tHOSTS\tMIN_TS\tMAX_TS\tEVENTS\tSTORED") var totalEvents, totalStored uint64 for _, r := range results { _, _ = fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%d\t%s\n", r.ObjectKey, r.Subject, strings.Join(r.Hosts, ","), r.MinTS.UTC().Format(time.RFC3339), r.MaxTS.UTC().Format(time.RFC3339), r.EventCount, humanBytes(r.StoredBytes)) totalEvents += r.EventCount totalStored += r.StoredBytes } _ = tw.Flush() _, _ = fmt.Fprintf(out, "\n%d objects, %d events, %s stored\n", len(results), totalEvents, humanBytes(totalStored)) return nil }, } sel.bind(cmd) cmd.Flags().BoolVar(&asJSON, "json", false, "output results as JSON") return cmd }