Skip to content

feat: add crc ssh and crc vm stats commands#5189

Open
sebrandon1 wants to merge 1 commit intocrc-org:mainfrom
sebrandon1:feat/ssh-and-vm-stats
Open

feat: add crc ssh and crc vm stats commands#5189
sebrandon1 wants to merge 1 commit intocrc-org:mainfrom
sebrandon1:feat/ssh-and-vm-stats

Conversation

@sebrandon1
Copy link
Contributor

@sebrandon1 sebrandon1 commented Mar 19, 2026

Summary

  • Add crc ssh for direct SSH access to the CRC VM — supports interactive shells (crc ssh) and remote commands (crc ssh -- <cmd>)
  • Add crc vm stats to display comprehensive VM diagnostics with usage bars, structured sections, and -o json support for scripting
  • Add crc vm subcommand group as a home for future VM inspection commands

Usage examples

$ crc ssh -- hostname
crc

$ crc ssh -- cat /etc/redhat-release
Red Hat Enterprise Linux release 9.6 (Plow)

$ crc ssh                # opens interactive shell
$ crc vm stats
  System
  ────────
    OS:      Red Hat Enterprise Linux CoreOS 9.6.20260225-1 (Plow)
    Kernel:  5.14.0-570.94.1.el9_6.aarch64 (aarch64)
    Uptime:  4h 0m

  CPU
  ─────
    Cores:     4
    Load Avg:  2.62 (1m)  2.06 (5m)  1.81 (15m)

  Memory
  ────────
    RAM:   6.953GB / 10.93GB  [############........] 64%
    Swap:  disabled

  Disk (/sysroot)
  ─────────────────
    Usage:  25.51GB / 36.98GB  [#############.......] 69%
    Free:   11.47GB

  Network
  ─────────
    Node IP:     127.0.0.1
    Cluster IP:  192.168.127.2
    DNS:         192.168.127.1

  Workload
  ──────────
    Pods:        66
    Containers:  104
    Images:      66

  Top Containers (by memory)
  ────────────────────────────
    kube-apiserver:           1.46GB
    ovnkube-controller:       262.2MB
    openshift-apiserver:      255.7MB
    etcd:                     247.4MB
    kube-controller-manager:  185MB

  Services
  ──────────
    kubelet:  ok
    crio:     ok

  Health
  ────────
    OOM Kills:          0
    Major Page Faults:  258166
$ crc vm stats -o json   # structured output for scripting

Implementation notes

  • crc ssh uses syscall.Exec for interactive mode (replaces process for proper TTY handling) and exec.Command for remote commands
  • crc vm stats collects all data in a single SSH round-trip for efficiency, parses crictl stats JSON natively in Go
  • No changes to the machine.Client interface — uses existing ConnectionDetails() and ssh.CreateRunner()

Test plan

  • make lint passes
  • go test ./cmd/crc/cmd/... passes (man page test updated)
  • crc ssh opens interactive shell
  • crc ssh -- hostname prints VM hostname
  • crc vm stats displays all sections with correct data
  • crc vm stats -o json outputs valid JSON

Summary by CodeRabbit

  • New Features

    • SSH command to connect to an OpenShift cluster node, with optional remote command execution.
    • VM command group for inspecting and managing the virtual machine, with a VM stats subcommand.
    • VM stats subcommand reporting OS, CPU, memory, disk, network, container, service, and kernel health info in JSON or human-readable table formats.
  • Tests

    • Updated test expectations to include the new CLI subcommand man pages.

@openshift-ci openshift-ci bot requested review from anjannath and lstocchi March 19, 2026 19:03
@openshift-ci
Copy link

openshift-ci bot commented Mar 19, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign praveenkumar for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found 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

openshift-ci bot commented Mar 19, 2026

Hi @sebrandon1. Thanks for your PR.

I'm waiting for a crc-org member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Tip

We noticed you've done this a few times! Consider joining the org to skip this step and gain /lgtm and other bot rights. We recommend asking approvers on your previous PRs to sponsor you.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

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

coderabbitai bot commented Mar 19, 2026

Walkthrough

Adds a top-level ssh command and a vm command with a nested stats subcommand; vm is registered with the root CLI. Tests updated to include generated man pages for ssh, vm, and vm stats.

Changes

Cohort / File(s) Summary
CLI Root Registration
cmd/crc/cmd/root.go
Imports cmd/crc/cmd/vm and registers the new vm command via rootCmd.AddCommand(cmdVM.GetVMCmd(config)).
SSH Command
cmd/crc/cmd/ssh.go
Adds crc ssh Cobra subcommand that builds SSH args from machine connection details, locates ssh, selects an existing identity file from connectionDetails.SSHKeys, and either replaces the process with ssh (interactive) or runs it with provided remote args.
VM Command Module
cmd/crc/cmd/vm/vm.go, cmd/crc/cmd/vm/stats.go
Adds vm top-level command (GetVMCmd) and vm stats subcommand. stats connects over SSH, runs a multi-section remote script to collect OS/CPU/memory/disk/network/container/service/kernel metrics, parses results into a vmStats structure, and supports --output json or formatted human-readable output.
Test Updates
cmd/crc/cmd/root_test.go
Updates manpage-generation test expectations to include crc-ssh.1, crc-vm.1, and crc-vm-stats.1.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant CLI as "crc CLI"
  participant Client as "machine client"
  participant SSH as "local ssh binary"
  participant VM as "CRC VM (remote)"

  User->>CLI: run `crc ssh [-- cmd]` or `crc vm stats`
  CLI->>Client: newMachine() / ConnectionDetails()
  Client-->>CLI: connection details (IP, port, SSHKeys)
  CLI->>SSH: LookPath("ssh") and select identity file
  CLI->>SSH: exec/run ssh with args
  SSH->>VM: open SSH session / run remote stats script
  VM-->>SSH: stdout (labeled sections / JSON)
  SSH-->>CLI: exit status + output
  CLI->>User: print JSON or formatted report
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibble bytes beneath the moon,
New ssh and vm make systems swoon,
Stats unfurl in JSON or chart,
Keys found, connections start—hop, depart! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add crc ssh and crc vm stats commands' accurately summarizes the main changes: adding two new commands (ssh and vm stats).
Description check ✅ Passed The description includes all major sections from the template: summary of changes, usage examples, implementation notes, and test plan confirming functionality.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@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 (2)
cmd/crc/cmd/vm/stats.go (1)

129-137: Consider extracting machine existence check to avoid duplication.

This logic duplicates checkIfMachineMissing() from root.go. While the vm package can't directly access cmd package internals, consider refactoring shared utilities to a common location to avoid divergence.

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

In `@cmd/crc/cmd/vm/stats.go` around lines 129 - 137, The runStats function
duplicates machine existence logic found in checkIfMachineMissing; extract the
existence check into a shared utility (e.g., MachineExists or
EnsureMachineExists) in a common package accessible to both cmd and vm, then
replace the inline block in runStats (which constructs
machine.NewSynchronizedMachine / client.Exists()) with a call to that new
utility and return the same crcErr.VMNotExist or propagated errors to avoid
divergence.
cmd/crc/cmd/ssh.go (1)

49-51: Consider improving the error message to aid debugging.

When no SSH key is found, listing the paths that were checked would help users troubleshoot.

💡 Suggested improvement
 	if sshKey == "" {
-		return fmt.Errorf("no SSH key found")
+		return fmt.Errorf("no SSH key found in: %v", connectionDetails.SSHKeys)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/crc/cmd/ssh.go` around lines 49 - 51, The error message for the SSH key
absence (the check using sshKey) is too generic; update the fmt.Errorf call to
include the list of file paths that were inspected so users can debug which
locations were checked. Locate the logic that computes or holds the candidate
paths (e.g., the variable or helper that produces the checked paths used when
resolving sshKey) and change the return to something like fmt.Errorf("no SSH key
found; checked paths: %v", checkedPaths) so the error carries the inspected
paths alongside the existing message.
🤖 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/crc/cmd/ssh.go`:
- Around line 63-70: The current ssh branch uses exec.Command(...).Run() and
returns its error directly, which yields a *exec.ExitError that doesn't match
the exec.CodeExitError the caller (root.go) expects; change the post-run error
handling around cmd.Run() so that if the error is an *exec.ExitError (use
errors.As) you extract the OS exit status (via syscall.WaitStatus) and return a
k8s.io/client-go/util/exec.CodeExitError populated with that status (otherwise
return the original error); update imports to include "errors" (and syscall and
k8s exec package as needed) and reference sshArgs, exec.Command, cmd.Run(),
exec.ExitError, errors.As, syscall.WaitStatus and exec.CodeExitError in the new
handling.
- Line 72: The code calls the Unix-only syscall.Exec(sshPath, sshArgs,
os.Environ()) which fails on Windows; either restrict the file to Unix builds or
replace the syscall.Exec path with the same exec.Command approach used earlier.
Change the syscall.Exec call to construct exec.Command(sshPath, sshArgs...)
(matching how exec.Command is used in the earlier branch), set its
Stdout/Stderr/Stdin as needed, and Run/Start it and return its error, or
alternatively add a Unix build tag (e.g., // +build unix) at the top of ssh.go
to exclude the file from Windows; reference symbols: syscall.Exec, sshPath,
sshArgs, exec.Command.

---

Nitpick comments:
In `@cmd/crc/cmd/ssh.go`:
- Around line 49-51: The error message for the SSH key absence (the check using
sshKey) is too generic; update the fmt.Errorf call to include the list of file
paths that were inspected so users can debug which locations were checked.
Locate the logic that computes or holds the candidate paths (e.g., the variable
or helper that produces the checked paths used when resolving sshKey) and change
the return to something like fmt.Errorf("no SSH key found; checked paths: %v",
checkedPaths) so the error carries the inspected paths alongside the existing
message.

In `@cmd/crc/cmd/vm/stats.go`:
- Around line 129-137: The runStats function duplicates machine existence logic
found in checkIfMachineMissing; extract the existence check into a shared
utility (e.g., MachineExists or EnsureMachineExists) in a common package
accessible to both cmd and vm, then replace the inline block in runStats (which
constructs machine.NewSynchronizedMachine / client.Exists()) with a call to that
new utility and return the same crcErr.VMNotExist or propagated errors to avoid
divergence.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2dbcc0e3-8146-450a-b381-37503b181d77

📥 Commits

Reviewing files that changed from the base of the PR and between 22055cf and c052b74.

📒 Files selected for processing (5)
  • cmd/crc/cmd/root.go
  • cmd/crc/cmd/root_test.go
  • cmd/crc/cmd/ssh.go
  • cmd/crc/cmd/vm/stats.go
  • cmd/crc/cmd/vm/vm.go

Comment on lines +63 to +70
if len(args) > 0 {
sshArgs = append(sshArgs, args...)
cmd := exec.Command(sshPath, sshArgs[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Exit code from remote command may not propagate correctly.

exec.Command.Run() returns *exec.ExitError on non-zero exit, but root.go checks for exec.CodeExitError (from k8s.io/client-go/util/exec). This type mismatch means crc ssh -- false will exit with code 1 instead of preserving the remote command's actual exit code.

🔧 Proposed fix to propagate exit codes
 	if len(args) > 0 {
 		sshArgs = append(sshArgs, args...)
 		cmd := exec.Command(sshPath, sshArgs[1:]...)
 		cmd.Stdin = os.Stdin
 		cmd.Stdout = os.Stdout
 		cmd.Stderr = os.Stderr
-		return cmd.Run()
+		if err := cmd.Run(); err != nil {
+			var exitErr *exec.ExitError
+			if errors.As(err, &exitErr) {
+				os.Exit(exitErr.ExitCode())
+			}
+			return err
+		}
+		return nil
 	}

Add "errors" to imports.

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

In `@cmd/crc/cmd/ssh.go` around lines 63 - 70, The current ssh branch uses
exec.Command(...).Run() and returns its error directly, which yields a
*exec.ExitError that doesn't match the exec.CodeExitError the caller (root.go)
expects; change the post-run error handling around cmd.Run() so that if the
error is an *exec.ExitError (use errors.As) you extract the OS exit status (via
syscall.WaitStatus) and return a k8s.io/client-go/util/exec.CodeExitError
populated with that status (otherwise return the original error); update imports
to include "errors" (and syscall and k8s exec package as needed) and reference
sshArgs, exec.Command, cmd.Run(), exec.ExitError, errors.As, syscall.WaitStatus
and exec.CodeExitError in the new handling.

return cmd.Run()
}

return syscall.Exec(sshPath, sshArgs, os.Environ())
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if CRC has Windows-specific build constraints or platform checks
rg -l 'windows' --type go | head -20
rg -n 'runtime.GOOS|GOOS.*windows' --type go | head -20

Repository: crc-org/crc

Length of output: 3083


🏁 Script executed:

#!/bin/bash
# Check build tags and constraints on ssh.go
echo "=== Build tags on ssh.go ==="
head -15 cmd/crc/cmd/ssh.go

echo -e "\n=== Check for Windows exclusions in CRC ==="
rg '\+build|//go:build' cmd/crc/cmd/ --type go

echo -e "\n=== Check CRC's supported platforms ==="
find . -maxdepth 2 -name 'README*' -o -name 'go.mod' | head -5

Repository: crc-org/crc

Length of output: 364


🏁 Script executed:

#!/bin/bash
# Check README for Windows support
echo "=== CRC README - Platform Support Section ==="
head -100 README.md | grep -i -A5 -B5 "windows\|platform\|support"

echo -e "\n=== Full README first 150 lines ==="
head -150 README.md

echo -e "\n=== Check for Windows-specific ssh handling ==="
rg -n 'windows|Windows' cmd/crc/cmd/ssh.go
rg -r 'func.*ssh|Windows.*ssh' cmd/crc/cmd/ --type go

Repository: crc-org/crc

Length of output: 3379


🏁 Script executed:

#!/bin/bash
# Check how ssh command is used and if there's Windows handling
echo "=== Full ssh.go file ==="
cat cmd/crc/cmd/ssh.go

echo -e "\n=== Check test coverage for ssh.go ==="
find . -name '*ssh*test*.go' -o -name '*test*ssh*.go' | grep -v vendor | head -10

echo -e "\n=== Search for Exec usage in codebase ==="
rg 'syscall\.Exec|exec\.Exec' --type go | grep -v vendor | head -20

Repository: crc-org/crc

Length of output: 1795


🏁 Script executed:

#!/bin/bash
# Check if syscall.Exec is available on Windows
echo "=== Check syscall.Exec availability on Windows ==="
rg 'func Exec' tools/vendor/golang.org/x/sys/windows/ --type go

echo -e "\n=== Check Go's syscall package for Exec on Windows ==="
python3 << 'EOF'
# Check if the code would compile on Windows
# syscall.Exec is only available on Unix-like systems
# On Windows, the syscall package doesn't provide Exec

print("syscall.Exec is a Unix-specific function.")
print("It is NOT available in the Windows version of the syscall package.")
print("On Windows, attempting to call syscall.Exec would result in:")
print("- Compilation error: undefined: syscall.Exec")
print("or")
print("- Build failure when cross-compiling for windows/amd64")
EOF

echo -e "\n=== Check if CRC has any Windows-specific build handling ==="
rg 'build windows|GOOS.*windows' cmd/crc/cmd/ --type go

echo -e "\n=== Check if there are any platform-specific alternatives ==="
rg -B3 -A3 'syscall.Exec' cmd/crc/cmd/ssh.go

Repository: crc-org/crc

Length of output: 970


🏁 Script executed:

#!/bin/bash
# Let's verify the compilation issue - check if ssh.go should have build tags
echo "=== Check first line of ssh.go to confirm no build tags ==="
head -1 cmd/crc/cmd/ssh.go

echo -e "\n=== Check other cmd files for build tag patterns ==="
head -1 cmd/crc/cmd/*.go | grep -B1 'build'

echo -e "\n=== Verify: Does syscall.Exec compilation fail on Windows? ==="
python3 << 'EOF'
# Check if Go compiler recognizes syscall.Exec on Windows
# This is a fact about Go's standard library

print("FACT: syscall.Exec is ONLY defined for Unix-like systems.")
print("Go's syscall package uses build tags internally:")
print("  - syscall.Exec is in syscall/exec_unix.go (with // +build unix)")
print("  - It is NOT available in Windows builds")
print("")
print("THEREFORE: Compiling ssh.go on Windows WILL result in:")
print("  'undefined: syscall.Exec'")
print("")
print("SOLUTION: ssh.go needs a build tag like '// +build !windows'")
print("or use exec.Command for all paths (already used for args > 0 case)")
EOF

echo -e "\n=== Check if Windows CI actually runs ssh command tests ==="
find . -name '.github' -type d | head -2

Repository: crc-org/crc

Length of output: 743


🏁 Script executed:

#!/bin/bash
# Check the Windows CI workflow to see what it actually builds
echo "=== Check Windows CI workflow ==="
cat .github/workflows/make-check-win.yml | head -50

echo -e "\n=== Check what make-check does on Windows ==="
grep -r 'ssh' .github/workflows/ | grep -i windows

echo -e "\n=== Check if cmd/crc/cmd/ssh.go is excluded from Windows builds ==="
rg 'ssh.go' Makefile build.sh .github/ --type text 2>/dev/null || echo "No references found in build files"

Repository: crc-org/crc

Length of output: 903


🏁 Script executed:

#!/bin/bash
# Check if make check actually compiles the full binary on Windows
echo "=== Check Makefile to see what 'make check' does ==="
grep -A 20 'check:' Makefile | head -30

echo -e "\n=== Look for any conditional compilation logic for ssh.go ==="
cat .golangci.yml | grep -A5 -B5 'skip-files\|exclude-files' 2>/dev/null || echo "No file exclusions in linter config"

echo -e "\n=== Check if ssh command is tested anywhere ==="
rg 'sshCmd|runSSH' cmd/crc/cmd/ --type go

Repository: crc-org/crc

Length of output: 1543


🏁 Script executed:

#!/bin/bash
# Check what the 'cross' target actually does
echo "=== Check 'cross' build target in Makefile ==="
rg -A 20 '^cross:' Makefile

echo -e "\n=== Check if Windows build target is actually invoked ==="
rg 'windows.*crc\.exe|crc\.exe.*windows' Makefile | head -10

echo -e "\n=== Search for any build script that might handle Windows differently ==="
ls -la scripts/ build* 2>/dev/null | head -20

echo -e "\n=== Final verification: Does the Windows build actually compile ssh.go? ==="
python3 << 'EOF'
print("ANALYSIS:")
print("=" * 60)
print("")
print("1. ssh.go has NO build tags (// +build unix or // +build !windows)")
print("2. Makefile has Windows build target: $(BUILD_DIR)/windows-amd64/crc.exe")
print("3. ssh.go contains: syscall.Exec(sshPath, sshArgs, os.Environ())")
print("4. syscall.Exec is ONLY defined for Unix in Go stdlib")
print("")
print("CONCLUSION:")
print("- If Windows target is compiled, it MUST fail on syscall.Exec")
print("- The original review comment is CORRECT")
print("- This is a CRITICAL BUG: code cannot compile for Windows")
print("")
print("However, code also shows exec.Command() usage for the case")
print("when args > 0, which works on all platforms.")
print("The fix is to either:")
print("1. Add // +build !windows to ssh.go, OR")
print("2. Replace syscall.Exec with exec.Command for all paths")
EOF

Repository: crc-org/crc

Length of output: 2469


🏁 Script executed:

#!/bin/bash
# One final check: Let's verify if maybe there's special handling we missed
echo "=== Check if ssh.go actually gets compiled by looking at go build output ==="
rg -A 5 -B 5 'undefined.*Exec' . 2>/dev/null || echo "No existing build errors found in repo"

echo -e "\n=== Verify the exact issue with syscall.Exec on Windows ==="
python3 << 'EOF'
# Check Go documentation/source
# syscall.Exec signature and availability

print("VERIFICATION COMPLETE:")
print("=" * 70)
print("")
print("Issue: syscall.Exec is not available on Windows")
print("")
print("Evidence:")
print("1. Makefile includes Windows build: $(BUILD_DIR)/windows-amd64/crc.exe")
print("2. 'make cross' target builds for: macos-arm64, macos-amd64,")
print("   linux-amd64, linux-arm64, AND windows-amd64")
print("3. ssh.go line 72 uses: syscall.Exec(sshPath, sshArgs, os.Environ())")
print("4. ssh.go has NO build tags (no // +build !windows)")
print("5. Go's syscall package only defines Exec for Unix platforms")
print("")
print("Impact: Windows binary CANNOT be built with current code")
print("This is a COMPILATION ERROR, not a runtime error")
print("")
print("Current code already uses exec.Command for conditional branch,")
print("showing that cross-platform approach is possible.")
EOF

Repository: crc-org/crc

Length of output: 7224


Fix Windows build failure: syscall.Exec is undefined on Windows.

syscall.Exec is Unix-specific and will cause a compilation error when building for Windows. The Makefile targets Windows ($(BUILD_DIR)/windows-amd64/crc.exe), but ssh.go lacks a build constraint. The code already uses exec.Command successfully in the conditional branch (lines 62-66); apply the same approach for the syscall.Exec path or add // +build unix to exclude ssh.go from Windows builds.

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

In `@cmd/crc/cmd/ssh.go` at line 72, The code calls the Unix-only
syscall.Exec(sshPath, sshArgs, os.Environ()) which fails on Windows; either
restrict the file to Unix builds or replace the syscall.Exec path with the same
exec.Command approach used earlier. Change the syscall.Exec call to construct
exec.Command(sshPath, sshArgs...) (matching how exec.Command is used in the
earlier branch), set its Stdout/Stderr/Stdin as needed, and Run/Start it and
return its error, or alternatively add a Unix build tag (e.g., // +build unix)
at the top of ssh.go to exclude the file from Windows; reference symbols:
syscall.Exec, sshPath, sshArgs, exec.Command.

@praveenkumar
Copy link
Member

@sebrandon1 we had an old issue #2079 about having crc ssh and since this is a OCP node so we wanted user to not directly ssh to system but use openshift way of accessing the node. We are still on that path and that issue have discussion. Another part of it as I told previously we are working on crc-ng which will use macadam and that tool have capability of ssh to node.

About the stat and vm command, we really didn't feel much of need and so the users, basic statics are shown as part of crc status which will provide details about if openshift is running an accessible, memory consumed, disk usage ..etc.

I am putting this PR on hold as of now and will discuss with team to see if we need something around it.

/hold

Copy link

@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.

♻️ Duplicate comments (2)
cmd/crc/cmd/ssh.go (2)

72-72: ⚠️ Potential issue | 🔴 Critical

Isolate Unix-only syscall.Exec to avoid Windows build breakage.

Line 72 uses syscall.Exec, which is not available on Windows builds. This should be moved behind OS-specific files/build tags (or replaced with a cross-platform execution path).

Minimal direction (delegate to OS-specific helper)
-import (
+import (
 	"fmt"
 	"os"
 	"os/exec"
 	"strconv"
-	"syscall"
@@
-	return syscall.Exec(sshPath, sshArgs, os.Environ())
+	return runInteractiveSSH(sshPath, sshArgs)
 }
#!/bin/bash
# Verify Unix-only API usage and Windows target presence.
rg -nP --type=go '\bsyscall\.Exec\(' cmd/crc/cmd/ssh.go
rg -nP 'windows-amd64|crc\.exe' Makefile
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/crc/cmd/ssh.go` at line 72, The syscall.Exec call using sshPath and
sshArgs is Unix-only and must be isolated into OS-specific files: create a
helper function (e.g., execReplace(path string, args []string, env []string)
error) and replace the direct syscall.Exec(sshPath, sshArgs, os.Environ()) call
with execReplace(sshPath, sshArgs, os.Environ()) in the existing code; implement
execReplace in ssh_unix.go with the current syscall.Exec call and a // +build
!windows tag, and provide a windows stub in ssh_windows.go (or an alternative
cross-platform exec flow using exec.Command.Start/Wait or return a clear error)
with // +build windows so Windows builds no longer reference syscall.Exec.

63-69: ⚠️ Potential issue | 🟠 Major

Preserve remote command exit status for CLI callers.

At Line 69, returning cmd.Run() directly yields *os/exec.ExitError; this does not match the k8s.io/client-go/util/exec.CodeExitError handling pattern in the caller, so remote exit codes may not be propagated correctly.

Proposed fix
 import (
+	"errors"
 	"fmt"
 	"os"
 	"os/exec"
 	"strconv"
 	"syscall"
 
 	"github.com/spf13/cobra"
+	k8sexec "k8s.io/client-go/util/exec"
 )
@@
 	if len(args) > 0 {
 		sshArgs = append(sshArgs, args...)
 		cmd := exec.Command(sshPath, sshArgs[1:]...)
 		cmd.Stdin = os.Stdin
 		cmd.Stdout = os.Stdout
 		cmd.Stderr = os.Stderr
-		return cmd.Run()
+		if err := cmd.Run(); err != nil {
+			var exitErr *exec.ExitError
+			if errors.As(err, &exitErr) {
+				if ws, ok := exitErr.Sys().(syscall.WaitStatus); ok {
+					return k8sexec.CodeExitError{Err: err, Code: ws.ExitStatus()}
+				}
+			}
+			return err
+		}
+		return nil
 	}
#!/bin/bash
# Verify current mismatch between returned error type and caller handling.
rg -nP --type=go 'return cmd\.Run\(\)' cmd/crc/cmd/ssh.go
rg -nP --type=go 'CodeExitError|ExitStatus\(' cmd/crc/cmd/root.go
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/crc/cmd/ssh.go` around lines 63 - 69, The current return of cmd.Run() in
the SSH command path can produce an *os/exec.ExitError which the caller expects
as k8s.io/client-go/util/exec.CodeExitError; change the error handling around
exec.Command(sshPath, sshArgs[1:]...) so that you run cmd.Run(), check if the
error is an *exec.ExitError (or has an exit code via
ExitError.ExitCode()/cmd.ProcessState.ExitCode()), and if so wrap/convert it
into a k8s.io/client-go/util/exec.CodeExitError with the original error and the
numeric exit status before returning; for non-exit errors return them unchanged.
Ensure you reference sshPath, sshArgs, cmd.Run and produce a CodeExitError on
remote-command non-zero exits.
🧹 Nitpick comments (1)
cmd/crc/cmd/vm/stats.go (1)

232-239: Consider debug-level logging for parse failures.

The best-effort parsing approach is reasonable for a stats command, but silently discarding conversion errors makes debugging harder when output looks wrong. Adding debug-level logging would help diagnose issues without affecting normal operation.

💡 Example for one section (apply pattern elsewhere)
 	if lines, ok := sections["CPU"]; ok && len(lines) >= 2 {
-		stats.CPU.Count, _ = strconv.Atoi(strings.TrimSpace(lines[0]))
+		var err error
+		stats.CPU.Count, err = strconv.Atoi(strings.TrimSpace(lines[0]))
+		if err != nil {
+			logging.Debugf("failed to parse CPU count: %v", err)
+		}
 		fields := strings.Fields(lines[1])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/crc/cmd/vm/stats.go` around lines 232 - 239, The CPU parsing silently
ignores conversion errors; capture and log parse failures at debug level so
malformed input is diagnosable. Wrap the strconv.Atoi and strconv.ParseFloat
calls in error checks in the CPU block (sections["CPU"], stats.CPU.Count,
fields) and emit a debug log with context (section name "CPU", the offending
string/field and the parse error) using the repository's debug logger; apply the
same pattern to other parsing blocks so parse errors are logged but do not
change the best-effort behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@cmd/crc/cmd/ssh.go`:
- Line 72: The syscall.Exec call using sshPath and sshArgs is Unix-only and must
be isolated into OS-specific files: create a helper function (e.g.,
execReplace(path string, args []string, env []string) error) and replace the
direct syscall.Exec(sshPath, sshArgs, os.Environ()) call with
execReplace(sshPath, sshArgs, os.Environ()) in the existing code; implement
execReplace in ssh_unix.go with the current syscall.Exec call and a // +build
!windows tag, and provide a windows stub in ssh_windows.go (or an alternative
cross-platform exec flow using exec.Command.Start/Wait or return a clear error)
with // +build windows so Windows builds no longer reference syscall.Exec.
- Around line 63-69: The current return of cmd.Run() in the SSH command path can
produce an *os/exec.ExitError which the caller expects as
k8s.io/client-go/util/exec.CodeExitError; change the error handling around
exec.Command(sshPath, sshArgs[1:]...) so that you run cmd.Run(), check if the
error is an *exec.ExitError (or has an exit code via
ExitError.ExitCode()/cmd.ProcessState.ExitCode()), and if so wrap/convert it
into a k8s.io/client-go/util/exec.CodeExitError with the original error and the
numeric exit status before returning; for non-exit errors return them unchanged.
Ensure you reference sshPath, sshArgs, cmd.Run and produce a CodeExitError on
remote-command non-zero exits.

---

Nitpick comments:
In `@cmd/crc/cmd/vm/stats.go`:
- Around line 232-239: The CPU parsing silently ignores conversion errors;
capture and log parse failures at debug level so malformed input is diagnosable.
Wrap the strconv.Atoi and strconv.ParseFloat calls in error checks in the CPU
block (sections["CPU"], stats.CPU.Count, fields) and emit a debug log with
context (section name "CPU", the offending string/field and the parse error)
using the repository's debug logger; apply the same pattern to other parsing
blocks so parse errors are logged but do not change the best-effort behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3653a5f5-67be-4e80-9774-f10171543ca4

📥 Commits

Reviewing files that changed from the base of the PR and between c052b74 and 48eb7dd.

📒 Files selected for processing (5)
  • cmd/crc/cmd/root.go
  • cmd/crc/cmd/root_test.go
  • cmd/crc/cmd/ssh.go
  • cmd/crc/cmd/vm/stats.go
  • cmd/crc/cmd/vm/vm.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • cmd/crc/cmd/root_test.go
  • cmd/crc/cmd/root.go
  • cmd/crc/cmd/vm/vm.go

Add direct SSH access to the CRC VM via 'crc ssh' with support for
interactive shells (syscall.Exec) and remote commands (exec.Command).
Add 'crc vm stats' to display comprehensive VM diagnostics including
OS info, CPU/memory/disk usage, network config, container stats,
service health, and OOM/fault counters. Supports -o json output
for scripting.

Usage examples:

  $ crc ssh -- hostname
  crc

  $ crc ssh -- cat /etc/redhat-release
  Red Hat Enterprise Linux release 9.6 (Plow)

  $ crc ssh                # opens interactive shell

  $ crc vm stats
  System
  ────────
    OS:      Red Hat Enterprise Linux CoreOS 9.6.20260225-1 (Plow)
    Kernel:  5.14.0-570.94.1.el9_6.aarch64 (aarch64)
    Uptime:  4h 0m

  CPU
  ─────
    Cores:     4
    Load Avg:  2.62 (1m)  2.06 (5m)  1.81 (15m)

  Memory
  ────────
    RAM:   6.953GB / 10.93GB  [############........] 64%
    Swap:  disabled

  Disk (/sysroot)
  ─────────────────
    Usage:  25.51GB / 36.98GB  [#############.......] 69%
    Free:   11.47GB

  Network
  ─────────
    Node IP:     127.0.0.1
    Cluster IP:  192.168.127.2
    DNS:         192.168.127.1

  Workload
  ──────────
    Pods:        66
    Containers:  104
    Images:      66

  Top Containers (by memory)
  ────────────────────────────
    kube-apiserver:           1.46GB
    ovnkube-controller:       262.2MB
    openshift-apiserver:      255.7MB
    etcd:                     247.4MB
    kube-controller-manager:  185MB

  Services
  ──────────
    kubelet:  ok
    crio:     ok

  Health
  ────────
    OOM Kills:          0
    Major Page Faults:  258166

  $ crc vm stats -o json   # JSON output for scripting
@sebrandon1 sebrandon1 force-pushed the feat/ssh-and-vm-stats branch from 48eb7dd to 58b2a0c Compare March 25, 2026 18:25
Copy link

@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: 3

🧹 Nitpick comments (1)
cmd/crc/cmd/vm/stats.go (1)

218-219: Keep JSON list fields stable for scripting.

Because stats starts with nil slices and parseContainerStats returns nil on unmarshal failure, crc vm stats -o json can emit null for list fields like top instead of []. For a scripting-facing command, consistent array types are much easier to consume.

Suggested patch
 func parseStats(output string) (*vmStats, error) {
-	stats := &vmStats{}
+	stats := &vmStats{
+		Containers: containerInfo{Top: []containerStat{}},
+		Services:   []serviceInfo{},
+	}
 	sections := splitSections(output)
@@
 func parseContainerStats(jsonData string) []containerStat {
 	var resp crictlStatsResponse
 	if err := json.Unmarshal([]byte(jsonData), &resp); err != nil {
-		return nil
+		return []containerStat{}
 	}

Also applies to: 288-289, 312-316

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

In `@cmd/crc/cmd/vm/stats.go` around lines 218 - 219, The JSON output can contain
null for list fields because vmStats and its nested container stats may have nil
slices and parseContainerStats can return nil on unmarshal failure; update
parseStats and where parseContainerStats is used (and the parseContainerStats
implementation itself) to ensure slice fields are initialized to empty slices
instead of nil. Concretely: in parseStats, after creating stats := &vmStats{}
and after any unmarshalling, initialize list fields (e.g., stats.Top,
stats.Containers[].Top, and any other slice fields) to empty slices if nil;
modify parseContainerStats to return empty slices/empty struct slice fields on
unmarshal failure rather than nil; ensure any code paths at or around the
parseStats, parseContainerStats, and consumption points (the functions named
parseStats and parseContainerStats and the vmStats struct and its Containers
elements) normalize nil slices to [] before encoding to JSON so the CLI always
emits [] for lists.
🤖 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/crc/cmd/vm/stats.go`:
- Around line 190-192: The NET probe emits plain positional lines (echo
"===NET===", the `ip -br addr show br-ex ...` call, and the `awk
'/^nameserver/{print $2; exit}' /etc/resolv.conf` call) so a blank output from
one command can shift/drop other metrics when later code trims empty lines and
discards sections; change the probe to emit key=value pairs for each metric
(e.g., "bridge_ip=<value>" and "dns=<value>" — emit an empty value like "dns="
when nothing found) and update the downstream trimming logic that currently
removes blank lines/discards whole sections to preserve lines with empty values
rather than deleting the whole NET section. Ensure the emitters and the consumer
use the key names consistently so blank probes no longer erase unrelated
metrics.
- Around line 129-166: The outputFormat validation is performed too late in
runStats (after creating the VM client and establishing SSH), so typos like "-o
jsno" open the VM unnecessarily; validate outputFormat (accepting "json" or
empty string) at the start of runStats and return an error for invalid values
before calling machine.NewSynchronizedMachine, client.Exists, ssh.CreateRunner,
or collectStats; update runStats to perform this early check (or factor a helper
validateOutputFormat(outputFormat)) so no VM/SSH work happens for an invalid
--output.
- Around line 25-32: The command's RunE ignores positional args so unexpected
arguments (e.g. "crc vm stats typo") are currently accepted; update the cobra
command definition (the cmd variable constructed in the stats command) to set
Args: cobra.NoArgs so Cobra validates and rejects unexpected positional
arguments before calling RunE/runStats, ensuring the command fails fast on extra
args.

---

Nitpick comments:
In `@cmd/crc/cmd/vm/stats.go`:
- Around line 218-219: The JSON output can contain null for list fields because
vmStats and its nested container stats may have nil slices and
parseContainerStats can return nil on unmarshal failure; update parseStats and
where parseContainerStats is used (and the parseContainerStats implementation
itself) to ensure slice fields are initialized to empty slices instead of nil.
Concretely: in parseStats, after creating stats := &vmStats{} and after any
unmarshalling, initialize list fields (e.g., stats.Top, stats.Containers[].Top,
and any other slice fields) to empty slices if nil; modify parseContainerStats
to return empty slices/empty struct slice fields on unmarshal failure rather
than nil; ensure any code paths at or around the parseStats,
parseContainerStats, and consumption points (the functions named parseStats and
parseContainerStats and the vmStats struct and its Containers elements)
normalize nil slices to [] before encoding to JSON so the CLI always emits []
for lists.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0d6a0415-d10d-4a3e-8945-84ed1f359cdf

📥 Commits

Reviewing files that changed from the base of the PR and between 48eb7dd and 58b2a0c.

📒 Files selected for processing (5)
  • cmd/crc/cmd/root.go
  • cmd/crc/cmd/root_test.go
  • cmd/crc/cmd/ssh.go
  • cmd/crc/cmd/vm/stats.go
  • cmd/crc/cmd/vm/vm.go
✅ Files skipped from review due to trivial changes (3)
  • cmd/crc/cmd/root.go
  • cmd/crc/cmd/root_test.go
  • cmd/crc/cmd/vm/vm.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/crc/cmd/ssh.go

Comment on lines +25 to +32
cmd := &cobra.Command{
Use: "stats",
Short: "Display detailed virtual machine statistics",
Long: "Display detailed statistics about the CRC virtual machine including OS, CPU, memory, disk, network, containers, and service health.",
RunE: func(_ *cobra.Command, _ []string) error {
return runStats(cfg, outputFormat)
},
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify how other CRC Cobra commands reject unexpected positional args.
# Expectation: commands that accept no args typically set `Args: cobra.NoArgs`.
rg -n --type=go 'Args:\s*cobra\.(NoArgs|ExactArgs|MaximumNArgs|MinimumNArgs)' cmd/crc/cmd

Repository: crc-org/crc

Length of output: 37


🏁 Script executed:

# Check if stats.go exists and read the relevant lines
head -40 cmd/crc/cmd/vm/stats.go

Repository: crc-org/crc

Length of output: 1157


🏁 Script executed:

# Look for other Cobra command definitions to understand the codebase pattern
fd -t f '\.go$' cmd/crc/cmd | head -20 | xargs -I {} sh -c 'echo "=== {} ===" && grep -A 5 "cmd := &cobra.Command" {} | head -10'

Repository: crc-org/crc

Length of output: 775


🏁 Script executed:

# Count how many command definitions exist and check if any use Args
find cmd/crc/cmd -name "*.go" -type f -exec grep -l "cmd := &cobra.Command" {} \; | wc -l

Repository: crc-org/crc

Length of output: 56


🏁 Script executed:

# Search more broadly for any Args usage in command definitions
rg -n "Args\s*:" cmd/crc/cmd -A 1 -B 1 | head -50

Repository: crc-org/crc

Length of output: 152


🏁 Script executed:

# Search for all cobra.Command definitions to count them
rg -n "cmd := &cobra\.Command" cmd/crc/cmd --type=go | head -20

Repository: crc-org/crc

Length of output: 105


🏁 Script executed:

# Check what the actual pattern is for commands in this codebase
# Look at a sample of command definitions
fd -t f '\.go$' cmd/crc/cmd | head -10 | xargs -I {} sh -c 'grep -n "&cobra.Command\|Args:" {} | head -5 && echo "---"'

Repository: crc-org/crc

Length of output: 384


🏁 Script executed:

# Look specifically at how many commands define Args field
rg "&cobra\.Command\{" -A 10 cmd/crc/cmd --type=go | grep -c "Args:"

Repository: crc-org/crc

Length of output: 56


Reject unexpected positional arguments.

This command ignores the args parameter in its RunE function and does not declare an Args validator, so crc vm stats typo will execute the SSH probe instead of failing fast. Add cobra.NoArgs to reject unexpected positional arguments.

Suggested patch
 	cmd := &cobra.Command{
 		Use:   "stats",
 		Short: "Display detailed virtual machine statistics",
 		Long:  "Display detailed statistics about the CRC virtual machine including OS, CPU, memory, disk, network, containers, and service health.",
+		Args:  cobra.NoArgs,
 		RunE: func(_ *cobra.Command, _ []string) error {
 			return runStats(cfg, outputFormat)
 		},
 	}
📝 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
cmd := &cobra.Command{
Use: "stats",
Short: "Display detailed virtual machine statistics",
Long: "Display detailed statistics about the CRC virtual machine including OS, CPU, memory, disk, network, containers, and service health.",
RunE: func(_ *cobra.Command, _ []string) error {
return runStats(cfg, outputFormat)
},
}
cmd := &cobra.Command{
Use: "stats",
Short: "Display detailed virtual machine statistics",
Long: "Display detailed statistics about the CRC virtual machine including OS, CPU, memory, disk, network, containers, and service health.",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
return runStats(cfg, outputFormat)
},
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/crc/cmd/vm/stats.go` around lines 25 - 32, The command's RunE ignores
positional args so unexpected arguments (e.g. "crc vm stats typo") are currently
accepted; update the cobra command definition (the cmd variable constructed in
the stats command) to set Args: cobra.NoArgs so Cobra validates and rejects
unexpected positional arguments before calling RunE/runStats, ensuring the
command fails fast on extra args.

Comment on lines +129 to +166
func runStats(cfg *config.Config, outputFormat string) error {
client := machine.NewSynchronizedMachine(machine.NewClient(constants.DefaultName, logging.IsDebug(), cfg))
exists, err := client.Exists()
if err != nil {
return err
}
if !exists {
return crcErr.VMNotExist
}

details, err := client.ConnectionDetails()
if err != nil {
return err
}

runner, err := ssh.CreateRunner(details.IP, details.SSHPort, details.SSHKeys...)
if err != nil {
return fmt.Errorf("cannot create SSH connection: %w", err)
}
defer runner.Close()

stats, err := collectStats(runner)
if err != nil {
return err
}
stats.Network.NodeIP = details.IP

switch outputFormat {
case "json":
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
return encoder.Encode(stats)
case "":
printStats(stats)
return nil
default:
return fmt.Errorf("invalid output format: %s", outputFormat)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Validate --output before touching the VM.

Line 156 is the first time outputFormat is checked, after Line 150 has already done the SSH round-trip. A typo like -o jsno should fail immediately instead of opening the VM connection and potentially returning an unrelated SSH error.

Suggested patch
 func runStats(cfg *config.Config, outputFormat string) error {
+	switch outputFormat {
+	case "", "json":
+	default:
+		return fmt.Errorf("invalid output format %q", outputFormat)
+	}
+
 	client := machine.NewSynchronizedMachine(machine.NewClient(constants.DefaultName, logging.IsDebug(), cfg))
 	exists, err := client.Exists()
 	if err != nil {
@@
-	switch outputFormat {
-	case "json":
-		encoder := json.NewEncoder(os.Stdout)
-		encoder.SetIndent("", "  ")
-		return encoder.Encode(stats)
-	case "":
-		printStats(stats)
-		return nil
-	default:
-		return fmt.Errorf("invalid output format: %s", outputFormat)
-	}
+	if outputFormat == "json" {
+		encoder := json.NewEncoder(os.Stdout)
+		encoder.SetIndent("", "  ")
+		return encoder.Encode(stats)
+	}
+	printStats(stats)
+	return nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/crc/cmd/vm/stats.go` around lines 129 - 166, The outputFormat validation
is performed too late in runStats (after creating the VM client and establishing
SSH), so typos like "-o jsno" open the VM unnecessarily; validate outputFormat
(accepting "json" or empty string) at the start of runStats and return an error
for invalid values before calling machine.NewSynchronizedMachine, client.Exists,
ssh.CreateRunner, or collectStats; update runStats to perform this early check
(or factor a helper validateOutputFormat(outputFormat)) so no VM/SSH work
happens for an invalid --output.

Comment on lines +190 to +192
`echo "===NET==="`,
`ip -br addr show br-ex 2>/dev/null | awk '{print $3}' | cut -d/ -f1`,
`awk '/^nameserver/{print $2; exit}' /etc/resolv.conf`,
Copy link

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 let one blank probe erase the rest of the section.

Line 350 drops empty lines, so if Line 191 returns nothing, the DNS value from Line 192 becomes the only NET entry and Line 277 discards the entire section. The same position-based format is fragile anywhere one probe can legitimately emit no output. Emitting key=value pairs per field would keep unrelated metrics from disappearing when one command is blank.

Illustrative direction
-		`echo "===NET==="`,
-		`ip -br addr show br-ex 2>/dev/null | awk '{print $3}' | cut -d/ -f1`,
-		`awk '/^nameserver/{print $2; exit}' /etc/resolv.conf`,
+		`echo "===NET==="`,
+		`printf 'cluster_ip=%s\n' "$(ip -br addr show br-ex 2>/dev/null | awk '{print $3}' | cut -d/ -f1)"`,
+		`printf 'dns_server=%s\n' "$(awk '/^nameserver/{print $2; exit}' /etc/resolv.conf)"`,
-	if lines, ok := sections["NET"]; ok && len(lines) >= 2 {
-		stats.Network.ClusterIP = strings.TrimSpace(lines[0])
-		stats.Network.DNSServer = strings.TrimSpace(lines[1])
-	}
+	for _, line := range sections["NET"] {
+		key, value, ok := strings.Cut(line, "=")
+		if !ok {
+			continue
+		}
+		switch key {
+		case "cluster_ip":
+			stats.Network.ClusterIP = strings.TrimSpace(value)
+		case "dns_server":
+			stats.Network.DNSServer = strings.TrimSpace(value)
+		}
+	}

Also applies to: 277-280, 344-352

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

In `@cmd/crc/cmd/vm/stats.go` around lines 190 - 192, The NET probe emits plain
positional lines (echo "===NET===", the `ip -br addr show br-ex ...` call, and
the `awk '/^nameserver/{print $2; exit}' /etc/resolv.conf` call) so a blank
output from one command can shift/drop other metrics when later code trims empty
lines and discards sections; change the probe to emit key=value pairs for each
metric (e.g., "bridge_ip=<value>" and "dns=<value>" — emit an empty value like
"dns=" when nothing found) and update the downstream trimming logic that
currently removes blank lines/discards whole sections to preserve lines with
empty values rather than deleting the whole NET section. Ensure the emitters and
the consumer use the key names consistently so blank probes no longer erase
unrelated metrics.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants