Skip to content

Commit 123691b

Browse files
authored
Merge pull request kubernetes#89231 from andrewsykim/e2e-framework-ssh
e2e/framework: implement ssh exec internally
2 parents 03f2b63 + 4b9b2d3 commit 123691b

File tree

4 files changed

+76
-6
lines changed

4 files changed

+76
-6
lines changed

pkg/ssh/ssh.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
// WARNING: DO NOT add new use-caes to this package as it is deprecated and slated for deletion.
18+
1719
package ssh
1820

1921
import (

test/e2e/framework/.import-restrictions

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,6 @@
199199
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/util",
200200
"k8s.io/kubernetes/pkg/securitycontext",
201201
"k8s.io/kubernetes/pkg/serviceaccount",
202-
"k8s.io/kubernetes/pkg/ssh",
203202
"k8s.io/kubernetes/pkg/util/async",
204203
"k8s.io/kubernetes/pkg/util/bandwidth",
205204
"k8s.io/kubernetes/pkg/util/config",

test/e2e/framework/ssh/BUILD

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ go_library(
66
importpath = "k8s.io/kubernetes/test/e2e/framework/ssh",
77
visibility = ["//visibility:public"],
88
deps = [
9-
"//pkg/ssh:go_default_library",
109
"//staging/src/k8s.io/api/core/v1:go_default_library",
1110
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
1211
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",

test/e2e/framework/ssh/ssh.go

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,21 @@ import (
2020
"bytes"
2121
"context"
2222
"fmt"
23+
"io/ioutil"
2324
"net"
2425
"os"
2526
"path/filepath"
2627
"time"
2728

2829
"github.com/onsi/gomega"
30+
2931
"golang.org/x/crypto/ssh"
32+
3033
v1 "k8s.io/api/core/v1"
3134
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3235
"k8s.io/apimachinery/pkg/fields"
3336
"k8s.io/apimachinery/pkg/util/wait"
3437
clientset "k8s.io/client-go/kubernetes"
35-
sshutil "k8s.io/kubernetes/pkg/ssh"
3638
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
3739
testutils "k8s.io/kubernetes/test/utils"
3840
)
@@ -54,7 +56,7 @@ const (
5456
func GetSigner(provider string) (ssh.Signer, error) {
5557
// honor a consistent SSH key across all providers
5658
if path := os.Getenv("KUBE_SSH_KEY_PATH"); len(path) > 0 {
57-
return sshutil.MakePrivateKeySignerFromFile(path)
59+
return makePrivateKeySignerFromFile(path)
5860
}
5961

6062
// Select the key itself to use. When implementing more providers here,
@@ -93,7 +95,21 @@ func GetSigner(provider string) (ssh.Signer, error) {
9395
keyfile = filepath.Join(keydir, keyfile)
9496
}
9597

96-
return sshutil.MakePrivateKeySignerFromFile(keyfile)
98+
return makePrivateKeySignerFromFile(keyfile)
99+
}
100+
101+
func makePrivateKeySignerFromFile(key string) (ssh.Signer, error) {
102+
buffer, err := ioutil.ReadFile(key)
103+
if err != nil {
104+
return nil, fmt.Errorf("error reading SSH key %s: '%v'", key, err)
105+
}
106+
107+
signer, err := ssh.ParsePrivateKey(buffer)
108+
if err != nil {
109+
return nil, fmt.Errorf("error parsing SSH key: '%v'", err)
110+
}
111+
112+
return signer, err
97113
}
98114

99115
// NodeSSHHosts returns SSH-able host names for all schedulable nodes - this
@@ -169,14 +185,68 @@ func SSH(cmd, host, provider string) (Result, error) {
169185
return result, err
170186
}
171187

172-
stdout, stderr, code, err := sshutil.RunSSHCommand(cmd, result.User, host, signer)
188+
stdout, stderr, code, err := runSSHCommand(cmd, result.User, host, signer)
173189
result.Stdout = stdout
174190
result.Stderr = stderr
175191
result.Code = code
176192

177193
return result, err
178194
}
179195

196+
// runSSHCommandViaBastion returns the stdout, stderr, and exit code from running cmd on
197+
// host as specific user, along with any SSH-level error.
198+
func runSSHCommand(cmd, user, host string, signer ssh.Signer) (string, string, int, error) {
199+
if user == "" {
200+
user = os.Getenv("USER")
201+
}
202+
// Setup the config, dial the server, and open a session.
203+
config := &ssh.ClientConfig{
204+
User: user,
205+
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
206+
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
207+
}
208+
client, err := ssh.Dial("tcp", host, config)
209+
if err != nil {
210+
err = wait.Poll(5*time.Second, 20*time.Second, func() (bool, error) {
211+
fmt.Printf("error dialing %s@%s: '%v', retrying\n", user, host, err)
212+
if client, err = ssh.Dial("tcp", host, config); err != nil {
213+
return false, err
214+
}
215+
return true, nil
216+
})
217+
}
218+
if err != nil {
219+
return "", "", 0, fmt.Errorf("error getting SSH client to %s@%s: '%v'", user, host, err)
220+
}
221+
defer client.Close()
222+
session, err := client.NewSession()
223+
if err != nil {
224+
return "", "", 0, fmt.Errorf("error creating session to %s@%s: '%v'", user, host, err)
225+
}
226+
defer session.Close()
227+
228+
// Run the command.
229+
code := 0
230+
var bout, berr bytes.Buffer
231+
session.Stdout, session.Stderr = &bout, &berr
232+
if err = session.Run(cmd); err != nil {
233+
// Check whether the command failed to run or didn't complete.
234+
if exiterr, ok := err.(*ssh.ExitError); ok {
235+
// If we got an ExitError and the exit code is nonzero, we'll
236+
// consider the SSH itself successful (just that the command run
237+
// errored on the host).
238+
if code = exiterr.ExitStatus(); code != 0 {
239+
err = nil
240+
}
241+
} else {
242+
// Some other kind of error happened (e.g. an IOError); consider the
243+
// SSH unsuccessful.
244+
err = fmt.Errorf("failed running `%s` on %s@%s: '%v'", cmd, user, host, err)
245+
}
246+
}
247+
return bout.String(), berr.String(), code, err
248+
}
249+
180250
// runSSHCommandViaBastion returns the stdout, stderr, and exit code from running cmd on
181251
// host as specific user, along with any SSH-level error. It uses an SSH proxy to connect
182252
// to bastion, then via that tunnel connects to the remote host. Similar to

0 commit comments

Comments
 (0)