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
7 changes: 4 additions & 3 deletions src/cli/Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,21 @@ tasks:
build:
desc: "Build d8v cli"
cmds:
- go build -o .out/d8v cmd/main.go
- echo Build binary ./d8v ...
- go build -o ./d8v cmd/main.go
install:
desc: "Install d8v cli to ~/.local/bin"
deps: [build]
cmds:
- echo "Check that ~/.local/bin in your PATH"
- echo "Installing d8v to ~/.local/bin"
- mkdir -p ~/.local/bin
- cp .out/d8v ~/.local/bin/d8v
- cp ./d8v ~/.local/bin/d8v
- task: clean
clean:
desc: "Clean up build artifacts"
cmds:
- rm -rf .out
- test -f ./d8v && rm -rf ./d8v

lint:
desc: "Run linters locally"
Expand Down
12 changes: 9 additions & 3 deletions src/cli/internal/cmd/scp/scp.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@ func NewCommand() *cobra.Command {
c.options.LocalClientName = "scp"

cmd := &cobra.Command{
Use: "scp VirtualMachine)",
Short: "SCP files from/to a virtual machine.",
Use: "scp [flags] SOURCE TARGET",
Short: "Secure file CoPy from/to a virtual machine.",
Example: usage(),
Args: templates.ExactArgs("scp", 2),
RunE: c.Run,
}

ssh.AddCommandlineArgs(cmd.Flags(), &c.options)
ssh.AddCommonSSHFlags(cmd.Flags(), &c.options)

cmd.Flags().BoolVarP(&c.recursive, recursiveFlag, recursiveFlagShort, c.recursive,
"Recursively copy entire directories")
cmd.Flags().BoolVar(&c.preserve, preserveFlag, c.preserve,
Expand All @@ -62,6 +63,11 @@ type SCP struct {
}

func (o *SCP) Run(cmd *cobra.Command, args []string) error {
err := o.options.ResolvePaths()
if err != nil {
return err
}

client, defaultNamespace, _, err := clientconfig.ClientAndNamespaceFromContext(cmd.Context())
if err != nil {
return err
Expand Down
102 changes: 71 additions & 31 deletions src/cli/internal/cmd/ssh/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/klog/v2"

"github.com/deckhouse/virtualization/src/cli/internal/clientconfig"
"github.com/deckhouse/virtualization/src/cli/internal/templates"
Expand Down Expand Up @@ -62,28 +62,51 @@ type SSHOptions struct {
}

func DefaultSSHOptions() SSHOptions {
homeDir, err := os.UserHomeDir()
if err != nil {
klog.Warningf("failed to determine user home directory: %v", err)
}
options := SSHOptions{
SSHPort: 22,
SSHUsername: defaultUsername(),
IdentityFilePath: filepath.Join(homeDir, ".ssh", "id_rsa"),
IdentityFilePath: filepath.Join("~", ".ssh", "id_rsa"),
IdentityFilePathProvided: false,
KnownHostsFilePath: "",
KnownHostsFilePathDefault: "",
KnownHostsFilePathDefault: filepath.Join("~", ".ssh", KnownHostsFileName),
AdditionalSSHLocalOptions: []string{},
WrapLocalSSH: wrapLocalSSHDefault,
LocalClientName: "ssh",
}

if len(homeDir) > 0 {
options.KnownHostsFilePathDefault = filepath.Join(homeDir, ".ssh", KnownHostsFileName)
}
return options
}

func (s *SSHOptions) ResolvePaths() error {
if s.IdentityFilePath != "" {
resolvedPath, err := resolveHomeDir(s.IdentityFilePath)
if err != nil {
return fmt.Errorf("resolve identity file path '%s': %w", s.IdentityFilePath, err)
}
s.IdentityFilePath = resolvedPath
}
if s.KnownHostsFilePath != "" {
resolvedPath, err := resolveHomeDir(s.KnownHostsFilePath)
if err != nil {
return fmt.Errorf("resolve known hosts file path '%s': %w", s.KnownHostsFilePath, err)
}
s.KnownHostsFilePath = resolvedPath
}
return nil
}

// resolveHomeDir substitutes beginning '~' with home dir path.
func resolveHomeDir(path string) (string, error) {
if !strings.HasPrefix(path, "~") {
return path, nil
}
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("get user home directory: %w", err)
}
return filepath.Join(homeDir, strings.TrimPrefix(path, "~")), nil
}

func defaultUsername() string {
vars := []string{
"USER", // linux
Expand All @@ -104,34 +127,40 @@ func NewCommand() *cobra.Command {
}

cmd := &cobra.Command{
Use: "ssh VirtualMachine",
Use: "ssh [-n|--namespace NAMESPACE] VIRTUAL-MACHINE-NAME",
Short: "Open a SSH connection to a virtual machine.",
Example: usage(),
Args: templates.ExactArgs("ssh", 1),
RunE: c.Run,
}

AddCommandlineArgs(cmd.Flags(), &c.options)
AddCommonSSHFlags(cmd.Flags(), &c.options)

cmd.Flags().StringVarP(&c.options.SSHUsername, usernameFlag, usernameFlagShort, c.options.SSHUsername,
"Specify user to log into virtual machine; If unassigned, this will be empty and the SSH default will apply")
cmd.Flags().StringVarP(&c.command, commandToExecute, commandToExecuteShort, c.command,
fmt.Sprintf(`--%s='ls /': Specify a command to execute in the VM`, commandToExecute))
"Specify a command to execute in the VM.")
cmd.SetUsageTemplate(templates.UsageTemplate())
return cmd
}

func AddCommandlineArgs(flagset *pflag.FlagSet, opts *SSHOptions) {
flagset.StringVarP(&opts.SSHUsername, usernameFlag, usernameFlagShort, opts.SSHUsername,
fmt.Sprintf("--%s=%s: Set this to the user you want to open the SSH connection as; If unassigned, this will be empty and the SSH default will apply", usernameFlag, opts.SSHUsername))
func AddCommonSSHFlags(flagset *pflag.FlagSet, opts *SSHOptions) {
flagset.StringVarP(&opts.IdentityFilePath, IdentityFilePathFlag, identityFilePathFlagShort, opts.IdentityFilePath,
fmt.Sprintf("--%s=/home/user/.ssh/id_rsa: Set the path to a private key used for authenticating to the server; If not provided, the client will try to use the local ssh-agent at $SSH_AUTH_SOCK", IdentityFilePathFlag))
"Specify a path to a private key used for authenticating to the server; If not provided, the client will try to use the local ssh-agent at $SSH_AUTH_SOCK")
flagset.StringVar(&opts.KnownHostsFilePath, knownHostsFilePathFlag, opts.KnownHostsFilePathDefault,
fmt.Sprintf("--%s=/home/user/.ssh/%s: Set the path to the known_hosts file.", KnownHostsFileName, knownHostsFilePathFlag))
"Set a path to the known_hosts file.")
flagset.IntVarP(&opts.SSHPort, portFlag, portFlagShort, opts.SSHPort,
fmt.Sprintf(`--%s=22: Specify a port on the VM to send SSH traffic to`, portFlag))
`Specify a port to connect to`)

addAdditionalCommandlineArgs(flagset, opts)
addLocalSSHClientFlags(flagset, opts)
}

func (o *SSH) Run(cmd *cobra.Command, args []string) error {
err := o.options.ResolvePaths()
if err != nil {
return err
}

client, defaultNamespace, _, err := clientconfig.ClientAndNamespaceFromContext(cmd.Context())
if err != nil {
return err
Expand Down Expand Up @@ -168,19 +197,30 @@ func PrepareCommand(cmd *cobra.Command, defaultNamespace string, opts *SSHOption
}

func usage() string {
return fmt.Sprintf(` # Connect to 'myvm':
{{ProgramName}} ssh user@myvm [--%s]
return fmt.Sprintf(` # Connect to virtualMachine 'myvm' in 'vms' namespace:
{{ProgramName}} ssh user@myvm.vms

# Specify namespace and user with flags:
{{ProgramName}} ssh --namespace=vms --%s=user myvm

# Specify identity file:
{{ProgramName}} ssh -n vms user@myvm -%s /some/path/id_rsa_for_myvm

# Connect to 'myvm' in 'mynamespace' namespace
{{ProgramName}} ssh user@myvm.mynamespace [--%s]
# Run command instead of opening shell:
{{ProgramName}} ssh -n vms user@myvm -%s 'ls -la /'

# Specify a username and namespace:
{{ProgramName}} ssh --namespace=mynamespace --%s=user myvm
# Connect using the local ssh binary found in $PATH:
{{ProgramName}} ssh --%s=true user@myvm

# Connect to 'myvm' using the local ssh binary found in $PATH:
{{ProgramName}} ssh --%s=true user@myvm`,
IdentityFilePathFlag,
IdentityFilePathFlag,
# Specify additional options for local ssh:
{{ProgramName}} ssh user@myvm --%s=true --%s='-o StrictHostKeyChecking=no' --%s='-o UserKnownHostsFile=/dev/null'
`,
usernameFlag,
wrapLocalSSHFlag)
identityFilePathFlagShort,
commandToExecuteShort,
wrapLocalSSHFlag,
wrapLocalSSHFlag,
additionalOpts,
additionalOpts,
)
}
6 changes: 3 additions & 3 deletions src/cli/internal/cmd/ssh/wrapped.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ import (
"k8s.io/klog/v2"
)

func addAdditionalCommandlineArgs(flagset *pflag.FlagSet, opts *SSHOptions) {
func addLocalSSHClientFlags(flagset *pflag.FlagSet, opts *SSHOptions) {
flagset.StringArrayVarP(&opts.AdditionalSSHLocalOptions, additionalOpts, additionalOptsShort, opts.AdditionalSSHLocalOptions,
fmt.Sprintf(`--%s="-o StrictHostKeyChecking=no" : Additional options to be passed to the local ssh. This is applied only if local-ssh=true`, additionalOpts))
"Additional options to be passed to the ssh client if --local-ssh=true is set")
flagset.BoolVar(&opts.WrapLocalSSH, wrapLocalSSHFlag, opts.WrapLocalSSH,
fmt.Sprintf("--%s=true: Set this to true to use the SSH/SCP client available on your system by using this command as ProxyCommand; If set to false, this will establish a SSH/SCP connection with limited capabilities provided by this client", wrapLocalSSHFlag))
"Use the SSH/SCP client available on your system by using this command as ProxyCommand; Default is false: use embedded SSH client with limited capabilities")
}

func RunLocalClient(cmd *cobra.Command, namespace, name string, options *SSHOptions, clientArgs []string) error {
Expand Down
8 changes: 7 additions & 1 deletion src/cli/internal/templates/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@ Use "{{ProgramName}} options" for a list of global command-line options (applies

// MainUsageTemplate returns the usage template for the root command
func MainUsageTemplate() string {
return `Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
return `Usage:{{if .Runnable}}
{{prepare .UseLine}}{{end}}{{if gt (len .Aliases) 0}}

Aliases:
{{.NameAndAliases}}{{end}}

Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
{{rpad .Name .NamePadding }} {{prepare .Short}}{{end}}{{end}}

Use "{{ProgramName}} <command> --help" for more information about a given command.
Expand Down
8 changes: 6 additions & 2 deletions src/cli/pkg/command/virtualization.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package command

import (
"context"
"fmt"
"os"
"os/signal"
"strings"
Expand All @@ -43,6 +44,9 @@ import (
"github.com/deckhouse/virtualization/src/cli/internal/templates"
)

// NewCommand defines command and flags configuration for cobra.
// Warning: d8 cli calls all configurations for all commands, so
// do not print warnings or errors here, postpone such actions to runtime (RunE).
func NewCommand(programName string) *cobra.Command {
// programName used in cobra templates to display either `d8 virtualization` or `d8vctl`
cobra.AddTemplateFunc(
Expand All @@ -60,8 +64,8 @@ func NewCommand(programName string) *cobra.Command {
)

virtCmd := &cobra.Command{
Use: programName,
Short: programName + " controls virtual machine related operations on your kubernetes cluster.",
Use: fmt.Sprintf("%s command [options]", programName),
Short: "Commands to work with Deckhouse Virtualization Platform.",
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down
Loading