Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/crc/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

cmdBundle "github.com/crc-org/crc/v2/cmd/crc/cmd/bundle"
cmdConfig "github.com/crc-org/crc/v2/cmd/crc/cmd/config"
cmdVM "github.com/crc-org/crc/v2/cmd/crc/cmd/vm"
crcConfig "github.com/crc-org/crc/v2/pkg/crc/config"
"github.com/crc-org/crc/v2/pkg/crc/constants"
crcErr "github.com/crc-org/crc/v2/pkg/crc/errors"
Expand Down Expand Up @@ -71,6 +72,7 @@ func init() {
// subcommands
rootCmd.AddCommand(cmdConfig.GetConfigCmd(config))
rootCmd.AddCommand(cmdBundle.GetBundleCmd(config))
rootCmd.AddCommand(cmdVM.GetVMCmd(config))

logging.AddLogLevelFlag(rootCmd.PersistentFlags())
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/crc/cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,13 @@ func TestCrcManPageGenerator_WhenInvoked_GeneratesManPagesForAllCrcSubCommands(t
"crc-oc-env.1",
"crc-podman-env.1",
"crc-setup.1",
"crc-ssh.1",
"crc-start.1",
"crc-status.1",
"crc-stop.1",
"crc-version.1",
"crc-vm-stats.1",
"crc-vm.1",
"crc.1",
}, manPagesFiles)
}
73 changes: 73 additions & 0 deletions cmd/crc/cmd/ssh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package cmd

import (
"fmt"
"os"
"os/exec"
"strconv"
"syscall"

"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(sshCmd)
}

var sshCmd = &cobra.Command{
Use: "ssh [-- COMMAND...]",
Short: "Open an SSH connection to the OpenShift cluster node",
Long: "Open an SSH connection to the OpenShift cluster node. Pass commands after -- to execute them remotely.",
RunE: func(_ *cobra.Command, args []string) error {
return runSSH(args)
},
}

func runSSH(args []string) error {
client := newMachine()
if err := checkIfMachineMissing(client); err != nil {
return err
}

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

sshPath, err := exec.LookPath("ssh")
if err != nil {
return fmt.Errorf("cannot find ssh binary: %w", err)
}

var sshKey string
for _, key := range connectionDetails.SSHKeys {
if _, err := os.Stat(key); err == nil {
sshKey = key
break
}
}
if sshKey == "" {
return fmt.Errorf("no SSH key found")
}

sshArgs := []string{
"ssh",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "LogLevel=ERROR",
"-i", sshKey,
"-p", strconv.Itoa(connectionDetails.SSHPort),
fmt.Sprintf("%s@%s", connectionDetails.SSHUsername, connectionDetails.IP),
}

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()
}
Comment on lines +63 to +70
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 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.

}
Loading