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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.20260914095603-3188a14c8a86
github.com/mattn/go-runewidth v0.0.29
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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.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=
Expand Down
4 changes: 4 additions & 0 deletions internal/cli/coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 51 additions & 0 deletions internal/cli/monit_prometheus_label_values.go
Original file line number Diff line number Diff line change
@@ -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 <label_name>",
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())
}
91 changes: 91 additions & 0 deletions internal/cli/monit_prometheus_label_values_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
5 changes: 5 additions & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
85 changes: 85 additions & 0 deletions internal/cli/zz_generated_diagnostics.go

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

1 change: 1 addition & 0 deletions internal/cli/zz_generated_manifest.go

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

Loading
Loading