Skip to content

Commit c04b741

Browse files
committed
feat(cli): add monit-query diagnose|rows subcommands
1 parent 5cca6fb commit c04b741

4 files changed

Lines changed: 437 additions & 0 deletions

File tree

‎internal/cli/command_test.go‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,15 @@ func (m *mockClient) CreateMCPServer(context.Context, *flashduty.CreateMCPServer
286286
return nil, fmt.Errorf("mockClient: CreateMCPServer not implemented")
287287
}
288288

289+
// CLI Phase 2: monit-query
290+
func (m *mockClient) MonitQueryDiagnose(context.Context, *flashduty.MonitQueryDiagnoseInput) (*flashduty.MonitQueryDiagnoseOutput, error) {
291+
return nil, fmt.Errorf("mockClient: MonitQueryDiagnose not implemented")
292+
}
293+
294+
func (m *mockClient) MonitQueryRows(context.Context, *flashduty.MonitQueryRowsInput) (*flashduty.MonitQueryRowsOutput, error) {
295+
return nil, fmt.Errorf("mockClient: MonitQueryRows not implemented")
296+
}
297+
289298
// saveAndResetGlobals saves the current state of all global vars that commands
290299
// mutate, resets them to safe defaults, and returns a restore function for
291300
// t.Cleanup.

‎internal/cli/monit_query.go‎

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
6+
flashduty "github.com/flashcatcloud/flashduty-sdk"
7+
"github.com/spf13/cobra"
8+
9+
"github.com/flashcatcloud/flashduty-cli/internal/timeutil"
10+
)
11+
12+
func newMonitQueryCmd() *cobra.Command {
13+
cmd := &cobra.Command{
14+
Use: "monit-query",
15+
Short: "Probe monit-backed datasources (prometheus|victorialogs|loki|mysql)",
16+
}
17+
cmd.AddCommand(newMonitQueryDiagnoseCmd())
18+
cmd.AddCommand(newMonitQueryRowsCmd())
19+
return cmd
20+
}
21+
22+
func newMonitQueryDiagnoseCmd() *cobra.Command {
23+
var (
24+
dsType, dsName, timeStart, timeEnd, inputQuery, operation string
25+
maxLogs, maxPatterns, timeoutSeconds int
26+
)
27+
28+
cmd := &cobra.Command{
29+
Use: "diagnose",
30+
Short: "Pre-clustered RCA findings (log_patterns or metric_trends)",
31+
RunE: func(cmd *cobra.Command, args []string) error {
32+
if dsType == "" || dsName == "" || inputQuery == "" {
33+
return fmt.Errorf("--ds-type, --ds-name, --input-query are required")
34+
}
35+
startTime, err := timeutil.Parse(timeStart)
36+
if err != nil {
37+
return fmt.Errorf("invalid --time-start: %w", err)
38+
}
39+
endTime, err := timeutil.Parse(timeEnd)
40+
if err != nil {
41+
return fmt.Errorf("invalid --time-end: %w", err)
42+
}
43+
44+
return runCommand(cmd, args, func(ctx *RunContext) error {
45+
input := &flashduty.MonitQueryDiagnoseInput{
46+
DsType: dsType,
47+
DsName: dsName,
48+
TimeStart: startTime,
49+
TimeEnd: endTime,
50+
Operation: operation,
51+
Input: flashduty.MonitQueryDiagnoseQuery{Query: inputQuery},
52+
}
53+
if maxLogs > 0 {
54+
input.MaxLogsScanned = maxLogs
55+
}
56+
if maxPatterns > 0 {
57+
input.MaxPatterns = maxPatterns
58+
}
59+
if timeoutSeconds > 0 {
60+
input.TimeoutSeconds = timeoutSeconds
61+
}
62+
63+
result, err := ctx.Client.MonitQueryDiagnose(cmdContext(ctx.Cmd), input)
64+
if err != nil {
65+
return err
66+
}
67+
return ctx.Printer.Print(result, nil)
68+
})
69+
},
70+
}
71+
72+
cmd.Flags().StringVar(&dsType, "ds-type", "", "Datasource type: prometheus|victorialogs|loki|mysql (required)")
73+
cmd.Flags().StringVar(&dsName, "ds-name", "", "Datasource name as configured (required)")
74+
cmd.Flags().StringVar(&timeStart, "time-start", "15m", "Window start (relative '15m'/'1h', unix seconds, or 'now')")
75+
cmd.Flags().StringVar(&timeEnd, "time-end", "now", "Window end (relative, unix seconds, or 'now'; span capped at 6h)")
76+
cmd.Flags().StringVar(&inputQuery, "input-query", "", "Filter-only log query OR matrix PromQL (required)")
77+
cmd.Flags().StringVar(&operation, "operation", "", "log_patterns or metric_trends (default inferred from ds-type)")
78+
cmd.Flags().IntVar(&maxLogs, "max-logs", 0, "Max log lines scanned (default 10000, cap 50000)")
79+
cmd.Flags().IntVar(&maxPatterns, "max-patterns", 0, "Max patterns returned (default 20, cap 50)")
80+
cmd.Flags().IntVar(&timeoutSeconds, "timeout-seconds", 0, "Per-call timeout in seconds (default 25, cap 30)")
81+
82+
return cmd
83+
}
84+
85+
func newMonitQueryRowsCmd() *cobra.Command {
86+
var (
87+
dsType, dsName, expr string
88+
argsKV []string
89+
)
90+
91+
cmd := &cobra.Command{
92+
Use: "rows",
93+
Short: "Raw datasource passthrough (returns values/rows as the datasource itself would)",
94+
RunE: func(cmd *cobra.Command, args []string) error {
95+
if dsType == "" || dsName == "" || expr == "" {
96+
return fmt.Errorf("--ds-type, --ds-name, --expr are required")
97+
}
98+
argsMap, err := parseKVSlice(argsKV)
99+
if err != nil {
100+
return fmt.Errorf("invalid --args: %w", err)
101+
}
102+
103+
return runCommand(cmd, args, func(ctx *RunContext) error {
104+
input := &flashduty.MonitQueryRowsInput{
105+
DsType: dsType,
106+
DsName: dsName,
107+
Expr: expr,
108+
Args: argsMap,
109+
}
110+
result, err := ctx.Client.MonitQueryRows(cmdContext(ctx.Cmd), input)
111+
if err != nil {
112+
return err
113+
}
114+
// MonitQueryRowsOutput intentionally captures the entire response
115+
// body as a RawMessage (data shape is datasource-specific). The
116+
// struct itself marshals to `{}`, so write the raw bytes through.
117+
if len(result.Data) == 0 {
118+
_, err = fmt.Fprintln(ctx.Writer, "{}")
119+
} else {
120+
_, err = fmt.Fprintln(ctx.Writer, string(result.Data))
121+
}
122+
return err
123+
})
124+
},
125+
}
126+
127+
cmd.Flags().StringVar(&dsType, "ds-type", "", "Datasource type (required)")
128+
cmd.Flags().StringVar(&dsName, "ds-name", "", "Datasource name (required)")
129+
cmd.Flags().StringVar(&expr, "expr", "", "Query expression (required)")
130+
cmd.Flags().StringSliceVar(&argsKV, "args", nil, "Arg entries KEY=VALUE (repeatable; values must be strings per monit-query contract)")
131+
132+
return cmd
133+
}

0 commit comments

Comments
 (0)