feat(cli): complete CLI with full CRUD operations (#563)#621
feat(cli): complete CLI with full CRUD operations (#563)#621ArangoGutierrez wants to merge 13 commits intoNVIDIA:mainfrom
Conversation
| ssh.PublicKeys(signer), | ||
| }, | ||
| // Holodeck instances are ephemeral with no pre-established host keys | ||
| HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec |
Check failure
Code scanning / CodeQL
Use of insecure HostKeyCallback implementation High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 1 hour ago
In general, to fix this problem you need to replace ssh.InsecureIgnoreHostKey() with a HostKeyCallback implementation that validates the server’s host key against an allow list (for example, keys loaded from a known_hosts-style file or a single pinned key using ssh.FixedHostKey). If the server presents a key that is not on the allow list, the callback must return an error so the connection fails.
For this specific function, the least invasive fix that preserves behavior for callers is to introduce a small helper that loads allowed host keys from a file path provided via environment or a conventional location, parses them, and builds a HostKeyCallback that enforces this allow list. We then call this helper from ConnectSSH and set the resulting callback in ssh.ClientConfig. This keeps the signature of ConnectSSH unchanged and adds security without changing how callers invoke it. If no allowed host keys can be loaded, the helper will return an error and ConnectSSH will fail rather than silently disabling verification.
Concretely:
- In
cmd/cli/common/host.go, add imports forbufio,errors,net,strings, andgolang.org/x/crypto/ssh/knownhosts. - Above
ConnectSSH, define a helpergetHostKeyCallback()that:- Determines a known-hosts file path (for example from an environment variable
HOLODECK_SSH_KNOWN_HOSTS, falling back to$HOME/.ssh/known_hosts). - Uses
knownhosts.New(path)to construct assh.HostKeyCallback. - Wraps this callback in our own function that adds a clearer error message if validation fails.
- Determines a known-hosts file path (for example from an environment variable
- In
ConnectSSH, callgetHostKeyCallback()and assign the returned callback toHostKeyCallbackinstead ofssh.InsecureIgnoreHostKey().
This uses the standard golang.org/x/crypto/ssh/knownhosts helper rather than custom parsing, and relies on a conventional known_hosts file as the allow list, which is a well-understood and secure pattern.
Design for issue NVIDIA#563 covering: - New CLI commands (describe, get, ssh, scp, update) - Global verbosity flags (-q, -v, -d) - Output formatting infrastructure - Implementation tasks and testing strategy Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Detailed task-by-task implementation plan for: - Global verbosity flags (-q, -v, -d) - Unit tests for all new CLI commands - Output formatter tests 11 tasks with TDD approach and commit checkpoints. Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Add Verbosity type with four levels (Quiet, Normal, Verbose, Debug) to control log output. Info, Check, and Warning methods now respect verbosity settings, while Error always prints. New Debug and Trace methods provide additional logging granularity for troubleshooting. Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Add global flags for controlling output verbosity: - --quiet, -q: Suppress non-error output - --verbose: Enable verbose output - --debug, -d: Enable debug-level logging (also reads DEBUG env var) Flag precedence: --debug > --verbose > --quiet Note: -v was not used for --verbose to avoid conflict with built-in --version flag. Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Rename the --quiet flag to --ids-only to avoid conflict with the new global --quiet flag that controls verbosity. The --ids-only flag now only outputs instance IDs, one per line. Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Add comprehensive unit tests for the pkg/output package covering: - ValidFormats() and IsValidFormat() validation functions - NewFormatter() with empty, valid, and invalid format inputs - Formatter.Format() accessor method - PrintJSON() with struct, map, and slice data types - PrintYAML() with struct, map, and nested struct data - Print() dispatch logic for JSON, YAML, and table formats - PrintTable() with mock TableData interface - TablePrinter fluent interface (Header, Row, Flush) - SetWriter() for output redirection - Error message quality for invalid formats Achieves 97.9% code coverage. Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Adds pkg/output with Formatter, TablePrinter, and TableData interface for consistent output formatting across all CLI commands. Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Adds -o flag (json/yaml/table) to holodeck status with structured output types for JSON/YAML serialization. Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
New commands for issue NVIDIA#563: - describe: detailed instance introspection with JSON/YAML output - get kubeconfig: download kubeconfig from instance - get ssh-config: generate SSH config entry - ssh: SSH into instance or run remote commands - scp: copy files to/from instance via SFTP - update: update instance configuration (add components, labels) All commands include unit tests for core logic. Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Updates list_test.go to use --ids-only instead of the old --quiet/-q flag which was renamed to avoid conflict with the global -q flag. Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
C1: Add nil check for env.Labels before writing provisioned label
in update --reprovision. Previously panicked when no --label flags
were provided and the cached environment had no labels.
C2: Replace incorrect containsSpace+fmt.Sprintf("%q") quoting with
simple strings.Join. SSH session.Run always passes through the
remote shell, so Go-style quoting was wrong. Document this behavior.
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
- Extract getHostURL and connectSSH into cmd/cli/common package, eliminating duplication across ssh, scp, and get commands (I1) - Use path instead of filepath for remote SFTP paths to ensure correct POSIX separators on all platforms (I2) - Log warnings for skipped files during recursive remote copy (I3) - Use c.IsSet() for flags with defaults to prevent overwriting existing config with default values (I4) Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
ef73606 to
fb19ac0
Compare
Pull Request Test Coverage Report for Build 21747509586Details
💛 - Coveralls |
There was a problem hiding this comment.
Pull request overview
This PR implements a comprehensive set of CLI CRUD operations to complete the Holodeck CLI (#563), adding essential features for managing GPU-ready cloud environments. The changes introduce global verbosity levels, structured output formatting, and critical SSH/file transfer capabilities.
Changes:
- Added logger verbosity system (Quiet/Normal/Verbose/Debug) with corresponding flags (-q, --verbose, -d)
- Created
pkg/outputpackage for consistent JSON/YAML/table formatting across commands - Implemented six new commands: describe, get (kubeconfig/ssh-config), ssh, scp, and update
- Refactored
listcommand to support output formats and renamed--quietto--ids-only - Created shared
cmd/cli/commonpackage withGetHostURLandConnectSSHutilities
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/logger/logger.go | Added verbosity levels (Quiet/Normal/Verbose/Debug) with new Debug() and Trace() methods |
| internal/logger/logger_test.go | Comprehensive unit tests for verbosity filtering and methods |
| pkg/output/output.go | Formatter interface with table/JSON/YAML support and TableData interface |
| pkg/output/output_test.go | 732 lines of tests covering all output formats and edge cases |
| cmd/cli/common/host.go | Shared GetHostURL and ConnectSSH utilities (contains bug) |
| cmd/cli/describe/describe.go | Detailed instance introspection with multi-format output |
| cmd/cli/get/get.go | Kubeconfig download and SSH config generation subcommands |
| cmd/cli/ssh/ssh.go | Interactive SSH and remote command execution |
| cmd/cli/scp/scp.go | SFTP file transfer with recursive directory support, proper POSIX path handling |
| cmd/cli/update/update.go | Add components, labels, and re-provision instances (contains bug) |
| cmd/cli/list/list.go | Refactored with output formatting, renamed --quiet to --ids-only |
| cmd/cli/status/status.go | Added output formatting with --output flag |
| cmd/cli/main.go | Integrated global verbosity flags and new commands |
| } | ||
| } | ||
| case v1alpha1.ProviderSSH: | ||
| return env.Spec.HostUrl, nil |
There was a problem hiding this comment.
The field path should be env.Spec.Instance.HostUrl not env.Spec.HostUrl. The HostUrl field is defined on the Instance struct, not directly on EnvironmentSpec. This will cause a compilation error or runtime panic.
| return env.Spec.HostUrl, nil | |
| if env.Spec.Instance != nil { | |
| return env.Spec.Instance.HostUrl, nil | |
| } |
| } | ||
| } | ||
| } else if env.Spec.Provider == v1alpha1.ProviderSSH { | ||
| hostUrl = env.Spec.HostUrl |
There was a problem hiding this comment.
The field path should be env.Spec.Instance.HostUrl not env.Spec.HostUrl. The HostUrl field is defined on the Instance struct, not directly on EnvironmentSpec. This will cause a compilation error or runtime panic.
| hostUrl = env.Spec.HostUrl | |
| if env.Spec.Instance == nil { | |
| return fmt.Errorf("instance spec is required for SSH provider") | |
| } | |
| hostUrl = env.Spec.Instance.HostUrl |
- Fix gofmt, gosec, staticcheck, unconvert, and errcheck lint issues - Promote gopkg.in/yaml.v3 from indirect to direct dependency - Remove planning documents (development artifacts) Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
5f98a33 to
8440213
Compare
Summary
Implements the complete CLI CRUD operations epic (#563) to unblock 11 dependent PRs. This adds:
-q,--verbose,-dflagspkg/outputpackagedescribe,get kubeconfig,get ssh-config,ssh,scp,updatelist: renamed--quietto--ids-onlyfor claritycmd/cli/commonpackage withGetHostURLandConnectSSH--outputflagKey changes
internal/logger/logger.go— verbosity levels (Quiet/Normal/Verbose/Debug)pkg/output/output.go— Formatter, TablePrinter, TableData interfacecmd/cli/common/host.go— shared host URL resolution and SSH connectioncmd/cli/describe/— detailed instance info with JSON/YAML/table outputcmd/cli/get/— kubeconfig download and SSH config generationcmd/cli/ssh/— interactive SSH and remote command executioncmd/cli/scp/— SFTP file transfer with recursive directory supportcmd/cli/update/— add components, labels, and re-provision instancesReview fixes included
--reprovisioncontainsSpacelogic)getHostURL/connectSSHinto sharedcommonpackagepath(POSIX) for remote SFTP paths instead offilepathc.IsSet()for flags with defaults to prevent config overwritesTest plan
go test ./cmd/cli/...)pkg/output)GetHostURLacross ssh, get, and common packagesparsePath,describe,update, and label handlingholodeck list,holodeck describe <id>,holodeck ssh <id>holodeck scp ./file <id>:/tmp/,holodeck get kubeconfig <id>holodeck update <id> --add-driver --driver-version 560.35.03Closes #563