Skip to content

Commit 0f7bc39

Browse files
Merge pull request #915 from Nikokolas3270/SREP-347
ROSAENG-14108 & ROSAENG-14110: new '--url' and '--browser' options for the 'osdctl rhobs metrics' and the 'osdctl rhobs logs' commands
2 parents a96132f + 8558b3f commit 0f7bc39

6 files changed

Lines changed: 278 additions & 59 deletions

File tree

cmd/rhobs/logsCmd.go

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88
"time"
99

10+
"github.com/pkg/browser"
1011
"github.com/spf13/cobra"
1112
"k8s.io/apimachinery/pkg/labels"
1213
"k8s.io/apimachinery/pkg/selection"
@@ -15,6 +16,8 @@ import (
1516
var allowedLogLevels = []string{"default", "trace", "info", "warn", "error"}
1617

1718
func newCmdLogs() *cobra.Command {
19+
var isComputingGrafanaUrl bool
20+
var isOpeningGrafanaUrl bool
1821
var lokiExpr string
1922
var namespace string
2023
var labelSelectorStr string
@@ -45,9 +48,13 @@ func newCmdLogs() *cobra.Command {
4548
"By default, logs from all the pods in the given namespace are returned but it is possible to specify " +
4649
"a single pod as an argument or filter pods using their labels. Logs themselves can be also filtered " +
4750
"to only keep the ones containing a given regexp (--contain-regex option) or a given log level (--level option).",
48-
Args: cobra.RangeArgs(0, 1),
51+
Args: cobra.MaximumNArgs(1),
4952
SilenceErrors: true,
5053
RunE: func(cmd *cobra.Command, args []string) error {
54+
if isOpeningGrafanaUrl && !isComputingGrafanaUrl {
55+
return fmt.Errorf("--browser can only be set if --url is set")
56+
}
57+
5158
if cmd.Flags().Changed("query") {
5259
if len(args) > 0 {
5360
return errors.New("pod argument cannot be used with --query flag")
@@ -159,13 +166,19 @@ func newCmdLogs() *cobra.Command {
159166
defaultStartTime := nowTime.Add(-5 * time.Minute)
160167

161168
if cmd.Flags().Changed("since") {
169+
if duration <= 0 {
170+
return fmt.Errorf("--since must be greater than 0")
171+
}
162172
startTime = nowTime.Add(-duration)
163173
} else if !cmd.Flags().Changed("start-time") && !cmd.Flags().Changed("since-time") {
164174
startTime = defaultStartTime
165175
}
166176
if !cmd.Flags().Changed("end-time") {
167177
endTime = nowTime
168178
}
179+
if startTime.After(endTime) {
180+
return fmt.Errorf("value passed to --start-time must be before the value passed to --end-time")
181+
}
169182

170183
isGoingForward := false
171184
if cmd.Flags().Changed("direction") {
@@ -211,18 +224,36 @@ func newCmdLogs() *cobra.Command {
211224
lokiExpr += fmt.Sprintf(` | openshift_cluster_id = "%s"`, rhobsFetcher.clusterExternalId)
212225
}
213226

214-
if isFollowing {
215-
err = rhobsFetcher.StreamLogs(lokiExpr, outputFormat, isPrintingTimestamp, printedFields)
227+
if isComputingGrafanaUrl {
228+
grafanaUrl, err := rhobsFetcher.GetGrafanaLogsUrl(lokiExpr, startTime, endTime, isGoingForward)
229+
if err != nil {
230+
return fmt.Errorf("failed to compute Grafana URL: %v", err)
231+
}
232+
if isOpeningGrafanaUrl {
233+
err = browser.OpenURL(grafanaUrl)
234+
if err != nil {
235+
return fmt.Errorf("failed to open Grafana URL in browser: %v", err)
236+
}
237+
} else {
238+
fmt.Println(grafanaUrl)
239+
}
216240
} else {
217-
err = rhobsFetcher.PrintLogs(lokiExpr, startTime, endTime, logsCount, isGoingForward, outputFormat, isPrintingTimestamp, printedFields)
218-
}
219-
if err != nil {
220-
return fmt.Errorf("failed to print logs: %v", err)
241+
if isFollowing {
242+
err = rhobsFetcher.StreamLogs(lokiExpr, outputFormat, isPrintingTimestamp, printedFields)
243+
} else {
244+
err = rhobsFetcher.PrintLogs(lokiExpr, startTime, endTime, logsCount, isGoingForward, outputFormat, isPrintingTimestamp, printedFields)
245+
}
246+
if err != nil {
247+
return fmt.Errorf("failed to print logs: %v", err)
248+
}
221249
}
222250
return nil
223251
},
224252
}
225253

254+
cmd.Flags().BoolVarP(&isComputingGrafanaUrl, "url", "u", false, "Only compute and print the grafana URL")
255+
cmd.Flags().BoolVarP(&isOpeningGrafanaUrl, "browser", "b", false, "Open in the default browser the URL computed with the --url option - only applicable if --url is set")
256+
226257
cmd.Flags().StringVarP(&lokiExpr, "query", "q", "", "LogQL expression - exclusive with many other flags")
227258
cmd.Flags().StringVarP(&namespace, "namespace", "n", "default", "Name of the namespace")
228259
cmd.Flags().StringVarP(&labelSelectorStr, "selector", "l", "", "Label selector for filtering pods - exclusive with the pod argument")
@@ -257,21 +288,25 @@ func newCmdLogs() *cobra.Command {
257288
`"backward" returns the most recent & interesting logs first, while "forward" matches the behavior of "kubectl logs" by returning the oldest logs first `+
258289
`(default to "backward" unless --follow is set in which case it is forced to "forward")`)
259290

260-
cmd.Flags().BoolVarP(&isFollowing, "follow", "f", false, "Specify if the logs should be streamed - exclusive with --start-time, --end-time, --since, --direction, --limit and --no-limit flags")
291+
cmd.Flags().BoolVarP(&isFollowing, "follow", "f", false, "Specify if the logs should be streamed - exclusive with --url, --start-time, --end-time, --since, --direction, --limit and --no-limit flags")
261292
cmd.MarkFlagsMutuallyExclusive("follow", "start-time")
262293
cmd.MarkFlagsMutuallyExclusive("follow", "since-time")
263294
cmd.MarkFlagsMutuallyExclusive("follow", "end-time")
264295
cmd.MarkFlagsMutuallyExclusive("follow", "since")
265296
cmd.MarkFlagsMutuallyExclusive("follow", "direction")
266297

267-
cmd.Flags().IntVar(&logsCount, "limit", 0, "Maximum number of logs to return - allowed range: [1 100000] (default to 10000 unless --follow is set in which case there is no limit)")
268-
cmd.Flags().BoolVar(&isNotLimitingLogsCount, "no-limit", false, "Do not limit the number of logs to return - exclusive with --limit flag")
269-
cmd.MarkFlagsMutuallyExclusive("limit", "no-limit", "follow")
298+
cmd.Flags().IntVar(&logsCount, "limit", 0, "Maximum number of logs to return - allowed range: [1 100000] - exclusive with --no-limit, --url & --follow flags (default to 10000, no limit if --follow is set)")
299+
cmd.Flags().BoolVar(&isNotLimitingLogsCount, "no-limit", false, "Do not limit the number of logs to return - exclusive with --limit, --url & --follow flags")
300+
cmd.MarkFlagsMutuallyExclusive("limit", "no-limit", "url", "follow")
270301

271-
cmd.Flags().StringVarP(&outputFormatStr, "output", "o", string(LogsFormatText), `Format of the output - allowed values: "text", "csv" or "json"`)
272-
cmd.Flags().BoolVar(&isPrintingTimestamp, "ts", false, `Print metadata timestamps - to be used when log messages do not have a timestamp - not possible with the "json" output format`)
302+
cmd.Flags().StringVarP(&outputFormatStr, "output", "o", string(LogsFormatText), `Format of the output - allowed values: "text", "csv" or "json" - exclusive with --url`)
303+
cmd.MarkFlagsMutuallyExclusive("output", "url")
304+
cmd.Flags().BoolVar(&isPrintingTimestamp, "ts", false, `Print metadata timestamps - to be used when log messages do not have a timestamp - not possible with the "json" output format - exclusive with --url`)
305+
cmd.MarkFlagsMutuallyExclusive("ts", "url")
273306
cmd.Flags().StringSliceVar(&printedFields, "field", []string{"k8s_pod_name"}, `Fields to print with the log message - not possible with the "json" output format - `+
274-
`flag can be repeated / values can also be aggregated with one flag using the comma as separator - possible values: "k8s_namespace_name", "k8s_pod_name", "k8s_container_name" - use the "json" output format to know about all possible fields`)
307+
`flag can be repeated / values can also be aggregated with one flag using the comma as separator - possible values: "k8s_namespace_name", "k8s_pod_name", "k8s_container_name" - `+
308+
`use the "json" output format to know about all possible fields - exclusive with --url`)
309+
cmd.MarkFlagsMutuallyExclusive("field", "url")
275310

276311
return cmd
277312
}

cmd/rhobs/metricsCmd.go

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import (
44
"fmt"
55
"time"
66

7+
"github.com/pkg/browser"
78
"github.com/spf13/cobra"
89
)
910

1011
func newCmdMetrics() *cobra.Command {
12+
var isComputingGrafanaUrl bool
13+
var isOpeningGrafanaUrl bool
1114
var evalTime time.Time
1215
var startTime time.Time
1316
var endTime time.Time
@@ -17,20 +20,26 @@ func newCmdMetrics() *cobra.Command {
1720
var isPrintingClusterResultsOnly bool
1821

1922
cmd := &cobra.Command{
20-
Use: "metrics PromQL-expression",
23+
Use: "metrics [PromQL-expression]",
2124
Short: "Fetch metrics from RHOBS for a given cluster",
2225
Long: "Fetch metrics from RHOBS for a given cluster. " +
2326
"The cluster can be a hosted cluster (HCP), a management cluster (MC) or whatever cluster sending metrics to RHOBS. " +
24-
"The prometheus expression provided as an argument can be either an instant query or a range query. " +
27+
"The prometheus expression provided as an argument can be either an instant query or a range query; it is optional if the --url option is set. " +
2528
"By default, the command will try to evaluate the expression as an instant query at the current time, " +
26-
"but it is possible to specify a different evaluation time using the --time option or a time range using the --start-time, --end-time and --since options." +
29+
"but it is possible to specify a different evaluation time using the --time option or a time range using the --start-time, --end-time and --since options. " +
2730
"Results can be filtered to only keep the ones matching the given cluster (--cluster-id option) with the --filter option " +
2831
"even if it is more efficient to do that filtering at the prometheus expression level.",
29-
Args: cobra.ExactArgs(1),
32+
Args: cobra.MaximumNArgs(1),
3033
SilenceErrors: true,
3134
RunE: func(cmd *cobra.Command, args []string) error {
32-
if len(args) != 1 {
33-
return fmt.Errorf("exactly one Prometheus expression must be provided as an argument")
35+
if !isComputingGrafanaUrl {
36+
if isOpeningGrafanaUrl {
37+
return fmt.Errorf("--browser can only be set if --url is set")
38+
}
39+
40+
if len(args) != 1 {
41+
return fmt.Errorf("a Prometheus expression must be provided when --url is not set")
42+
}
3443
}
3544

3645
nowTime := time.Now()
@@ -45,8 +54,8 @@ func newCmdMetrics() *cobra.Command {
4554
startTime = defaultStartTime
4655
}
4756
if cmd.Flags().Changed("end-time") {
48-
if !cmd.Flags().Changed("start-time") {
49-
return fmt.Errorf("--end-time can only be set if --start-time is set")
57+
if !cmd.Flags().Changed("start-time") && !cmd.Flags().Changed("url") {
58+
return fmt.Errorf("--end-time can only be set if --start-time or --url is set")
5059
}
5160
} else {
5261
endTime = nowTime
@@ -55,8 +64,8 @@ func newCmdMetrics() *cobra.Command {
5564
return fmt.Errorf("value passed to --start-time must be before the value passed to --end-time")
5665
}
5766
if cmd.Flags().Changed("step") {
58-
if !cmd.Flags().Changed("start-time") && !cmd.Flags().Changed("since") {
59-
return fmt.Errorf("--step can only be set if --start-time or --since is set")
67+
if !cmd.Flags().Changed("start-time") && !cmd.Flags().Changed("since") && !cmd.Flags().Changed("url") {
68+
return fmt.Errorf("--step can only be set if --start-time, --since or --url is set")
6069
}
6170
if stepDuration <= 0 {
6271
return fmt.Errorf("--step must be greater than 0")
@@ -75,30 +84,55 @@ func newCmdMetrics() *cobra.Command {
7584
return err
7685
}
7786

78-
if cmd.Flags().Changed("since") || cmd.Flags().Changed("start-time") {
79-
err = rhobsFetcher.PrintRangeMetrics(cmd.Context(), args[0], NewMetricsTimeRange(startTime, endTime, stepDuration), outputFormat, isPrintingClusterResultsOnly)
87+
if isComputingGrafanaUrl {
88+
promExpr := ""
89+
if len(args) == 1 {
90+
promExpr = args[0]
91+
}
92+
grafanaUrl, err := rhobsFetcher.GetGrafanaMetricsUrl(promExpr, startTime, endTime)
93+
if err != nil {
94+
return fmt.Errorf("failed to compute Grafana URL: %v", err)
95+
}
96+
if isOpeningGrafanaUrl {
97+
err = browser.OpenURL(grafanaUrl)
98+
if err != nil {
99+
return fmt.Errorf("failed to open Grafana URL in browser: %v", err)
100+
}
101+
} else {
102+
fmt.Println(grafanaUrl)
103+
}
80104
} else {
81-
err = rhobsFetcher.PrintInstantMetrics(cmd.Context(), args[0], evalTime, outputFormat, isPrintingClusterResultsOnly)
82-
}
83-
if err != nil {
84-
return fmt.Errorf("failed to print metrics: %v", err)
105+
if cmd.Flags().Changed("since") || cmd.Flags().Changed("start-time") {
106+
err = rhobsFetcher.PrintRangeMetrics(cmd.Context(), args[0], NewMetricsTimeRange(startTime, endTime, stepDuration), outputFormat, isPrintingClusterResultsOnly)
107+
} else {
108+
err = rhobsFetcher.PrintInstantMetrics(cmd.Context(), args[0], evalTime, outputFormat, isPrintingClusterResultsOnly)
109+
}
110+
if err != nil {
111+
return fmt.Errorf("failed to print metrics: %v", err)
112+
}
85113
}
86114

87115
return nil
88116
},
89117
}
90118

91-
cmd.Flags().TimeVar(&evalTime, "time", time.Time{}, []string{time.RFC3339}, "Time at which the PromQL expression must be evaluated (default to now)")
119+
cmd.Flags().BoolVarP(&isComputingGrafanaUrl, "url", "u", false, "Only compute and print the grafana URL")
120+
cmd.Flags().BoolVarP(&isOpeningGrafanaUrl, "browser", "b", false, "Open in the default browser the URL computed with the --url option - only applicable if --url is set")
121+
122+
cmd.Flags().TimeVar(&evalTime, "time", time.Time{}, []string{time.RFC3339}, "Time at which the PromQL expression must be evaluated - exclusive with --url (default to now)")
123+
cmd.MarkFlagsMutuallyExclusive("time", "url")
92124
cmd.Flags().TimeVar(&startTime, "start-time", time.Time{}, []string{time.RFC3339}, "Start time at which the PromQL expression must be evaluated - enable time range mode - exclusive with --time (default to 30 minutes ago)")
93-
cmd.Flags().TimeVar(&endTime, "end-time", time.Time{}, []string{time.RFC3339}, "End time at which the PromQL expression must be evaluated - can only be set if --start-time set (default to now)")
125+
cmd.Flags().TimeVar(&endTime, "end-time", time.Time{}, []string{time.RFC3339}, "End time at which the PromQL expression must be evaluated - can only be set if --start-time or --url is set (default to now)")
94126
cmd.Flags().DurationVar(&duration, "since", 0, "Only return values newer than a relative duration (e.g. 1h, 30m) - enable time range mode - exclusive with --time, --start-time & --end-time")
95127
cmd.Flags().DurationVar(&stepDuration, "step", 0, "Duration between data points (e.g. 30s, 2m) - can only be set if in time range mode (i.e. --start-time or --since is set)")
96128
cmd.MarkFlagsMutuallyExclusive("time", "start-time", "since")
97129
cmd.MarkFlagsMutuallyExclusive("time", "end-time", "since")
98130

99-
cmd.Flags().StringVarP(&outputFormatStr, "output", "o", string(MetricsFormatTable), `Format of the output - allowed values: "table", "csv" or "json"`)
131+
cmd.Flags().StringVarP(&outputFormatStr, "output", "o", string(MetricsFormatTable), `Format of the output - allowed values: "table", "csv" or "json" - exclusive with --url`)
132+
cmd.MarkFlagsMutuallyExclusive("output", "url")
100133
cmd.Flags().BoolVarP(&isPrintingClusterResultsOnly, "filter", "f", false, "Only keep the results matching the given cluster - "+
101-
"only effective if some of those results have a _id, _mc_id or mc_name label")
134+
"only effective if some of those results have a _id, _mc_id or mc_name label - exclusive with --url")
135+
cmd.MarkFlagsMutuallyExclusive("filter", "url")
102136

103137
return cmd
104138
}

0 commit comments

Comments
 (0)