Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions internal/cli/coverage_test.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,14 @@ var curatedOperationIDs = map[string]bool{
"automation-trigger-write-fire": true,
}

// retiredOperationIDs bridges the release window where the server-side route
// is gone but the CLI still compiles against the previous released SDK spec.
var retiredOperationIDs = map[string]bool{
"monit-preview-sync": true,
"monit-read-query-rows": true,
"monit-rule-write-status": true,
}

// loadSpecOps reads every public GET/POST operation from the openapi spec
// shipped in the linked go-flashduty module — the same spec cligen generates
// against — recording each op's id, path, and whether its 200 response is a
Expand DownExpand Up@@ -80,6 +88,9 @@ func loadSpecPaths(t *testing.T) map[string]string {
t.Helper()
ids := map[string]string{}
for _, op := range loadSpecOps(t) {
if retiredOperationIDs[op.id] {
continue
}
ids[op.id] = op.path
}
return ids
Expand DownExpand Up@@ -158,6 +169,9 @@ func TestGeneratorTargetsFullSpec(t *testing.T) {
curated := map[string]bool{}
wantGenerated := map[string]bool{}
for _, op := range ops {
if retiredOperationIDs[op.id] {
continue
}
if op.streaming {
streaming[op.id] = true
continue
Expand Down
70 changes: 1 addition & 69 deletions internal/cli/monit_query.go
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
package cli

import (
"encoding/json"
"fmt"
"strconv"

Expand All@@ -15,7 +14,6 @@ func newMonitQueryCmd() *cobra.Command {
cmd := newGroupCmd("monit-query", "Probe monit-backed datasources (9 types via data; diagnose: loki|victorialogs log patterns, prometheus metric trends)")
cmd.AddCommand(newMonitQueryDiagnoseCmd())
cmd.AddCommand(newMonitQueryDataCmd())
cmd.AddCommand(newMonitQueryRowsCmd())
return cmd
}

Expand DownExpand Up@@ -135,74 +133,8 @@ func newMonitQueryDataCmd() *cobra.Command {
return cmd
}

func newMonitQueryRowsCmd() *cobra.Command {
var (
dsType, dsName, expr string
argsKV []string
)

cmd := &cobra.Command{
Use: "rows",
Short: "Raw datasource passthrough (returns values/rows as the datasource itself would). Deprecated — prefer 'monit-query data'",
Deprecated: "use 'monit-query data' instead",
Long: curatedLong("Deprecated. Raw datasource passthrough returning values/rows as the datasource itself would. Migrate to 'monit-query data', which preserves frames/records/samples without forcing results into legacy rows.", "Diagnostics", "QueryRows"),
RunE: func(cmd *cobra.Command, args []string) error {
if dsType == "" || dsName == "" || expr == "" {
return fmt.Errorf("--ds-type, --ds-name, --expr are required")
}
argsMap, err := parseKVSlice(argsKV)
if err != nil {
return fmt.Errorf("invalid --args: %w", err)
}
if err := normalizeRawTimeArgs(dsType, argsMap); err != nil {
return err
}

return runCommand(cmd, args, func(ctx *RunContext) error {
input := &flashduty.QueryRowsRequest{
DsType: dsType,
DsName: dsName,
Expr: expr,
Args: argsMap,
}
result, _, err := ctx.Client.Diagnostics.QueryRows(cmdContext(ctx.Cmd), input)
if err != nil {
return err
}
// This command is a raw datasource passthrough. The legacy SDK
// captured the response body (a JSON array of {fields,values}
// objects) as a RawMessage and wrote it through verbatim,
// independent of the --json/--toon output format. go-flashduty
// decodes that same array into []QueryRow, so re-marshal it to
// the equivalent JSON array and write it through unchanged to
// preserve the legacy single-blob output shape.
if result == nil {
_, err = fmt.Fprintln(ctx.Writer, "{}")
return err
}
body, err := json.Marshal(*result)
if err != nil {
return fmt.Errorf("failed to marshal query rows: %w", err)
}
_, err = fmt.Fprintln(ctx.Writer, string(body))
return err
})
},
}

cmd.Flags().StringVar(&dsType, "ds-type", "", "Datasource type (required)")
cmd.Flags().StringVar(&dsName, "ds-name", "", "Datasource name (required)")
registerEnumFlag(cmd, "ds-type", "prometheus", "victorialogs", "loki", "mysql")
cmd.Flags().StringVar(&expr, "expr", "", "Query expression (required)")
cmd.Flags().StringSliceVar(&argsKV, "args", nil, "Arg entries KEY=VALUE (repeatable; values must be strings per monit-query contract). "+
"For loki/victorialogs raw mode, <ds-type>.start/<ds-type>.end accept a relative duration ('15m'), 'now', a date/RFC3339 timestamp, "+
"or a unix epoch in seconds or milliseconds — normalized to the form the datasource requires before sending")

return cmd
}

// normalizeRawTimeArgs rewrites the raw-mode time-window args of a
// monit-query rows call (<ds-type>.start / <ds-type>.end) into the unix-
// monit-query data call (<ds-type>.start / <ds-type>.end) into the unix-
// seconds form the server requires, accepting any format timeutil.Parse
// understands (RFC3339, date/datetime, relative duration, unix seconds or
// milliseconds). Loki and VictoriaLogs are the only ds-types whose raw mode
Expand Down
131 changes: 5 additions & 126 deletions internal/cli/monit_query_test.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,15 +22,6 @@ func TestMonitQueryDiagnoseFlags(t *testing.T) {
}
}

func TestMonitQueryRowsFlags(t *testing.T) {
cmd := newMonitQueryRowsCmd()
for _, name := range []string{"ds-type", "ds-name", "expr", "args"} {
if cmd.Flags().Lookup(name) == nil {
t.Errorf("flag --%s missing", name)
}
}
}

func TestMonitQueryDataFlags(t *testing.T) {
cmd := newMonitQueryDataCmd()
for _, name := range []string{"ds-type", "ds-name", "expr", "args", "delay-seconds"} {
Expand DownExpand Up@@ -209,7 +200,7 @@ func TestMonitQueryDiagnoseInvalidTimeStart(t *testing.T) {
}
}

// --- monit-query rows -----------------------------------------------------
// --- monit-query data -----------------------------------------------------

func TestMonitQueryDataHappyPath(t *testing.T) {
saveAndResetGlobals(t)
Expand DownExpand Up@@ -309,96 +300,6 @@ func TestMonitQueryDataRequiredFlags(t *testing.T) {
}
}

func TestMonitQueryRowsHappyPath(t *testing.T) {
saveAndResetGlobals(t)
stub := newGFStub(t)
// rows is a raw datasource passthrough: the response envelope "data" is a
// JSON array of QueryRow ({fields,values}) objects, decoded into
// QueryRowsResponse ([]QueryRow) and re-marshalled verbatim to the writer.
stub.data = []any{
map[string]any{
"fields": map[string]any{"instance": "node-1"},
"values": map[string]any{"__value__": 1},
},
}

out, err := execCommand(
"monit-query", "rows",
"--ds-type", "prometheus",
"--ds-name", "prom-prod",
"--expr", "up",
"--args", "step=15s",
"--args", "tenant=acme",
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if stub.lastPath != "/monit/query/rows" {
t.Fatalf("expected /monit/query/rows, got %q", stub.lastPath)
}
body := stub.lastBody
if body["ds_type"] != "prometheus" || body["ds_name"] != "prom-prod" || body["expr"] != "up" {
t.Errorf("unexpected rows input: %#v", body)
}
args, _ := body["args"].(map[string]any)
if args["step"] != "15s" || args["tenant"] != "acme" {
t.Errorf("expected args step=15s tenant=acme, got %#v", args)
}
// The rendered output is the re-marshalled row array (passthrough shape).
if !strings.Contains(out, "node-1") || !strings.Contains(out, "__value__") {
t.Errorf("expected rendered rows to carry the datasource payload, got:\n%s", out)
}
}

func TestMonitQueryRowsRequiredFlags(t *testing.T) {
cases := []struct {
name string
args []string
}{
{
name: "missing ds-type",
args: []string{
"monit-query", "rows",
"--ds-name", "prom-prod",
"--expr", "up",
},
},
{
name: "missing ds-name",
args: []string{
"monit-query", "rows",
"--ds-type", "prometheus",
"--expr", "up",
},
},
{
name: "missing expr",
args: []string{
"monit-query", "rows",
"--ds-type", "prometheus",
"--ds-name", "prom-prod",
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
saveAndResetGlobals(t)
stub := newGFStub(t)

_, err := execCommand(tc.args...)
if err == nil {
t.Fatal("expected required-flag error, got nil")
}
if !strings.Contains(err.Error(), "required") {
t.Errorf("expected error to mention 'required', got %q", err.Error())
}
if stub.requests != 0 {
t.Errorf("rows should not have been called: %d request(s)", stub.requests)
}
})
}
}

// --- normalizeRawTimeArgs --------------------------------------------------

func TestNormalizeRawTimeArgsAcceptedFormats(t *testing.T) {
Expand DownExpand Up@@ -469,17 +370,17 @@ func TestNormalizeRawTimeArgsInvalidValue(t *testing.T) {
}
}

// TestMonitQueryRowsRawModeNormalizesRFC3339 is the regression test for the
// TestMonitQueryDataRawModeNormalizesRFC3339 is the regression test for the
// raw-vs-stats time format inconsistency: a raw-mode VictoriaLogs query given
// RFC3339 --args timestamps must reach the server as the unix-seconds form
// the raw query path requires.
func TestMonitQueryRowsRawModeNormalizesRFC3339(t *testing.T) {
func TestMonitQueryDataRawModeNormalizesRFC3339(t *testing.T) {
saveAndResetGlobals(t)
stub := newGFStub(t)
stub.data = []any{}
stub.data = map[string]any{"format": "query_result.v1", "result": map[string]any{"kind": "records", "records": []any{}}}

_, err := execCommand(
"monit-query", "rows",
"monit-query", "data",
"--ds-type", "victorialogs",
"--ds-name", "vl-prod",
"--expr", `{app="api"} |= "error"`,
Expand DownExpand Up@@ -534,25 +435,3 @@ func TestMonitQueryDataInvalidArgs(t *testing.T) {
t.Errorf("data should not have been called: %d request(s)", stub.requests)
}
}

func TestMonitQueryRowsInvalidArgs(t *testing.T) {
saveAndResetGlobals(t)
stub := newGFStub(t)

_, err := execCommand(
"monit-query", "rows",
"--ds-type", "prometheus",
"--ds-name", "prom-prod",
"--expr", "up",
"--args", "no-equals-sign",
)
if err == nil {
t.Fatal("expected error for malformed --args, got nil")
}
if !strings.Contains(err.Error(), "--args") {
t.Errorf("expected error to mention --args, got %q", err.Error())
}
if stub.requests != 0 {
t.Errorf("rows should not have been called: %d request(s)", stub.requests)
}
}
51 changes: 0 additions & 51 deletions internal/cli/zz_generated_alert_rules.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading