• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubevirt / kubevirt / 9c26b0b7-0f50-47e5-9262-db26cb953f93

30 May 2025 10:58PM UTC coverage: 71.006% (-0.002%) from 71.008%
9c26b0b7-0f50-47e5-9262-db26cb953f93

push

prow

web-flow
Merge pull request #14828 from rmohr/ssh-command

[virtctl] Properly pick up cobra onfigured streams in ssh command

0 of 8 new or added lines in 2 files covered. (0.0%)

64695 of 91112 relevant lines covered (71.01%)

0.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.23
/pkg/virtctl/ssh/ssh.go
1
/*
2
 * This file is part of the KubeVirt project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 *
16
 * Copyright The KubeVirt Authors.
17
 *
18
 */
19

20
package ssh
21

22
import (
23
        "errors"
24
        "fmt"
25
        "os"
26
        "path/filepath"
27
        "strings"
28

29
        "github.com/spf13/cobra"
30
        "github.com/spf13/pflag"
31
        "kubevirt.io/client-go/log"
32

33
        "kubevirt.io/kubevirt/pkg/virtctl/clientconfig"
34
        "kubevirt.io/kubevirt/pkg/virtctl/portforward"
35
        "kubevirt.io/kubevirt/pkg/virtctl/templates"
36
)
37

38
const (
39
        portFlag, portFlagShort                         = "port", "p"
40
        wrapLocalSSHFlag                                = "local-ssh"
41
        usernameFlag, usernameFlagShort                 = "username", "l"
42
        IdentityFilePathFlag, identityFilePathFlagShort = "identity-file", "i"
43
        knownHostsFilePathFlag                          = "known-hosts"
44
        commandToExecute, commandToExecuteShort         = "command", "c"
45
        additionalOpts, additionalOptsShort             = "local-ssh-opts", "t"
46
)
47

48
func NewCommand() *cobra.Command {
1✔
49
        log.InitializeLogging("ssh")
1✔
50
        c := &SSH{
1✔
51
                options: DefaultSSHOptions(),
1✔
52
        }
1✔
53

1✔
54
        cmd := &cobra.Command{
1✔
55
                Use:     "ssh (VM|VMI)",
1✔
56
                Short:   "Open a SSH connection to a virtual machine instance.",
1✔
57
                Example: usage(),
1✔
58
                Args:    cobra.ExactArgs(1),
1✔
59
                RunE:    c.Run,
1✔
60
        }
1✔
61

1✔
62
        AddCommandlineArgs(cmd.Flags(), &c.options)
1✔
63
        cmd.Flags().StringVarP(&c.command, commandToExecute, commandToExecuteShort, c.command,
1✔
64
                fmt.Sprintf(`--%s='ls /': Specify a command to execute in the VM`, commandToExecute))
1✔
65
        cmd.SetUsageTemplate(templates.UsageTemplate())
1✔
66
        return cmd
1✔
67
}
1✔
68

69
func AddCommandlineArgs(flagset *pflag.FlagSet, opts *SSHOptions) {
1✔
70
        flagset.StringVarP(&opts.SSHUsername, usernameFlag, usernameFlagShort, opts.SSHUsername,
1✔
71
                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))
1✔
72
        flagset.StringVarP(&opts.IdentityFilePath, IdentityFilePathFlag, identityFilePathFlagShort, opts.IdentityFilePath,
1✔
73
                fmt.Sprintf("--%s=/home/jdoe/.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))
1✔
74
        flagset.StringVar(&opts.KnownHostsFilePath, knownHostsFilePathFlag, opts.KnownHostsFilePathDefault,
1✔
75
                fmt.Sprintf("--%s=/home/jdoe/.ssh/kubevirt_known_hosts: Set the path to the known_hosts file.", knownHostsFilePathFlag))
1✔
76
        flagset.IntVarP(&opts.SSHPort, portFlag, portFlagShort, opts.SSHPort,
1✔
77
                fmt.Sprintf(`--%s=22: Specify a port on the VM to send SSH traffic to`, portFlag))
1✔
78

1✔
79
        addAdditionalCommandlineArgs(flagset, opts)
1✔
80
}
1✔
81

82
func DefaultSSHOptions() SSHOptions {
1✔
83
        homeDir, err := os.UserHomeDir()
1✔
84
        if err != nil {
2✔
85
                log.Log.Warningf("failed to determine user home directory: %v", err)
1✔
86
        }
1✔
87
        options := SSHOptions{
1✔
88
                SSHPort:                   22,
1✔
89
                SSHUsername:               defaultUsername(),
1✔
90
                IdentityFilePath:          filepath.Join(homeDir, ".ssh", "id_rsa"),
1✔
91
                IdentityFilePathProvided:  false,
1✔
92
                KnownHostsFilePath:        "",
1✔
93
                KnownHostsFilePathDefault: "",
1✔
94
                AdditionalSSHLocalOptions: []string{},
1✔
95
                WrapLocalSSH:              true,
1✔
96
                LocalClientName:           "ssh",
1✔
97
        }
1✔
98

1✔
99
        if len(homeDir) > 0 {
1✔
100
                options.KnownHostsFilePathDefault = filepath.Join(homeDir, ".ssh", "kubevirt_known_hosts")
×
101
        }
×
102
        return options
1✔
103
}
104

105
type SSH struct {
106
        options SSHOptions
107
        command string
108
}
109

110
type SSHOptions struct {
111
        SSHPort                   int
112
        SSHUsername               string
113
        IdentityFilePath          string
114
        IdentityFilePathProvided  bool
115
        KnownHostsFilePath        string
116
        KnownHostsFilePathDefault string
117
        AdditionalSSHLocalOptions []string
118
        WrapLocalSSH              bool
119
        LocalClientName           string
120
}
121

122
func (o *SSH) Run(cmd *cobra.Command, args []string) error {
×
123
        client, namespace, _, err := clientconfig.ClientAndNamespaceFromContext(cmd.Context())
×
124
        if err != nil {
×
125
                return err
×
126
        }
×
127

128
        kind, namespace, name, err := PrepareCommand(cmd, namespace, &o.options, args)
×
129
        if err != nil {
×
130
                return err
×
131
        }
×
132

133
        if cmd.Flags().Changed(wrapLocalSSHFlag) {
×
134
                cmd.PrintErrln("The --local-ssh flag is deprecated and now defaults to true.")
×
135
        }
×
136
        if o.options.WrapLocalSSH {
×
137
                clientArgs := o.buildSSHTarget(kind, namespace, name)
×
138
                return RunLocalClient(kind, namespace, name, &o.options, clientArgs)
×
139
        }
×
140

NEW
141
        return o.nativeSSH(kind, namespace, name, client, cmd.InOrStdin(), cmd.OutOrStdout(), cmd.ErrOrStderr())
×
142
}
143

144
func PrepareCommand(cmd *cobra.Command, fallbackNamespace string, opts *SSHOptions, args []string) (kind, namespace, name string, err error) {
×
145
        opts.IdentityFilePathProvided = cmd.Flags().Changed(IdentityFilePathFlag)
×
146
        var targetUsername string
×
147
        kind, namespace, name, targetUsername, err = ParseTarget(args[0])
×
148
        if err != nil {
×
149
                return
×
150
        }
×
151

152
        if len(namespace) < 1 {
×
153
                namespace = fallbackNamespace
×
154
        }
×
155

156
        if len(targetUsername) > 0 {
×
157
                opts.SSHUsername = targetUsername
×
158
        }
×
159
        return
×
160
}
161

162
func usage() string {
1✔
163
        return fmt.Sprintf(`  # Connect to 'testvmi':
1✔
164
  {{ProgramName}} ssh jdoe@vmi/testvmi [--%s]
1✔
165

1✔
166
  # Connect to 'testvm' in 'mynamespace' namespace
1✔
167
  {{ProgramName}} ssh jdoe@vm/testvm/mynamespace [--%s]
1✔
168

1✔
169
  # Specify a username and namespace:
1✔
170
  {{ProgramName}} ssh --namespace=mynamespace --%s=jdoe vmi/testvmi`,
1✔
171
                IdentityFilePathFlag,
1✔
172
                IdentityFilePathFlag,
1✔
173
                usernameFlag,
1✔
174
        ) + additionalUsage()
1✔
175
}
1✔
176

177
func defaultUsername() string {
1✔
178
        vars := []string{
1✔
179
                "USER",     // linux
1✔
180
                "USERNAME", // linux, windows
1✔
181
                "LOGNAME",  // linux
1✔
182
        }
1✔
183
        for _, env := range vars {
2✔
184
                if v := os.Getenv(env); v != "" {
2✔
185
                        return v
1✔
186
                }
1✔
187
        }
188
        return ""
×
189
}
190

191
// ParseTarget SSH Target argument supporting the form of [username@]type/name[/namespace]
192
// or the legacy form of [username@]type/name.namespace
193
func ParseTarget(arg string) (string, string, string, string, error) {
1✔
194
        username := ""
1✔
195

1✔
196
        usernameAndTarget := strings.Split(arg, "@")
1✔
197
        if len(usernameAndTarget) > 1 {
2✔
198
                username = usernameAndTarget[0]
1✔
199
                if username == "" {
2✔
200
                        return "", "", "", "", errors.New("expected username before '@'")
1✔
201
                }
1✔
202
                arg = usernameAndTarget[1]
1✔
203
        }
204

205
        if arg == "" {
2✔
206
                return "", "", "", "", errors.New("expected target after '@'")
1✔
207
        }
1✔
208

209
        kind, namespace, name, err := portforward.ParseTarget(arg)
1✔
210
        if err != nil {
2✔
211
                return "", "", "", "", err
1✔
212
        }
1✔
213

214
        return kind, namespace, name, username, err
1✔
215
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc