From 974269dc33d486e87b4d7d7749e52addca9a0ca9 Mon Sep 17 00:00:00 2001 From: ysyneu Date: Mon, 14 Sep 2026 02:44:01 -0700 Subject: [PATCH 1/2] chore(deps): bump go-flashduty and regenerate commands --- go.mod | 2 +- go.sum | 4 +- internal/cli/zz_generated_diagnostics.go | 85 ++++++++++++++++++++++ internal/cli/zz_generated_manifest.go | 1 + internal/cli/zz_generated_response_help.go | 1 + skills/flashduty/reference/monit-probe.md | 7 ++ 6 files changed, 97 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e8420d7..f7edce6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/flashcatcloud/flashduty-cli go 1.25.1 require ( - github.com/flashcatcloud/go-flashduty v0.15.2 + github.com/flashcatcloud/go-flashduty v0.15.3-0.20260914093700-c688445d02c3 github.com/mattn/go-runewidth v0.0.29 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 diff --git a/go.sum b/go.sum index e3f6214..1d29788 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/flashcatcloud/go-flashduty v0.15.2 h1:dDUcoaMB48cLoRVJU8AcfXWaSEEHtI0Z6XCX81KHNpQ= -github.com/flashcatcloud/go-flashduty v0.15.2/go.mod h1:YpHiTYXR5NXBI/rGRZfUy537XMkhdCkwA8NW1QoRHwk= +github.com/flashcatcloud/go-flashduty v0.15.3-0.20260914093700-c688445d02c3 h1:Fq3FELZmnEzyeAsHBV9o3pfhjMONonhHFdecRFqw0m8= +github.com/flashcatcloud/go-flashduty v0.15.3-0.20260914093700-c688445d02c3/go.mod h1:YpHiTYXR5NXBI/rGRZfUy537XMkhdCkwA8NW1QoRHwk= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/mattn/go-runewidth v0.0.29 h1:3oGF3R/S2N9DQ3ptftzVIvg2eicmojCzlwBEmqEPDfQ= diff --git a/internal/cli/zz_generated_diagnostics.go b/internal/cli/zz_generated_diagnostics.go index 8811e40..1fb405c 100644 --- a/internal/cli/zz_generated_diagnostics.go +++ b/internal/cli/zz_generated_diagnostics.go @@ -93,7 +93,92 @@ Response fields ('data' envelope is unwrapped — these fields are at the top le return cmd } +func genDiagnosticsQueryExploreCmd() *cobra.Command { + var dataJSON string + var fDatasourceID int64 + var fExpr string + cmd := &cobra.Command{ + Use: "query-explore ", + Short: "Run Explore query", + Long: `Run Explore query. + +Run an Explore query against a configured data source and return frames, samples, or logs. + +API: POST /monit/query/explore (monit-read-query-explore) + +Request fields: + --datasource-id int (required) — Data source ID from '/monit/datasource/list'. Must be a positive JavaScript-safe integer and belong to the authenticated account. (1-9007199254740991) + --expr string (required) — Query expression in the data source's native language (PromQL, LogsQL, SQL, and so on). Non-empty UTF-8 of at most 64 KiB; some data source types enforce a lower limit. (≥1 chars) + args (object, via --data) (required) — Macro substitutions keyed by variable name, used for Grafana-style variables. Keys are at most 256 bytes, values at most 64 KiB, with a 128 KiB total budget. + execution (object, via --data) (required) — Time semantics of the query. The accepted companion fields depend on 'kind': 'instant' takes only 'to_ms' (plus optional 'from_ms'), 'range' requires 'from_ms', 'to_ms', and 'max_data_points', and 'window' takes only 'from_ms' and 'to_ms'. 'step_seconds' is never accepted over HTTP. + - from_ms (integer) — Unix timestamp in milliseconds for the start of the range. Required for 'range' and 'window'; optional for 'instant'. + - kind (string) (required) — Execution kind. 'instant' evaluates at a single point in time, 'range' evaluates a series over a range, and 'window' returns raw rows inside a time window. [instant, range, window] + - max_data_points (integer) — Maximum number of points to return. Required for 'range' and rejected for 'instant' and 'window'. (2-5000) + - min_step_seconds (integer) — Lower bound, in seconds, for the step derived from 'max_data_points'. Optional and only accepted for 'range'. (min 1) + - to_ms (integer) — Unix timestamp in milliseconds for the end of the range. Required for every execution kind. + +Response fields ('data' envelope is unwrapped — these fields are at the top level): + - execution (object) — Execution actually used, present when the data source returned a stepped result. + - effective_step_seconds (integer) (required) — Step, in seconds, the query was executed with after applying 'max_data_points' and 'min_step_seconds'. + - kind (string) (required) — Execution kind; always 'range' when this object is present. [range] + - format (string) (required) — Result contract version; always 'explore_result.v1'. [explore_result.v1] + - result (object) (required) — Result body. Exactly one of 'frames', 'samples', or 'entries' is present and matches 'kind'. + - applied_limit (integer) — Entry limit applied to a logs result; at most 1000. + - entries (array) — Log entries. Present when 'kind' is 'logs'; never longer than 'applied_limit'. + - fields (object) (required) — Log fields as raw JSON values. Integer literals outside JavaScript's safe integer range are returned as decimal strings. + - timestamp_ns (string) (required) — Entry time as a canonical unsigned decimal string of Unix epoch nanoseconds, at most 20 digits. + - frames (array) — Columnar frames. Present when 'kind' is 'frames'; at most 1,000 frames. + - fields (array) (required) — Columns of the frame. + - labels (object) — Label set of this column. Only 'time_series' value fields may carry labels; a 'table' field must not. + - name (string) (required) — Column name, at most 1 MiB of UTF-8. + - type (string) (required) — Column type. 'string' is rejected inside a 'time_series' frame; 'float' holds numbers and 'time' holds UTC RFC3339Nano strings. [string, float, time] + - values (array) (required) — Column values in row order. + - kind (string) (required) — Frame shape. 'table' is an unlabeled table, while 'time_series' carries exactly one time field and one float field. [table, time_series] + - has_more (boolean) — Whether a logs result was truncated by 'applied_limit'. + - kind (string) (required) — Result shape. 'frames' returns columnar tables or time series, 'samples' returns instant values with labels, and 'logs' returns log entries. [frames, samples, logs] + - samples (array) — Instant samples. Present when 'kind' is 'samples'; at most 1,000 samples. + - labels (object) (required) — Label set of the sample. May be empty but never null. + - value (any) (required) — Sample value: a number, or one of the strings 'NaN', '+Inf', and '-Inf'. Never null. +`, + Args: requireBodyFieldOrExactArg("datasource_id", "datasource-id"), + Example: ` flashduty monit query-explore --data '{"args":{},"datasource_id":101,"execution":{"from_ms":1787187600000,"kind":"range","max_data_points":1200,"min_step_seconds":15,"to_ms":1787191200000},"expr":"rate(http_requests_total[5m])"}'`, + RunE: func(cmd *cobra.Command, args []string) error { + return runCommand(cmd, args, func(ctx *RunContext) error { + body, err := genAssembleBody(dataJSON, func(body map[string]any) error { + if err := genFoldPositional(args, body, "datasource_id", "int"); err != nil { + return err + } + if cmd.Flags().Changed("datasource-id") { + body["datasource_id"] = fDatasourceID + } + if cmd.Flags().Changed("expr") { + body["expr"] = fExpr + } + return nil + }) + if err != nil { + return err + } + req := new(flashduty.QueryExploreRequest) + if err := genBindBody(body, req); err != nil { + return err + } + out, _, err := ctx.Client.Diagnostics.QueryExplore(cmdContext(ctx.Cmd), req) + if err != nil { + return err + } + return printGenericResult(ctx, out) + }) + }, + } + cmd.Flags().Int64Var(&fDatasourceID, "datasource-id", 0, "Data source ID from '/monit/datasource/list'. Must be a positive JavaScript-safe integer and belong to the authenticated account. (required) (1-9007199254740991)") + cmd.Flags().StringVar(&fExpr, "expr", "", "Query expression in the data source's native language (PromQL, LogsQL, SQL, and so on). Non-empty UTF-8 of at most 64 KiB; some data source types enforce a lower limit. (required) (≥1 chars)") + cmd.Flags().StringVar(&dataJSON, "data", "", "Full request body as JSON; positional arguments and typed flags override its fields. Accepts inline JSON, or - to read stdin.") + return cmd +} + func registerGeneratedDiagnostics(root *cobra.Command) { gMonit := genGroup(root, "monit", "Monitors API") genAddLeaf(gMonit, genDiagnosticsQueryDataCmd()) + genAddLeaf(gMonit, genDiagnosticsQueryExploreCmd()) } diff --git a/internal/cli/zz_generated_manifest.go b/internal/cli/zz_generated_manifest.go index 8f39691..5ca9441 100644 --- a/internal/cli/zz_generated_manifest.go +++ b/internal/cli/zz_generated_manifest.go @@ -193,6 +193,7 @@ var generatedOpIDs = []string{ "monit-datasource-write-delete", "monit-datasource-write-update", "monit-read-query-data", + "monit-read-query-explore", "monit-rule-read-audit-detail", "monit-rule-read-audits", "monit-rule-read-counter-channel", diff --git a/internal/cli/zz_generated_response_help.go b/internal/cli/zz_generated_response_help.go index 85e861f..2bad4a4 100644 --- a/internal/cli/zz_generated_response_help.go +++ b/internal/cli/zz_generated_response_help.go @@ -104,6 +104,7 @@ var responseHelpBySDKMethod = map[string]string{ "DataSources.WriteCreate": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - account_id (integer) (required) — Account ID.\n - address (string) (required) — Connection address. For Prometheus/Loki/VictoriaLogs: HTTP URL. For MySQL/Oracle/Postgres/ClickHouse: `host:port`. For SLS: endpoint without http/https prefix. Redis/MongoDB diagnostic types: one host:port, bracket IPv6; no URI, userinfo or query. Kafka: 1–32 unique comma-separated host:port bootstrap addresses; payload has no broker list. At most 4096 characters after normalization. (≤4096 chars)\n - alerting_enabled (boolean) (required) — Whether alert evaluation is allowed. Alerting also requires enabled=true and an alerting-capable type. Always false for diagnostic-only types; false does not block non-alerting queries or tools.\n - edge_cluster_name (string) (required) — Monitors edge cluster name responsible for evaluating rules using this datasource.\n - enabled (boolean) (required) — Whether business execution is enabled. Disabled datasources reject business queries and tools; enabling does not change alerting_enabled.\n - id (integer) (required) — Unique datasource ID.\n - name (string) (required) — Datasource display name.\n - note (string) (required) — Optional description.\n - payload (any) (required) — Type-specific configuration block; must contain the key matching `type_ident`. Always `null` in `/monit/datasource/list` responses (the list query does not read the payload column); populated in create/update/info responses. For `tencent_cls`, `secret_key` is masked to an empty string unless it is an `${env:...}` reference. For diagnostic types, password and Kafka tls_key are omitted from responses unless they are ${env:...} references. On update, omit those fields to preserve stored secrets; explicitly send an empty string to clear. Other configuration fields retain their existing behavior.\n - type_ident (string) (required) — Datasource type identifier. Allowed: `prometheus`, `loki`, `mysql`, `oracle`, `postgres`, `clickhouse`, `elasticsearch`, `sls`, `tencent_cls`, `victorialogs`, `redis_node`, `redis_sentinel`, `mongodb_mongod`, `mongodb_mongos`, `kafka`。\n - updated_at (string) (required) — Last update timestamp, Unix epoch seconds. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n", "DataSources.WriteUpdate": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - account_id (integer) (required) — Account ID.\n - address (string) (required) — Connection address. For Prometheus/Loki/VictoriaLogs: HTTP URL. For MySQL/Oracle/Postgres/ClickHouse: `host:port`. For SLS: endpoint without http/https prefix. Redis/MongoDB diagnostic types: one host:port, bracket IPv6; no URI, userinfo or query. Kafka: 1–32 unique comma-separated host:port bootstrap addresses; payload has no broker list. At most 4096 characters after normalization. (≤4096 chars)\n - alerting_enabled (boolean) (required) — Whether alert evaluation is allowed. Alerting also requires enabled=true and an alerting-capable type. Always false for diagnostic-only types; false does not block non-alerting queries or tools.\n - edge_cluster_name (string) (required) — Monitors edge cluster name responsible for evaluating rules using this datasource.\n - enabled (boolean) (required) — Whether business execution is enabled. Disabled datasources reject business queries and tools; enabling does not change alerting_enabled.\n - id (integer) (required) — Unique datasource ID.\n - name (string) (required) — Datasource display name.\n - note (string) (required) — Optional description.\n - payload (any) (required) — Type-specific configuration block; must contain the key matching `type_ident`. Always `null` in `/monit/datasource/list` responses (the list query does not read the payload column); populated in create/update/info responses. For `tencent_cls`, `secret_key` is masked to an empty string unless it is an `${env:...}` reference. For diagnostic types, password and Kafka tls_key are omitted from responses unless they are ${env:...} references. On update, omit those fields to preserve stored secrets; explicitly send an empty string to clear. Other configuration fields retain their existing behavior.\n - type_ident (string) (required) — Datasource type identifier. Allowed: `prometheus`, `loki`, `mysql`, `oracle`, `postgres`, `clickhouse`, `elasticsearch`, `sls`, `tencent_cls`, `victorialogs`, `redis_node`, `redis_sentinel`, `mongodb_mongod`, `mongodb_mongos`, `kafka`。\n - updated_at (string) (required) — Last update timestamp, Unix epoch seconds. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n", "Diagnostics.QueryData": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - format (string) (required) — Public result-contract version. It is independent of the internal monit-edge query protocol version. Fixed at `query_result.v1`, which defines the structure of the `result` field. [query_result.v1]\n - result (object) (required) — Exactly one natural result shape, selected by `kind`.\n - frames (array) — Typed table or time-series frames. A response can contain more than one frame.\n - fields (array) (required) — Columns of the frame; all fields share the same `values` length and row i is composed of each field's `values[i]`.\n - labels (object) — Series labels. Present on the float field of a time-series frame.\n - name (string) (required) — Column name; on a time-series float field, series are distinguished by `labels` and `name` is usually the metric name.\n - type (string) (required) — Value type governing `values` encoding: `string` = strings or null, `float` = numbers or `NaN`/`±Inf` strings or null, `time` = RFC 3339 Nano strings or null. [string, float, time]\n - values (array) (required) — All values of this column in row order; length matches the other fields in the frame.\n - kind (string) (required) — Frame type: `table` for a generic table, `time_series` for a series (exactly one time field and one float field). [table, time_series]\n - kind (string) (required) — Result-kind discriminator, always `frames`, indicating the `frames` payload of typed table/time-series frames. [frames, records, samples]\n - records (array) — Schema-flexible records. Records may have different fields, contain nested JSON, or be null. Integers outside JavaScript's safe range are encoded as decimal strings.\n - samples (array) — Instant samples with their complete label sets.\n - labels (object) (required) — The sample's full label set; may be an empty object but is always present.\n - value (any) (required) — Finite numeric value or a JSON-safe representation of a non-finite float.\n", + "Diagnostics.QueryExplore": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - execution (object) — Execution actually used, present when the data source returned a stepped result.\n - effective_step_seconds (integer) (required) — Step, in seconds, the query was executed with after applying `max_data_points` and `min_step_seconds`.\n - kind (string) (required) — Execution kind; always `range` when this object is present. [range]\n - format (string) (required) — Result contract version; always `explore_result.v1`. [explore_result.v1]\n - result (object) (required) — Result body. Exactly one of `frames`, `samples`, or `entries` is present and matches `kind`.\n - applied_limit (integer) — Entry limit applied to a logs result; at most 1000.\n - entries (array) — Log entries. Present when `kind` is `logs`; never longer than `applied_limit`.\n - fields (object) (required) — Log fields as raw JSON values. Integer literals outside JavaScript's safe integer range are returned as decimal strings.\n - timestamp_ns (string) (required) — Entry time as a canonical unsigned decimal string of Unix epoch nanoseconds, at most 20 digits.\n - frames (array) — Columnar frames. Present when `kind` is `frames`; at most 1,000 frames.\n - fields (array) (required) — Columns of the frame.\n - labels (object) — Label set of this column. Only `time_series` value fields may carry labels; a `table` field must not.\n - name (string) (required) — Column name, at most 1 MiB of UTF-8.\n - type (string) (required) — Column type. `string` is rejected inside a `time_series` frame; `float` holds numbers and `time` holds UTC RFC3339Nano strings. [string, float, time]\n - values (array) (required) — Column values in row order.\n - kind (string) (required) — Frame shape. `table` is an unlabeled table, while `time_series` carries exactly one time field and one float field. [table, time_series]\n - has_more (boolean) — Whether a logs result was truncated by `applied_limit`.\n - kind (string) (required) — Result shape. `frames` returns columnar tables or time series, `samples` returns instant values with labels, and `logs` returns log entries. [frames, samples, logs]\n - samples (array) — Instant samples. Present when `kind` is `samples`; at most 1,000 samples.\n - labels (object) (required) — Label set of the sample. May be empty but never null.\n - value (any) (required) — Sample value: a number, or one of the strings `NaN`, `+Inf`, and `-Inf`. Never null.\n", "ErrorIngestionRules.Create": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - rule_id (string) (required) — ID assigned to the new rule.\n - rule_name (string) (required) — Echo of the created rule's name.\n", "ErrorIngestionRules.HistoryList": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - rules (array) (required) — The application's complete rule list as of this version.\n - account_id (integer) (required) — Account ID.\n - application_id (string) (required) — RUM application ID the rule belongs to.\n - created_at (string) (required) — Unix timestamp in milliseconds when the row was created. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - created_by (integer) (required) — Member ID who created the rule.\n - deleted_at (string) (required) — Unix timestamp in milliseconds when the row was soft-deleted; `0` when not deleted. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - description (string) (required) — Rule description.\n - filters (array>) (required) — The rule's filter conditions as of this snapshot version.\n - key (string) (required) — Field key. One of `error.usr_id`, `error.usr_email`, `error.error_type`, `error.error_message`, `error.error_stack`, `error.view_url`, `error.env`, `error.version`, `error.service`, `error.browser_name`, `error.browser_version`, `error.fingerprint`, `error.is_crash`, or a `context.`-prefixed custom context path (up to 3 levels deep).\n - oper (string) (required) — Match mode: `IN` matches when the field value matches any entry in `vals`; `NOTIN` matches when it matches none. [IN, NOTIN]\n - vals (array) (required) — Values to match against, at least 1 entry. Each entry is an exact string, or a special pattern using wildcards (`*`/`?`), a regexp wrapped in `/`, a `cidr:`-prefixed CIDR match, or a `num:lt|le|gt|ge:`-prefixed numeric comparison.\n - id (integer) (required) — Internal row ID.\n - rule_id (string) (required) — Rule ID.\n - rule_name (string) (required) — Rule name.\n - status (string) (required) — The rule's status as of this snapshot version. [enabled, disabled]\n - updated_at (string) (required) — Unix timestamp in milliseconds when the row was last updated. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - updated_by (integer) (required) — Member ID who last updated the rule.\n - updated_at (string) (required) — Unix timestamp in milliseconds when this snapshot was recorded. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - updated_by (integer) (required) — Member ID whose action triggered this snapshot.\n - updated_by_name (string) (required) — Display name of the member whose action triggered this snapshot.\n - version (integer) (required) — History version number, incrementing from 1.\n", "ErrorIngestionRules.List": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - created_at (string) (required) — Unix timestamp in milliseconds when the rule was created. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n - description (string) (required) — Rule description, up to 512 characters.\n - filters (array>) (required) — The rule's filter conditions.\n - key (string) (required) — Field key. One of `error.usr_id`, `error.usr_email`, `error.error_type`, `error.error_message`, `error.error_stack`, `error.view_url`, `error.env`, `error.version`, `error.service`, `error.browser_name`, `error.browser_version`, `error.fingerprint`, `error.is_crash`, or a `context.`-prefixed custom context path (up to 3 levels deep).\n - oper (string) (required) — Match mode: `IN` matches when the field value matches any entry in `vals`; `NOTIN` matches when it matches none. [IN, NOTIN]\n - vals (array) (required) — Values to match against, at least 1 entry. Each entry is an exact string, or a special pattern using wildcards (`*`/`?`), a regexp wrapped in `/`, a `cidr:`-prefixed CIDR match, or a `num:lt|le|gt|ge:`-prefixed numeric comparison.\n - rule_id (string) (required) — Rule ID.\n - rule_name (string) (required) — Rule name, 1-128 characters. Not required to be unique within the application.\n - status (string) (required) — Current status of the rule. [enabled, disabled]\n - updated_at (string) (required) — Unix timestamp in milliseconds when the rule was last updated. CLI `--json` renders this as an RFC3339 string in the process's local timezone (NOT UTC, and NOT the wire integer); an unset value renders as null.\n", diff --git a/skills/flashduty/reference/monit-probe.md b/skills/flashduty/reference/monit-probe.md index 51cbaba..6983e69 100644 --- a/skills/flashduty/reference/monit-probe.md +++ b/skills/flashduty/reference/monit-probe.md @@ -24,4 +24,11 @@ Query structured data - body-only (`--data`): args (object) - response: single object (`data` unwrapped to the top level) — fields: format (string); result (object) +### query-explore +Run Explore query +- `` (positional, required) int64 — Data source ID from '/monit/datasource/list'. Must be a positive JavaScript-safe integer and belong to the authenticated account. (1-9007199254740991) +- `--expr` string (required) — Query expression in the data source's native language (PromQL, LogsQL, SQL, and so on). Non-empty UTF-8 of at most 64 KiB; some data source types enforce a lower limit. (≥1 chars) +- body-only (`--data`): args (object) (required); execution (object) (required) +- response: single object (`data` unwrapped to the top level) — fields: execution (object); format (string); result (object) + From 34d1cb8a91c2f5159c4946051e0acbf7495b45a2 Mon Sep 17 00:00:00 2001 From: ysyneu Date: Mon, 14 Sep 2026 03:03:51 -0700 Subject: [PATCH 2/2] feat: serve Prometheus label values as a curated command monit-prometheus-read-label-values carries a path parameter and a non-enveloped response, so it is excluded from generation. Bump go-flashduty to the commit that adds the hand-written SDK method, and attach a curated command at the operation's path-name under the monit group: a positional label name, a required --data-source-id flag for the X-DSID header, and standard result rendering. --- go.mod | 2 +- go.sum | 4 +- internal/cli/coverage_test.go | 4 + internal/cli/monit_prometheus_label_values.go | 51 +++++++++++ .../cli/monit_prometheus_label_values_test.go | 91 +++++++++++++++++++ internal/cli/root.go | 5 + skills/flashduty/reference/monit.md | 3 + 7 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 internal/cli/monit_prometheus_label_values.go create mode 100644 internal/cli/monit_prometheus_label_values_test.go diff --git a/go.mod b/go.mod index f7edce6..5773f88 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/flashcatcloud/flashduty-cli go 1.25.1 require ( - github.com/flashcatcloud/go-flashduty v0.15.3-0.20260914093700-c688445d02c3 + github.com/flashcatcloud/go-flashduty v0.15.3-0.20260914095603-3188a14c8a86 github.com/mattn/go-runewidth v0.0.29 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 diff --git a/go.sum b/go.sum index 1d29788..5f5369d 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/flashcatcloud/go-flashduty v0.15.3-0.20260914093700-c688445d02c3 h1:Fq3FELZmnEzyeAsHBV9o3pfhjMONonhHFdecRFqw0m8= -github.com/flashcatcloud/go-flashduty v0.15.3-0.20260914093700-c688445d02c3/go.mod h1:YpHiTYXR5NXBI/rGRZfUy537XMkhdCkwA8NW1QoRHwk= +github.com/flashcatcloud/go-flashduty v0.15.3-0.20260914095603-3188a14c8a86 h1:s+wN5nw7uViY3fa8AP9TXnI48xHLCGTc5oNAwUUBMRw= +github.com/flashcatcloud/go-flashduty v0.15.3-0.20260914095603-3188a14c8a86/go.mod h1:YpHiTYXR5NXBI/rGRZfUy537XMkhdCkwA8NW1QoRHwk= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/mattn/go-runewidth v0.0.29 h1:3oGF3R/S2N9DQ3ptftzVIvg2eicmojCzlwBEmqEPDfQ= diff --git a/internal/cli/coverage_test.go b/internal/cli/coverage_test.go index e0d211c..6d3fa70 100644 --- a/internal/cli/coverage_test.go +++ b/internal/cli/coverage_test.go @@ -27,6 +27,10 @@ var curatedOperationIDs = map[string]bool{ // upload methods (see upload.go). "mapping-data-write-upload": true, "skill-write-upload": true, + // Path parameter plus X-DSID header; the response is also the data + // source's native (non-enveloped) payload. Served by a curated command + // built on the hand-written SDK method (see monit_prometheus_label_values.go). + "monit-prometheus-read-label-values": true, } // loadSpecOps reads every public GET/POST operation from the openapi spec diff --git a/internal/cli/monit_prometheus_label_values.go b/internal/cli/monit_prometheus_label_values.go new file mode 100644 index 0000000..8228e86 --- /dev/null +++ b/internal/cli/monit_prometheus_label_values.go @@ -0,0 +1,51 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// newMonitPrometheusLabelValuesCmd builds the curated leaf for +// GET /monit/prometheus/api/v1/label/{label_name}/values. The op carries a path +// parameter, so it is excluded from generation and served by a hand-written SDK +// method (flashduty.DataSourcesService.ReadPrometheusLabelValues); the command +// name is the mechanical path-derived name (group "monit", verb the remaining +// path segments hyphen-joined) so it stays reachable at its path-name like every +// other operation. +func newMonitPrometheusLabelValuesCmd() *cobra.Command { + var dataSourceID int64 + + cmd := &cobra.Command{ + Use: "prometheus-api-v1-label-{label_name}-values ", + Short: "List Prometheus label values", + Long: `List the values of one label from a Prometheus-compatible data source, through the Monitors proxy. + +The response is the data source's native Prometheus HTTP API payload, not the standard Flashduty envelope: on success it carries status "success" and the label values in data; a query the data source itself rejects still surfaces here as a command error, with the data source's own error text as the message. + +API: GET /monit/prometheus/api/v1/label/{label_name}/values (monit-prometheus-read-label-values)`, + Example: ` flashduty monit prometheus-api-v1-label-{label_name}-values job --data-source-id 12345`, + Args: requireExactArg("label_name"), + RunE: func(cmd *cobra.Command, args []string) error { + return runCommand(cmd, args, func(ctx *RunContext) error { + if dataSourceID < 1 { + return fmt.Errorf("--data-source-id is required") + } + out, _, err := ctx.Client.DataSources.ReadPrometheusLabelValues(cmdContext(ctx.Cmd), uint64(dataSourceID), ctx.Args[0]) + if err != nil { + return err + } + return printGenericResult(ctx, out) + }) + }, + } + + cmd.Flags().Int64Var(&dataSourceID, "data-source-id", 0, "Data source ID to query. Must reference a Prometheus-compatible data source owned by the authenticated account; obtainable via 'flashduty monit datasource-list'. (required)") + _ = cmd.MarkFlagRequired("data-source-id") + return cmd +} + +func attachMonitPrometheusLabelValues(root *cobra.Command) { + g := genGroup(root, "monit", "Monitors API") + genAddLeaf(g, newMonitPrometheusLabelValuesCmd()) +} diff --git a/internal/cli/monit_prometheus_label_values_test.go b/internal/cli/monit_prometheus_label_values_test.go new file mode 100644 index 0000000..2027050 --- /dev/null +++ b/internal/cli/monit_prometheus_label_values_test.go @@ -0,0 +1,91 @@ +package cli + +import ( + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/flashcatcloud/go-flashduty" +) + +// The endpoint returns the data source's native Prometheus payload, not the +// Flashduty {request_id, error, data} envelope gfStub always sends, so this +// test wires its own stub server instead of using gfStub. +func newPrometheusLabelValuesStub(t *testing.T, handler http.HandlerFunc) { + t.Helper() + srv := httptest.NewServer(handler) + t.Cleanup(srv.Close) + newClientFn = func() (*flashduty.Client, error) { + return flashduty.NewClient("test-key", flashduty.WithBaseURL(srv.URL)) + } +} + +func TestMonitPrometheusLabelValuesSendsPathAndHeader(t *testing.T) { + saveAndResetGlobals(t) + + var gotMethod, gotPath, gotDSID string + newPrometheusLabelValuesStub(t, func(w http.ResponseWriter, r *http.Request) { + gotMethod = r.Method + gotPath = r.URL.Path + gotDSID = r.Header.Get("X-DSID") + w.Header().Set("Content-Type", "application/json") + _, _ = io.WriteString(w, `{"status":"success","data":["api","db","worker"]}`) + }) + + out, err := execCommand( + "monit", "prometheus-api-v1-label-{label_name}-values", "job", + "--data-source-id", "12345", + "--json", + ) + if err != nil { + t.Fatalf("[monit-prometheus-label-values] unexpected error: %v", err) + } + if gotMethod != http.MethodGet { + t.Fatalf("[monit-prometheus-label-values] method = %q", gotMethod) + } + if gotPath != "/monit/prometheus/api/v1/label/job/values" { + t.Fatalf("[monit-prometheus-label-values] path = %q", gotPath) + } + if gotDSID != "12345" { + t.Fatalf("[monit-prometheus-label-values] X-DSID = %q", gotDSID) + } + if !strings.Contains(out, `"db"`) { + t.Fatalf("[monit-prometheus-label-values] output = %s", out) + } +} + +func TestMonitPrometheusLabelValuesRequiresDataSourceID(t *testing.T) { + saveAndResetGlobals(t) + newPrometheusLabelValuesStub(t, func(w http.ResponseWriter, r *http.Request) { + t.Fatal("request should not be sent") + }) + + _, err := execCommand("monit", "prometheus-api-v1-label-{label_name}-values", "job") + if err == nil { + t.Fatal("[monit-prometheus-label-values-missing-flag] expected an error") + } + if !strings.Contains(err.Error(), "data-source-id") { + t.Fatalf("[monit-prometheus-label-values-missing-flag] err = %v", err) + } +} + +func TestMonitPrometheusLabelValuesSurfacesDataSourceError(t *testing.T) { + saveAndResetGlobals(t) + newPrometheusLabelValuesStub(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = io.WriteString(w, `{"status":"error","errorType":"bad_data","error":"unknown label name"}`) + }) + + _, err := execCommand( + "monit", "prometheus-api-v1-label-{label_name}-values", "job", + "--data-source-id", "12345", + ) + if err == nil { + t.Fatal("[monit-prometheus-label-values-ds-error] expected an error") + } + if !strings.Contains(err.Error(), "unknown label name") { + t.Fatalf("[monit-prometheus-label-values-ds-error] err = %v", err) + } +} diff --git a/internal/cli/root.go b/internal/cli/root.go index 8b34425..845289d 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -151,6 +151,11 @@ func init() { attachSafariSessionExport(rootCmd) attachSafariAutomationTriggerFire(rootCmd) + // monit-prometheus-read-label-values carries a path parameter, so it is + // excluded from generation; attach its curated leaf to the generated + // `monit` group so the operation stays reachable at its path-name. + attachMonitPrometheusLabelValues(rootCmd) + // Multipart uploads are excluded from generation (non-JSON request body); // attach their curated leaves to the generated path groups. attachEnrichmentMappingDataUpload(rootCmd) diff --git a/skills/flashduty/reference/monit.md b/skills/flashduty/reference/monit.md index 65932ba..6780eff 100644 --- a/skills/flashduty/reference/monit.md +++ b/skills/flashduty/reference/monit.md @@ -20,5 +20,8 @@ Read verbs are free. Mutating verbs change state — confirm before running; eac +### prometheus-api-v1-label-{label_name}-values +List Prometheus label values +- `--data-source-id` int64