Skip to content

SREP-347: Adding 'osdctl rhobs' subcommand to interact with RHOBS#876

Draft
Nikokolas3270 wants to merge 1 commit intoopenshift:masterfrom
Nikokolas3270:SREP-347
Draft

SREP-347: Adding 'osdctl rhobs' subcommand to interact with RHOBS#876
Nikokolas3270 wants to merge 1 commit intoopenshift:masterfrom
Nikokolas3270:SREP-347

Conversation

@Nikokolas3270
Copy link
Copy Markdown
Contributor

@Nikokolas3270 Nikokolas3270 commented Apr 13, 2026

Sub-subcommands which are already ready for review:

  • cell: give the RHOBS cell for a given or for the current cluster
  • metrics: give the metrics for a given or for the current cluster - currently only the JSON output format is available... working on some more human friendly formats

Summary by CodeRabbit

  • New Features
    • Added RHOBS command with cell, logs, and metrics subcommands for observability queries
    • Metrics command supports multiple output formats: table, CSV, and JSON
    • Added --cluster-id flag to specify cluster for RHOBS operations

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Apr 13, 2026
@openshift-ci-robot
Copy link
Copy Markdown

openshift-ci-robot commented Apr 13, 2026

@Nikokolas3270: This pull request references SREP-347 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the epic to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Sub-subcommands which are already ready for review:

  • cell: give the RHOBS cell for a given or for the current cluster
  • metrics: give the metrics for a given or for the current cluster - currently only the JSON output format is available... working on some more human friendly formats

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 13, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Apr 13, 2026

PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 2026

Walkthrough

Adds a new rhobs CLI command (with cell, logs, metrics subcommands), extracts HTTP request and OAuth token logic into a new requester package, refactors Vault helpers into a vault package, and updates the Dynatrace code to use the new requester/vault utilities.

Changes

Cohort / File(s) Summary
Root CLI registration
cmd/cmd.go, cmd/rhobs/rootCmd.go
Registers new top-level rhobs command and attaches its subcommands.
RHOBS commands
cmd/rhobs/urlCmd.go, cmd/rhobs/logsCmd.go, cmd/rhobs/metricsCmd.go
Adds cell, logs, and metrics Cobra subcommands with flag parsing, lazy cluster-id resolution, and delegation to backend helpers.
RHOBS backend & formatting
cmd/rhobs/requests.go
Implements cluster-to-RHOBS-cell resolution (OCM/Hive/K8s logic), token retrieval, Prometheus query request, response parsing, and output formatting (table/csv/json).
Generic HTTP + auth requester
cmd/requester/requester.go
New shared Requester struct with Send() and GetScopedAccessToken() to perform HTTP calls and OAuth2 client_credentials token retrieval.
Vault utilities
cmd/vault/vault.go, cmd/vault/vault_test.go
Renames package to vault, exposes GetVaultRef, GetSecretFromVault, GetCredsFromVault, improves error handling and uses viper/logrus.
Dynatrace refactor
cmd/dynatrace/requests.go
Removes local HTTP/vault abstractions; uses requester.Requester/requester.GetScopedAccessToken() and renames vault key constants (DTStorageVaultPathKey, DTDocumentVaultPathKey).

Sequence Diagram(s)

mermaid
sequenceDiagram
participant CLI as CLI (rhobs metrics)
participant K8s as Kubernetes / kubeconfig
participant OCM as OCM API
participant Hive as Hive API
participant Vault as Vault (via vault helper)
participant RHOBS as RHOBS Prometheus API
CLI->>K8s: resolve cluster-id (if not provided)
CLI->>OCM: load cluster by key
OCM->>Hive: map/find ClusterDeployment
Hive->>K8s: create/list client (if needed)
CLI->>Vault: GetScopedAccessToken(authURL, vaultKey, scopes)
Vault->>Vault: read secret via vault kv get
CLI->>RHOBS: GET /prometheus/api/v1/query?query= (Bearer token)
RHOBS->>CLI: JSON result
CLI->>CLI: format output (table/csv/json)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 9 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding a new 'osdctl rhobs' subcommand, which aligns perfectly with the changeset's focus on introducing RHOBS interaction functionality.
Stable And Deterministic Test Names ✅ Passed The pull request does not contain any Ginkgo tests, only Go standard testing package in test files and production code in new files.
Test Structure And Quality ✅ Passed PR does not introduce new Ginkgo test files or substantive changes to existing Ginkgo tests, making this check not applicable.
Microshift Test Compatibility ✅ Passed This PR does not add new Ginkgo e2e tests; it includes CLI implementations and standard Go unit tests, so the check is not applicable.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The pull request does not add any Ginkgo e2e tests. Changes consist of CLI command implementations, utility libraries, and standard Go unit tests.
Topology-Aware Scheduling Compatibility ✅ Passed This PR modifies only CLI tool code with no Kubernetes manifests, operator controllers, or scheduling constraints present.
Ote Binary Stdout Contract ✅ Passed The OTE Binary Stdout Contract check is not applicable to this pull request. osdctl is a standalone CLI tool, not an OpenShift Tests Extension binary.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed This PR does not add any Ginkgo e2e tests. Modified files contain only command implementations and standard Go unit tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch SREP-347

Comment @coderabbitai help to get the list of available commands and usage tips.

@Nikokolas3270 Nikokolas3270 marked this pull request as draft April 13, 2026 08:54
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 13, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Apr 13, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: Nikokolas3270

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Apr 13, 2026

@Nikokolas3270: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/build d85e415 link true /test build
ci/prow/lint d85e415 link true /test lint
ci/prow/format d85e415 link true /test format
ci/prow/test d85e415 link true /test test
ci/prow/verify-docs d85e415 link true /test verify-docs

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Apr 13, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
cmd/vault/vault_test.go (1)

37-45: These tests mirror the implementation instead of exercising it.

Both test bodies rebuild the loginArgs and stdout/stderr branches locally rather than calling setupVaultToken() in cmd/vault/vault.go. That means they can stay green even if the production logic drifts. Please extract the command-construction/output-selection into a helper and assert against that, or inject the exec factory so the tests hit the real code path.

Also applies to: 123-137

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/vault/vault_test.go` around lines 37 - 45, The tests currently
reconstruct loginArgs and stdout/stderr themselves instead of exercising
setupVaultToken; refactor so the production code exposes the
command-construction logic or an injectable exec factory and update tests to
call setupVaultToken so they assert the real behavior. Specifically, extract the
logic that builds the vault command into a helper like buildVaultLoginArgs (or
accept an execCommandFactory in setupVaultToken) and have the tests call
setupVaultToken (or use the injected factory) to capture the constructed
exec.Command and chosen output stream, then assert on that command args and
output selection instead of rebuilding loginArgs locally; apply the same change
to the other test block currently mirroring lines 123-137.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/rhobs/logsCmd.go`:
- Around line 13-37: The newCmdLogs command advertises a --cluster-id flag but
never registers it and its main_ always returns nil; fix by registering the flag
in newCmdLogs (e.g., use cmd.Flags().StringVar(&clusterId, "cluster-id", "",
"cluster identifier") or equivalent) so the flag is recognized, and change main_
to return an explicit not-implemented error (e.g., return fmt.Errorf("logs
command not implemented")) until a real implementation exists; reference
newCmdLogs for flag registration and main_ for the error return.

In `@cmd/rhobs/metricsCmd.go`:
- Around line 59-66: The function retrieveMetrics should not prepend a
human-readable banner for machine-readable outputs; update retrieveMetrics to
only print the "Metrics:" banner for non-structured formats and emit the raw
metrics payload when format is JSON or CSV. Locate retrieveMetrics and the call
to getMetrics (using cmdMetricsOptions.clusterId and promExpr) and change the
printing logic to conditionally print the banner based on MetricsFormat (skip
banner for JSON/CSV), ensuring stdout contains only the raw metrics payload for
machine consumption.

---

Nitpick comments:
In `@cmd/vault/vault_test.go`:
- Around line 37-45: The tests currently reconstruct loginArgs and stdout/stderr
themselves instead of exercising setupVaultToken; refactor so the production
code exposes the command-construction logic or an injectable exec factory and
update tests to call setupVaultToken so they assert the real behavior.
Specifically, extract the logic that builds the vault command into a helper like
buildVaultLoginArgs (or accept an execCommandFactory in setupVaultToken) and
have the tests call setupVaultToken (or use the injected factory) to capture the
constructed exec.Command and chosen output stream, then assert on that command
args and output selection instead of rebuilding loginArgs locally; apply the
same change to the other test block currently mirroring lines 123-137.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b158aa44-15a4-4a81-9ef8-28bee475559b

📥 Commits

Reviewing files that changed from the base of the PR and between 4e14680 and d85e415.

📒 Files selected for processing (10)
  • cmd/cmd.go
  • cmd/dynatrace/requests.go
  • cmd/requester/requester.go
  • cmd/rhobs/logsCmd.go
  • cmd/rhobs/metricsCmd.go
  • cmd/rhobs/requests.go
  • cmd/rhobs/rootCmd.go
  • cmd/rhobs/urlCmd.go
  • cmd/vault/vault.go
  • cmd/vault/vault_test.go
👮 Files not reviewed due to content moderation or server errors (2)
  • cmd/requester/requester.go
  • cmd/rhobs/requests.go

Comment thread cmd/rhobs/logsCmd.go
Comment on lines +13 to +37
func newCmdLogs() *cobra.Command {
cmd := &cobra.Command{
Use: "logs --cluster-id <cluster-identifier>",
Short: "Fetch logs from RHOBS.next",
Run: func(cmd *cobra.Command, args []string) {
var err error
if clusterId == "" {
clusterId, err = k8s.GetCurrentCluster()
if err != nil {
cmdutil.CheckErr(err)
}
}

err = main_(clusterId)
if err != nil {
cmdutil.CheckErr(err)
}
},
}

return cmd
}

func main_(clusterId string) error {
return nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

logs is exposed as a real command, but it has no usable implementation yet.

Line 15 advertises --cluster-id, but the flag is never registered, so osdctl rhobs logs --cluster-id ... will fail with an unknown flag. On top of that, main_() at Lines 36-37 always returns nil, so the command can also exit successfully without doing anything. Please either keep logs unregistered until it's implemented, or at least register the flag and return an explicit not implemented error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/rhobs/logsCmd.go` around lines 13 - 37, The newCmdLogs command advertises
a --cluster-id flag but never registers it and its main_ always returns nil; fix
by registering the flag in newCmdLogs (e.g., use
cmd.Flags().StringVar(&clusterId, "cluster-id", "", "cluster identifier") or
equivalent) so the flag is recognized, and change main_ to return an explicit
not-implemented error (e.g., return fmt.Errorf("logs command not implemented"))
until a real implementation exists; reference newCmdLogs for flag registration
and main_ for the error return.

Comment thread cmd/rhobs/metricsCmd.go Outdated
Comment on lines +59 to +66
func retrieveMetrics(promExpr string, format MetricsFormat) error {
metrics, err := getMetrics(cmdMetricsOptions.clusterId, promExpr, format)
if err != nil {
return fmt.Errorf("failed to retrieve metrics: %v", err)
}

fmt.Println("Metrics:")
fmt.Println(metrics)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't prepend a banner to structured output.

Lines 65-66 make -o json (and -o csv) non-parseable by tools, because stdout starts with Metrics: instead of the payload. Machine-readable formats should be emitted raw.

Suggested fix
 func retrieveMetrics(promExpr string, format MetricsFormat) error {
 	metrics, err := getMetrics(cmdMetricsOptions.clusterId, promExpr, format)
 	if err != nil {
 		return fmt.Errorf("failed to retrieve metrics: %v", err)
 	}
 
-	fmt.Println("Metrics:")
+	if format == MetricsFormatJson || format == MetricsFormatCsv {
+		fmt.Println(metrics)
+		return nil
+	}
+
+	fmt.Println("Metrics:")
 	fmt.Println(metrics)
 
 	return nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func retrieveMetrics(promExpr string, format MetricsFormat) error {
metrics, err := getMetrics(cmdMetricsOptions.clusterId, promExpr, format)
if err != nil {
return fmt.Errorf("failed to retrieve metrics: %v", err)
}
fmt.Println("Metrics:")
fmt.Println(metrics)
func retrieveMetrics(promExpr string, format MetricsFormat) error {
metrics, err := getMetrics(cmdMetricsOptions.clusterId, promExpr, format)
if err != nil {
return fmt.Errorf("failed to retrieve metrics: %v", err)
}
if format == MetricsFormatJson || format == MetricsFormatCsv {
fmt.Println(metrics)
return nil
}
fmt.Println("Metrics:")
fmt.Println(metrics)
return nil
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/rhobs/metricsCmd.go` around lines 59 - 66, The function retrieveMetrics
should not prepend a human-readable banner for machine-readable outputs; update
retrieveMetrics to only print the "Metrics:" banner for non-structured formats
and emit the raw metrics payload when format is JSON or CSV. Locate
retrieveMetrics and the call to getMetrics (using cmdMetricsOptions.clusterId
and promExpr) and change the printing logic to conditionally print the banner
based on MetricsFormat (skip banner for JSON/CSV), ensuring stdout contains only
the raw metrics payload for machine consumption.

@openshift-ci-robot
Copy link
Copy Markdown

openshift-ci-robot commented Apr 14, 2026

@Nikokolas3270: This pull request references SREP-347 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the epic to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Sub-subcommands which are already ready for review:

  • cell: give the RHOBS cell for a given or for the current cluster
  • metrics: give the metrics for a given or for the current cluster - currently only the JSON output format is available... working on some more human friendly formats

Summary by CodeRabbit

  • New Features
  • Added RHOBS command with cell, logs, and metrics subcommands for observability queries
  • Metrics command supports multiple output formats: table, CSV, and JSON
  • Added --cluster-id flag to specify cluster for RHOBS operations

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (10)
cmd/dynatrace/requests.go (3)

105-118: Variable shadows package name.

The local variable requester shadows the imported package requester, which can lead to confusion and potential issues if you need to reference the package again within this scope.

♻️ Suggested rename
-	requester := requester.Requester{
+	req := requester.Requester{
 		Method: http.MethodPost,
 		Url:    dtURL + "platform/storage/query/v1/query:execute",
 		Data:   string(payloadJSON),
 		Headers: map[string]string{
 			"Content-Type":  "application/json",
 			"Authorization": "Bearer " + accessToken,
 		},
 		SuccessCode: http.StatusAccepted,
 	}

 	var resp string
 	for {
-		resp, err = requester.Send()
+		resp, err = req.Send()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/dynatrace/requests.go` around lines 105 - 118, The local variable named
requester shadows the imported requester package; rename the local variable
(e.g., reqClient or dtRequester) wherever it is declared and used (the Requester
struct literal and the variable passed to resp, err = requester.Send()) so you
no longer mask the package name; update all references (the Requester struct
instantiation, its Headers/Method/Url/Data/SuccessCode fields, and the
subsequent Send() call) to use the new variable name.

161-172: Same shadowing issue with requester variable.

Consider renaming to req for consistency across all usages in this file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/dynatrace/requests.go` around lines 161 - 172, The variable named
requester shadows the package/type name requester.Requester; rename the local
variable to req for consistency and to avoid shadowing: change the instantiation
from "requester := requester.Requester{...}" to "req :=
requester.Requester{...}" and update all subsequent uses (e.g., calls to Send())
in this scope/loop to use req.Send() and any other req fields; ensure there are
no leftover references to the old variable name.

205-215: Same shadowing issue with requester variable.

Consider renaming to req for consistency across all usages in this file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/dynatrace/requests.go` around lines 205 - 215, The variable requester is
shadowing the package name requester; rename the local variable requester (from
the instantiation requester := requester.Requester{...}) to req (or similar) and
update its usage (req.Send(), req.Method, req.Url, req.Headers, req.SuccessCode)
throughout this block so you don't conflict with the requester package
identifier; ensure imports and any other references in the file remain unchanged
and compile.
cmd/requester/requester.go (3)

62-70: Error response handling loses context when JSON unmarshal fails.

If the response body is not valid JSON (e.g., HTML error page, plain text), the unmarshal error is returned without the actual response content. This makes debugging difficult.

♻️ Proposed fix to preserve response content
 	if resp.StatusCode != rh.SuccessCode {
 		var respErr responseError
-		err = json.Unmarshal([]byte(body), &respErr)
+		err = json.Unmarshal(body, &respErr)
 		if err != nil {
-			return "", err
+			// If response is not JSON, include raw body in error
+			return "", fmt.Errorf("request failed: %s, body: %s", resp.Status, string(body))
 		}

 		return "", fmt.Errorf("request failed: %v %s", resp.Status, respErr)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/requester/requester.go` around lines 62 - 70, When handling non-success
responses in requester.go (the block comparing resp.StatusCode to
rh.SuccessCode), the current json.Unmarshal into responseError can fail and you
return that unmarshal error without preserving the raw response body; instead
catch json.Unmarshal errors and include the original body (variable body) and
HTTP status in the returned error so callers see the raw response content when
parsing fails (e.g., return an error that contains resp.Status and the raw body)
while still attempting to use responseError when unmarshal succeeds.

29-32: Consider documenting the 600-second timeout.

A 10-minute timeout is unusually long. If this is intentional (e.g., for long-running Dynatrace queries), consider adding a comment explaining why, or making it configurable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/requester/requester.go` around lines 29 - 32, The Send method currently
constructs an http.Client with a hardcoded 600-second timeout (in func Send on
type Requester), which is unusually long; either document why 10 minutes is
required with an inline comment above the client creation (e.g., because
Dynatrace queries may be long-running) or make the timeout configurable by
replacing the literal with a Requester field or configuration value (e.g.,
rh.Timeout or a command-line/env flag) so callers can control it and add a brief
comment describing the default behavior.

17-19: responseError field naming may be misleading.

The field is named Records but maps to JSON key "error". Consider renaming to Error for clarity.

♻️ Proposed rename
 type responseError struct {
-	Records json.RawMessage `json:"error"`
+	Error json.RawMessage `json:"error"`
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/requester/requester.go` around lines 17 - 19, The struct field
responseError.Records is misleading because it maps to the JSON key "error";
rename the field to Error (i.e., change responseError.Records ->
responseError.Error) and keep the json tag `json:"error"`, then update all
usages/readers of responseError that reference Records to use Error instead
(search for responseError, Records, and any unmarshalling sites to update
variable access).
cmd/vault/vault.go (2)

113-113: Returning pointer to map is unnecessary.

Maps in Go are already reference types. Returning *map[string]string adds indirection without benefit and requires callers to dereference (e.g., (*secretData)["key"] at lines 149, 153).

♻️ Return map directly
-func GetSecretFromVault(vaultRef VaultRef) (*map[string]string, error) {
+func GetSecretFromVault(vaultRef VaultRef) (map[string]string, error) {
 	err := setupVaultToken(vaultRef.Addr)
 	if err != nil {
-		return nil, err
+		return nil, err
 	}
 	// ... rest of function ...
-	return &formattedOutput.Data.Data, nil
+	return formattedOutput.Data.Data, nil
 }

And update GetCredsFromVault:

-	clientID, ok := (*secretData)["client_id"]
+	clientID, ok := secretData["client_id"]
 	// ...
-	clientSecret, ok := (*secretData)["client_secret"]
+	clientSecret, ok := secretData["client_secret"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/vault/vault.go` at line 113, Change GetSecretFromVault to return a
map[string]string (not *map[string]string) and update any functions that call it
(e.g., GetCredsFromVault) to expect and pass a map directly; remove pointer
indirection and replace dereferences like (*secretData)["key"] with
secretData["key"]. Ensure signatures for GetSecretFromVault and
GetCredsFromVault are updated consistently and update all callers to stop
dereferencing the map pointer.

113-136: Redundant VAULT_ADDR environment variable setting.

setupVaultToken(vaultRef.Addr) already sets VAULT_ADDR at line 42. The second os.Setenv call at line 119 is unnecessary.

♻️ Remove redundant Setenv
 func GetSecretFromVault(vaultRef VaultRef) (*map[string]string, error) {
 	err := setupVaultToken(vaultRef.Addr)
 	if err != nil {
 		return nil, err
 	}

-	err = os.Setenv("VAULT_ADDR", vaultRef.Addr)
-	if err != nil {
-		return nil, fmt.Errorf("error setting environment variable: %v", err)
-	}
-
 	kvGetCommand := exec.Command("vault", "kv", "get", "-format=json", vaultRef.Path)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/vault/vault.go` around lines 113 - 136, GetSecretFromVault redundantly
calls os.Setenv("VAULT_ADDR", vaultRef.Addr) after setupVaultToken already sets
VAULT_ADDR; remove the redundant os.Setenv call and its error handling from
GetSecretFromVault so the function just relies on setupVaultToken(vaultRef.Addr)
to set the env var, keeping the rest of GetSecretFromVault (exec.Command,
json.Unmarshal, return) unchanged and ensure setupVaultToken remains responsible
for VAULT_ADDR.
cmd/rhobs/requests.go (2)

143-152: Same variable shadowing as in dynatrace/requests.go.

The local variable requester shadows the imported package name.

♻️ Suggested rename
-	requester := requester.Requester{
+	req := requester.Requester{
 		Method: http.MethodGet,
 		Url:    "https://" + rhobsCell + "/api/metrics/v1/hcp/api/v1/query?" + queryData,
 		// ...
 	}

-	resp, err := requester.Send()
+	resp, err := req.Send()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/rhobs/requests.go` around lines 143 - 152, The local variable named
requester shadows the imported package requester; rename the local variable (for
example to req, reqBuilder, or requesterCfg) wherever it's declared and used so
it no longer collides with the package name, e.g., change the variable assigned
from requester.Requester{ ... } and all subsequent references to that variable
(including fields Method, Url, Headers, SuccessCode) to the new name while
leaving the imported package identifier requester unchanged.

238-261: Use fmt.Printf instead of fmt.Print(fmt.Sprintf(...)).

Multiple lines use redundant fmt.Sprintf calls inside fmt.Print. This is flagged by staticcheck (S1038).

♻️ Proposed fix
 	// Header
-	fmt.Print("|")
+	fmt.Print("|")
 	for _, column := range columns {
-		fmt.Print(fmt.Sprintf(" %-*s |", column.width, column.name))
+		fmt.Printf(" %-*s |", column.width, column.name)
 	}
 	// ...
 	for _, metric := range metrics {
 		// ...
-		fmt.Print(fmt.Sprintf("| %*s | %*s |", columns[0].width, time, columns[1].width, value))
+		fmt.Printf("| %*s | %*s |", columns[0].width, time, columns[1].width, value)

 		for _, column := range columns[2:] {
 			labelValue := metric.Metric[column.name]
-			fmt.Print(fmt.Sprintf(" %*s |", column.width, labelValue))
+			fmt.Printf(" %*s |", column.width, labelValue)
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/rhobs/requests.go` around lines 238 - 261, Replace redundant
fmt.Print(fmt.Sprintf(...)) calls with direct fmt.Printf or fmt.Println where
appropriate: use fmt.Printf for formatted strings (e.g., the header row, each
metric row, and label columns) and keep fmt.Println for newline-only prints;
update the blocks that render the table (references: variables columns, metrics,
separatorLine and the loop over metric.Value and columns[2:]) to call fmt.Printf
with the same format verbs and arguments instead of wrapping fmt.Sprintf inside
fmt.Print.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/rhobs/requests.go`:
- Around line 84-87: The call to hiveClient.List is discarding its returned
error; capture and check the error from hiveClient.List(...) (e.g., assign to
err using err = hiveClient.List(...) or err := hiveClient.List(...) as
appropriate) and handle it the same way as the previous error check so that
failures listing clusterDeployments are detected and returned (referencing
hiveClient.List, clusterDeployments, clusterSelector and the existing fmt.Errorf
return).
- Around line 264-287: printMetricsAsCsv currently ignores errors from
csv.NewWriter's writer.Write and writer.Flush which can mask I/O failures;
update printMetricsAsCsv to return an error, check and propagate the error
returned by each writer.Write call (for header and each row) and the final
writer.Flush call, and update the caller at the previous call site accordingly;
locate the function printMetricsAsCsv and the loops that call writer.Write and
the final writer.Flush to add proper error handling and return propagation (also
ensure csv.NewWriter and getTableColumns usage remains unchanged).

---

Nitpick comments:
In `@cmd/dynatrace/requests.go`:
- Around line 105-118: The local variable named requester shadows the imported
requester package; rename the local variable (e.g., reqClient or dtRequester)
wherever it is declared and used (the Requester struct literal and the variable
passed to resp, err = requester.Send()) so you no longer mask the package name;
update all references (the Requester struct instantiation, its
Headers/Method/Url/Data/SuccessCode fields, and the subsequent Send() call) to
use the new variable name.
- Around line 161-172: The variable named requester shadows the package/type
name requester.Requester; rename the local variable to req for consistency and
to avoid shadowing: change the instantiation from "requester :=
requester.Requester{...}" to "req := requester.Requester{...}" and update all
subsequent uses (e.g., calls to Send()) in this scope/loop to use req.Send() and
any other req fields; ensure there are no leftover references to the old
variable name.
- Around line 205-215: The variable requester is shadowing the package name
requester; rename the local variable requester (from the instantiation requester
:= requester.Requester{...}) to req (or similar) and update its usage
(req.Send(), req.Method, req.Url, req.Headers, req.SuccessCode) throughout this
block so you don't conflict with the requester package identifier; ensure
imports and any other references in the file remain unchanged and compile.

In `@cmd/requester/requester.go`:
- Around line 62-70: When handling non-success responses in requester.go (the
block comparing resp.StatusCode to rh.SuccessCode), the current json.Unmarshal
into responseError can fail and you return that unmarshal error without
preserving the raw response body; instead catch json.Unmarshal errors and
include the original body (variable body) and HTTP status in the returned error
so callers see the raw response content when parsing fails (e.g., return an
error that contains resp.Status and the raw body) while still attempting to use
responseError when unmarshal succeeds.
- Around line 29-32: The Send method currently constructs an http.Client with a
hardcoded 600-second timeout (in func Send on type Requester), which is
unusually long; either document why 10 minutes is required with an inline
comment above the client creation (e.g., because Dynatrace queries may be
long-running) or make the timeout configurable by replacing the literal with a
Requester field or configuration value (e.g., rh.Timeout or a command-line/env
flag) so callers can control it and add a brief comment describing the default
behavior.
- Around line 17-19: The struct field responseError.Records is misleading
because it maps to the JSON key "error"; rename the field to Error (i.e., change
responseError.Records -> responseError.Error) and keep the json tag
`json:"error"`, then update all usages/readers of responseError that reference
Records to use Error instead (search for responseError, Records, and any
unmarshalling sites to update variable access).

In `@cmd/rhobs/requests.go`:
- Around line 143-152: The local variable named requester shadows the imported
package requester; rename the local variable (for example to req, reqBuilder, or
requesterCfg) wherever it's declared and used so it no longer collides with the
package name, e.g., change the variable assigned from requester.Requester{ ... }
and all subsequent references to that variable (including fields Method, Url,
Headers, SuccessCode) to the new name while leaving the imported package
identifier requester unchanged.
- Around line 238-261: Replace redundant fmt.Print(fmt.Sprintf(...)) calls with
direct fmt.Printf or fmt.Println where appropriate: use fmt.Printf for formatted
strings (e.g., the header row, each metric row, and label columns) and keep
fmt.Println for newline-only prints; update the blocks that render the table
(references: variables columns, metrics, separatorLine and the loop over
metric.Value and columns[2:]) to call fmt.Printf with the same format verbs and
arguments instead of wrapping fmt.Sprintf inside fmt.Print.

In `@cmd/vault/vault.go`:
- Line 113: Change GetSecretFromVault to return a map[string]string (not
*map[string]string) and update any functions that call it (e.g.,
GetCredsFromVault) to expect and pass a map directly; remove pointer indirection
and replace dereferences like (*secretData)["key"] with secretData["key"].
Ensure signatures for GetSecretFromVault and GetCredsFromVault are updated
consistently and update all callers to stop dereferencing the map pointer.
- Around line 113-136: GetSecretFromVault redundantly calls
os.Setenv("VAULT_ADDR", vaultRef.Addr) after setupVaultToken already sets
VAULT_ADDR; remove the redundant os.Setenv call and its error handling from
GetSecretFromVault so the function just relies on setupVaultToken(vaultRef.Addr)
to set the env var, keeping the rest of GetSecretFromVault (exec.Command,
json.Unmarshal, return) unchanged and ensure setupVaultToken remains responsible
for VAULT_ADDR.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1da9481d-674c-4905-838e-f5d227bd7f37

📥 Commits

Reviewing files that changed from the base of the PR and between d85e415 and 1280a97.

📒 Files selected for processing (10)
  • cmd/cmd.go
  • cmd/dynatrace/requests.go
  • cmd/requester/requester.go
  • cmd/rhobs/logsCmd.go
  • cmd/rhobs/metricsCmd.go
  • cmd/rhobs/requests.go
  • cmd/rhobs/rootCmd.go
  • cmd/rhobs/urlCmd.go
  • cmd/vault/vault.go
  • cmd/vault/vault_test.go
✅ Files skipped from review due to trivial changes (2)
  • cmd/vault/vault_test.go
  • cmd/cmd.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • cmd/rhobs/urlCmd.go
  • cmd/rhobs/metricsCmd.go
  • cmd/rhobs/logsCmd.go
  • cmd/rhobs/rootCmd.go

Comment thread cmd/rhobs/requests.go
Comment on lines +84 to +87
hiveClient.List(context.TODO(), &clusterDeployments, &client.ListOptions{LabelSelector: clusterSelector})
if err != nil {
return "", fmt.Errorf("failed to list cluster deployments for cluster '%s': %v", cluster.ID(), err)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical bug: hiveClient.List error is not captured.

The error returned by hiveClient.List() is discarded. The err checked on line 85 refers to the previous error from LabelSelectorAsSelector (line 78), not the List operation. This means List failures will go undetected, and the code will proceed with potentially uninitialized clusterDeployments.

🐛 Proposed fix
-	hiveClient.List(context.TODO(), &clusterDeployments, &client.ListOptions{LabelSelector: clusterSelector})
-	if err != nil {
+	err = hiveClient.List(context.TODO(), &clusterDeployments, &client.ListOptions{LabelSelector: clusterSelector})
+	if err != nil {
 		return "", fmt.Errorf("failed to list cluster deployments for cluster '%s': %v", cluster.ID(), err)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
hiveClient.List(context.TODO(), &clusterDeployments, &client.ListOptions{LabelSelector: clusterSelector})
if err != nil {
return "", fmt.Errorf("failed to list cluster deployments for cluster '%s': %v", cluster.ID(), err)
}
err = hiveClient.List(context.TODO(), &clusterDeployments, &client.ListOptions{LabelSelector: clusterSelector})
if err != nil {
return "", fmt.Errorf("failed to list cluster deployments for cluster '%s': %v", cluster.ID(), err)
}
🧰 Tools
🪛 golangci-lint (2.11.4)

[error] 84-84: Error return value of hiveClient.List is not checked

(errcheck)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/rhobs/requests.go` around lines 84 - 87, The call to hiveClient.List is
discarding its returned error; capture and check the error from
hiveClient.List(...) (e.g., assign to err using err = hiveClient.List(...) or
err := hiveClient.List(...) as appropriate) and handle it the same way as the
previous error check so that failures listing clusterDeployments are detected
and returned (referencing hiveClient.List, clusterDeployments, clusterSelector
and the existing fmt.Errorf return).

Comment thread cmd/rhobs/requests.go
Comment on lines +264 to +287
func printMetricsAsCsv(metrics []rhobsMetric) {
columns := getTableColumns(metrics)

writer := csv.NewWriter(os.Stdout)

header := []string{}
for _, column := range columns {
header = append(header, column.name)
}
writer.Write(header)

for _, metric := range metrics {
if len(metric.Value) < 2 {
continue
}
row := []string{fmt.Sprintf("%.3f", metric.Value[0]), fmt.Sprintf("%s", metric.Value[1])}
for _, column := range columns[2:] {
row = append(row, metric.Metric[column.name])
}
writer.Write(row)
}

writer.Flush()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

CSV writer errors are not checked.

writer.Write() can fail (e.g., due to I/O errors). Consider checking return values for robustness, especially for the data rows.

🛡️ Proposed fix
 func printMetricsAsCsv(metrics []rhobsMetric) {
+func printMetricsAsCsv(metrics []rhobsMetric) error {
 	columns := getTableColumns(metrics)

 	writer := csv.NewWriter(os.Stdout)

 	header := []string{}
 	for _, column := range columns {
 		header = append(header, column.name)
 	}
-	writer.Write(header)
+	if err := writer.Write(header); err != nil {
+		return fmt.Errorf("failed to write CSV header: %v", err)
+	}

 	for _, metric := range metrics {
 		if len(metric.Value) < 2 {
 			continue
 		}
 		row := []string{fmt.Sprintf("%.3f", metric.Value[0]), fmt.Sprintf("%s", metric.Value[1])}
 		for _, column := range columns[2:] {
 			row = append(row, metric.Metric[column.name])
 		}
-		writer.Write(row)
+		if err := writer.Write(row); err != nil {
+			return fmt.Errorf("failed to write CSV row: %v", err)
+		}
 	}

 	writer.Flush()
+	return writer.Error()
 }

Note: This requires updating the call site at line 173 and the function signature.

🧰 Tools
🪛 golangci-lint (2.11.4)

[error] 273-273: Error return value of writer.Write is not checked

(errcheck)


[error] 283-283: Error return value of writer.Write is not checked

(errcheck)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/rhobs/requests.go` around lines 264 - 287, printMetricsAsCsv currently
ignores errors from csv.NewWriter's writer.Write and writer.Flush which can mask
I/O failures; update printMetricsAsCsv to return an error, check and propagate
the error returned by each writer.Write call (for header and each row) and the
final writer.Flush call, and update the caller at the previous call site
accordingly; locate the function printMetricsAsCsv and the loops that call
writer.Write and the final writer.Flush to add proper error handling and return
propagation (also ensure csv.NewWriter and getTableColumns usage remains
unchanged).

Comment thread cmd/vault/vault.go
versionCheckCmd := exec.Command("vault", "version")

versionCheckCmd.Stdout = os.Stdout
versionCheckCmd.Stdout = os.Stderr
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is driving this change?

Comment thread cmd/rhobs/logsCmd.go

func newCmdLogs() *cobra.Command {
cmd := &cobra.Command{
Use: "logs --cluster-id <cluster-identifier>",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would there be an option to add simple filtering? something like --since=timestamp --untill timestamp --level ERROR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants