Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 2 additions & 2 deletions pkg/cmd/clipboard/clipboard_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package clipboard
import (
"context"
"fmt"
"io/ioutil"
"io"
"net/http"
"strings"

Expand Down Expand Up @@ -56,7 +56,7 @@ func (server *Server) Run() {
r := gin.New()

r.GET("/", func(c *gin.Context) {
jsonData, err := ioutil.ReadAll(c.Request.Body)
jsonData, err := io.ReadAll(c.Request.Body)
if err != nil {
// Handle error
c.String(http.StatusBadRequest, "Can't parse body")
Expand Down
1 change: 0 additions & 1 deletion pkg/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,6 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor
cmd.AddCommand(secret.NewCmdSecret(loginCmdStore, t))
cmd.AddCommand(sshkeys.NewCmdSSHKeys(t, loginCmdStore))
cmd.AddCommand(start.NewCmdStart(t, loginCmdStore, noLoginCmdStore))
cmd.AddCommand(start.NewCmdStart(t, loginCmdStore, noLoginCmdStore))
cmd.AddCommand(create.NewCmdCreate(t, loginCmdStore))
cmd.AddCommand(stop.NewCmdStop(t, loginCmdStore, noLoginCmdStore))
cmd.AddCommand(delete.NewCmdDelete(t, loginCmdStore, noLoginCmdStore))
Expand Down
3 changes: 1 addition & 2 deletions pkg/cmd/importideconfig/importideconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package importideconfig
import (
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
Expand Down Expand Up @@ -190,7 +189,7 @@ func getExtensions(homedir string) ([]entity.VscodeExtensionMetadata, error) {
func recursivelyFindFile(filenames []string, path string) ([]string, error) {
var paths []string

files, err := ioutil.ReadDir(path)
files, err := os.ReadDir(path)
if err != nil {
return nil, breverrors.WrapAndTrace(err)
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/cmd/paths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package paths

import (
"fmt"
"io/ioutil"
"os"
// breverrors "github.com/brevdev/brev-cli/pkg/errors"
)

func GetVsCodePaths() []string {
fi, err := ioutil.ReadDir("/home/brev/.vscode-server/bin")
fi, err := os.ReadDir("/home/brev/.vscode-server/bin")
if err != nil {
return []string{}
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package files
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -143,7 +143,7 @@ func ReadString(fs afero.Fs, unsafeFilePathString string) (string, error) {
return "", breverrors.WrapAndTrace(err)
}

dataBytes, err := ioutil.ReadAll(f)
dataBytes, err := io.ReadAll(f)
if err != nil {
return "", breverrors.WrapAndTrace(err)
}
Expand Down Expand Up @@ -186,7 +186,7 @@ func OverwriteJSON(fs afero.Fs, filepath string, v interface{}) error {
if err != nil {
return breverrors.WrapAndTrace(err)
}
err = ioutil.WriteFile(filepath, dataBytes, os.ModePerm)
err = os.WriteFile(filepath, dataBytes, os.ModePerm)
if err != nil {
return breverrors.WrapAndTrace(err)
}
Expand Down
3 changes: 1 addition & 2 deletions pkg/huproxyclient/huproxyclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"time"
Expand All @@ -30,7 +29,7 @@ type HubProxyStore interface {
func dialError(url string, resp *http.Response, err error) {
if resp != nil {
extra := ""
b, err1 := ioutil.ReadAll(resp.Body)
b, err1 := io.ReadAll(resp.Body)
if err1 != nil {
log.Warningf("Failed to read HTTP body: %v", err1)
}
Expand Down
226 changes: 220 additions & 6 deletions pkg/integration/cli_output_compatibility_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,45 @@
// These tests verify CLI output formats that external integrations depend on.
// Breaking these tests indicates a breaking change that could affect external tools
// like NVIDIA Workbench that parse brev CLI output.
//
// NVIDIA AI Workbench Dependencies (CRITICAL - DO NOT BREAK):
// ============================================================
// Workbench uses the Brev CLI for managing compute instances and depends on:
//
// 1. Commands that MUST exist and maintain their behavior:
// - brev -h / brev --help (to check CLI exists)
// - brev --version (returns X.Y.Z format, with minimum version 0.6.306)
// - brev refresh (refreshes SSH configuration)
// - brev org set <org> (sets active organization)
// - brev org ls (lists organizations with NAME and ID columns)
// - brev ls (lists instances with NAME, STATUS, ID columns)
// - brev ls --org <org> (lists instances for specific org)
// - brev start [url] --org <org> (starts/creates workspace)
// - brev stop <name> (stops workspace)
//
// 2. Output formats that MUST remain stable:
// - brev ls: Table format with columns (in order): NAME, STATUS, ID, MACHINE
// * Workbench has a custom parser that uses column headers to identify cells
// * New columns can be ADDED but existing columns cannot be REMOVED or RENAMED
// * Column order should be maintained for reliable parsing
// - brev --version: Must contain "Current Version:" or "Current version:"
// followed by semantic version X.Y.Z that can be parsed with regex
// - brev org ls: Table format with NAME and ID columns
// * Current org marked with "*" prefix
//
// 3. API endpoints that Workbench depends on:
// - https://api.brev.dev/v1/instance/types
// * Required fields: "type" (string), "stoppable" (boolean)
// * DO NOT remove or rename these fields
//
// 4. Authentication integration:
// - Workbench implements custom KAS login flow that integrates with Brev credentials
// - Credential management must remain compatible with Brev's token storage
//
// References:
// - Workbench CLI integration: https://gitlab-master.nvidia.com/workbench/workbench-cli/-/blob/main/pkg/brev/cli.go
// - Credential manager: https://gitlab-master.nvidia.com/workbench/credential-manager/-/blob/main/pkg/integrations/brev.go
// - Documentation: /Users/kejones/Git/brevdev/notes/brev-cli/workbench-dependencies.md

const (
// Regular expressions
Expand Down Expand Up @@ -198,16 +237,20 @@
}

// Test_StartCommandFormat verifies that 'brev start' command accepts --org flag
// CRITICAL: Workbench requires the --org flag to specify organization for workspace creation
func Test_StartCommandFormat(t *testing.T) {
cmd := exec.Command("go", "run", brevCLIPath, "start", "--help")
output, _ := cmd.CombinedOutput()
output, err := cmd.CombinedOutput()
require.NoError(t, err, "brev start --help should succeed")

outputStr := string(output)
// Should mention org flag or organization, or at least not be unknown command
hasOrgSupport := strings.Contains(outputStr, "--org") ||
strings.Contains(outputStr, "organization") ||
(strings.Contains(outputStr, "start") && !strings.Contains(outputStr, "unknown command"))
assert.True(t, hasOrgSupport, "start command should exist and potentially support org specification")

// Verify the command exists
assert.Contains(t, outputStr, "start", "start command should exist")
assert.NotContains(t, outputStr, "unknown command", "start should be a valid command")

// CRITICAL: Verify --org flag is documented and available
assert.Contains(t, outputStr, "--org", "start command MUST support --org flag for Workbench compatibility")
}

// Test_StopCommandExists verifies that 'brev stop' command exists
Expand Down Expand Up @@ -316,3 +359,174 @@
}
return false
}

// Test_ListWithOrgFlag verifies that 'brev ls --org' flag is recognized
// CRITICAL: Workbench uses this to list instances for specific organizations
func Test_ListWithOrgFlag(t *testing.T) {
cmd := exec.Command("go", "run", brevCLIPath, "ls", "--help")
output, err := cmd.CombinedOutput()
require.NoError(t, err, "brev ls --help should succeed")

outputStr := string(output)

// CRITICAL: Verify --org flag exists for ls command
assert.Contains(t, outputStr, "--org", "ls command MUST support --org flag for Workbench compatibility")

// Verify the flag description mentions organization
lines := strings.Split(outputStr, "\n")
foundOrgLine := false
for _, line := range lines {
if strings.Contains(line, "--org") {
foundOrgLine = true
// The line should mention organization or org
assert.True(t,
strings.Contains(strings.ToLower(line), "org") ||
strings.Contains(strings.ToLower(line), "organization"),
"--org flag should have documentation mentioning organization")
break
}
}
assert.True(t, foundOrgLine, "--org flag should be documented in help output")
}

// Test_ShortHelpFlag verifies that 'brev -h' works
// CRITICAL: Workbench uses 'brev -h' to check if CLI exists
func Test_ShortHelpFlag(t *testing.T) {
cmd := exec.Command("go", "run", brevCLIPath, "-h")
output, err := cmd.CombinedOutput()
require.NoError(t, err, "brev -h should succeed")

outputStr := string(output)

// Should show help text
assert.Contains(t, outputStr, "brev", "Help should mention brev")
assert.Contains(t, outputStr, "Usage", "Help should show usage information")

// Should list essential commands
essentialCommands := []string{"ls", "start", "stop", "org"}
for _, cmd := range essentialCommands {
assert.Contains(t, outputStr, cmd, "Help should list essential command: %s", cmd)
}
}

// Test_VersionWithNoCheckLatestFlag verifies version flag variations
// CRITICAL: Ensures version command works with various flags Workbench might use
func Test_VersionWithNoCheckLatestFlag(t *testing.T) {
cmd := exec.Command("go", "run", brevCLIPath, "--version", "--no-check-latest")
output, err := cmd.CombinedOutput()
require.NoError(t, err, "brev --version --no-check-latest should succeed")

outputStr := string(output)

// Should still show version information (even if it's dev-XXXXXXXX format)
// The important thing is the command doesn't crash or fail
assert.NotEmpty(t, outputStr, "Version command should produce output")

// For production builds, should contain version information
versionRegexp := regexp.MustCompile(versionPattern)
matches := versionRegexp.FindAllString(outputStr, -1)

// If we find a semver version, validate it
if len(matches) > 0 {
versionStr := matches[0]
versionParts := strings.Split(versionStr, ".")
assert.Len(t, versionParts, 3, "Version should have exactly 3 components")
} else {
// Dev builds may have "dev-XXXXXXXX" format, which is acceptable
t.Log("Version is in dev format (not semver), which is acceptable for development builds")
}
}

// Test_InstanceListColumnHeadersStability verifies ALL required columns exist
// CRITICAL: Workbench parser depends on these exact column headers
func Test_InstanceListColumnHeadersStability(t *testing.T) {
cmd := exec.Command("go", "run", brevCLIPath, "ls", "--help")
output, _ := cmd.CombinedOutput()

// Verify ls command exists
outputStr := string(output)
assert.NotContains(t, outputStr, "unknown command", "ls command must exist")

// Run actual ls command (may skip if auth fails)
cmd = exec.Command("go", "run", brevCLIPath, "ls")
output, err := cmd.CombinedOutput()
if err != nil {
t.Skip("ls command requires authentication")
return
}

outputStr = string(output)

// If there are no instances, the output won't have column headers (which is correct behavior)
if strings.Contains(outputStr, "No instances") {
t.Log("✅ No instances present, skipping column header validation (headers only shown when data exists)")
return
}

// CRITICAL: These columns MUST exist when instances are present - Workbench parser depends on them
requiredColumns := []string{"NAME", "STATUS", "ID"}
for _, col := range requiredColumns {
assert.Contains(t, outputStr, col,
"CRITICAL: '%s' column MUST exist for Workbench compatibility. DO NOT REMOVE OR RENAME.", col)
}

// Note: Additional columns can be added, but these core columns must remain
t.Log("✅ All required column headers present. New columns can be added, but existing ones MUST NOT be removed or renamed.")
}

// Test_OrgListColumnHeadersStability verifies org list column headers
// CRITICAL: Ensures org list output format remains stable for Workbench
func Test_OrgListColumnHeadersStability(t *testing.T) {
cmd := exec.Command("go", "run", brevCLIPath, "org", "ls")
output, err := cmd.CombinedOutput()
if err != nil {
t.Skip("org ls requires authentication")
return
}

outputStr := string(output)

// CRITICAL: These columns MUST exist for Workbench
requiredColumns := []string{"NAME", "ID"}
for _, col := range requiredColumns {
assert.Contains(t, outputStr, col,
"CRITICAL: '%s' column MUST exist in org ls for Workbench compatibility.", col)
}
}

// Test_CommandExistenceForWorkbench verifies all Workbench-critical commands exist
// CRITICAL: Comprehensive check that all commands Workbench depends on are available
func Test_CommandExistenceForWorkbench(t *testing.T) {
criticalCommands := []struct {
name string
args []string
description string
}{
{"help_short", []string{"-h"}, "check CLI exists"},
{"help_long", []string{"--help"}, "show full help"},
{"version", []string{"--version"}, "get version"},
{"ls", []string{"ls", "--help"}, "list instances"},
{"org_ls", []string{"org", "ls", "--help"}, "list organizations"},
{"org_set", []string{"org", "set", "--help"}, "set active org"},
{"start", []string{"start", "--help"}, "start/create workspace"},
{"stop", []string{"stop", "--help"}, "stop workspace"},
{"refresh", []string{"refresh", "--help"}, "refresh SSH config"},
}

for _, cmd := range criticalCommands {
t.Run(cmd.name, func(t *testing.T) {
execCmd := exec.Command("go", append([]string{"run", brevCLIPath}, cmd.args...)...)

Check failure on line 518 in pkg/integration/cli_output_compatibility_test.go

View workflow job for this annotation

GitHub Actions / ci (ubuntu-22.04)

G204: Subprocess launched with a potential tainted input or cmd arguments (gosec)

Check failure on line 518 in pkg/integration/cli_output_compatibility_test.go

View workflow job for this annotation

GitHub Actions / ci (ubuntu-22.04)

G204: Subprocess launched with a potential tainted input or cmd arguments (gosec)
output, err := execCmd.CombinedOutput()

// Command should execute (may fail with auth, but shouldn't be "unknown command")
outputStr := string(output)
assert.NotContains(t, outputStr, "unknown command",
"CRITICAL: Command '%s' MUST exist for Workbench (%s)", cmd.name, cmd.description)

// For help commands, should succeed
if strings.Contains(cmd.name, "help") || strings.HasSuffix(cmd.args[len(cmd.args)-1], "--help") {
assert.NoError(t, err, "Help command should succeed: %s", cmd.name)
}
})
}
}
6 changes: 3 additions & 3 deletions pkg/mergeshells/mergeshells.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"embed"
"errors"
"fmt"
"io/ioutil"
"io"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -336,7 +336,7 @@ func importFile(nameVersion string) ([]ShellFragment, error) {
return []ShellFragment{}, errors.New(strings.Join([]string{"Path does not exist:", path}, " "))
}
}
out, err := ioutil.ReadAll(script)
out, err := io.ReadAll(script)
stringScript := string(out)
if !noversion {
stringScript = strings.ReplaceAll(stringScript, "${version}", subPaths[1])
Expand Down Expand Up @@ -556,7 +556,7 @@ func appendPath(a string, b string) string {
func recursivelyFindFile(filenames []string, path string) []string {
var paths []string

files, err := ioutil.ReadDir(path)
files, err := os.ReadDir(path)
if err != nil {
fmt.Println(err)
}
Expand Down
Loading
Loading