mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 02:41:25 +00:00
e2e/framework: implement ssh exec internally
Signed-off-by: Andrew Sy Kim <kim.andrewsy@gmail.com>
This commit is contained in:
parent
e5c67f75a9
commit
f26b8372f8
@ -198,7 +198,6 @@
|
|||||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/util",
|
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/util",
|
||||||
"k8s.io/kubernetes/pkg/securitycontext",
|
"k8s.io/kubernetes/pkg/securitycontext",
|
||||||
"k8s.io/kubernetes/pkg/serviceaccount",
|
"k8s.io/kubernetes/pkg/serviceaccount",
|
||||||
"k8s.io/kubernetes/pkg/ssh",
|
|
||||||
"k8s.io/kubernetes/pkg/util/async",
|
"k8s.io/kubernetes/pkg/util/async",
|
||||||
"k8s.io/kubernetes/pkg/util/bandwidth",
|
"k8s.io/kubernetes/pkg/util/bandwidth",
|
||||||
"k8s.io/kubernetes/pkg/util/config",
|
"k8s.io/kubernetes/pkg/util/config",
|
||||||
|
@ -6,7 +6,6 @@ go_library(
|
|||||||
importpath = "k8s.io/kubernetes/test/e2e/framework/ssh",
|
importpath = "k8s.io/kubernetes/test/e2e/framework/ssh",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/ssh:go_default_library",
|
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||||
|
@ -20,19 +20,21 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/onsi/gomega"
|
"github.com/onsi/gomega"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
sshutil "k8s.io/kubernetes/pkg/ssh"
|
|
||||||
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
|
||||||
testutils "k8s.io/kubernetes/test/utils"
|
testutils "k8s.io/kubernetes/test/utils"
|
||||||
)
|
)
|
||||||
@ -54,7 +56,7 @@ const (
|
|||||||
func GetSigner(provider string) (ssh.Signer, error) {
|
func GetSigner(provider string) (ssh.Signer, error) {
|
||||||
// honor a consistent SSH key across all providers
|
// honor a consistent SSH key across all providers
|
||||||
if path := os.Getenv("KUBE_SSH_KEY_PATH"); len(path) > 0 {
|
if path := os.Getenv("KUBE_SSH_KEY_PATH"); len(path) > 0 {
|
||||||
return sshutil.MakePrivateKeySignerFromFile(path)
|
return makePrivateKeySignerFromFile(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select the key itself to use. When implementing more providers here,
|
// Select the key itself to use. When implementing more providers here,
|
||||||
@ -93,7 +95,21 @@ func GetSigner(provider string) (ssh.Signer, error) {
|
|||||||
keyfile = filepath.Join(keydir, keyfile)
|
keyfile = filepath.Join(keydir, keyfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sshutil.MakePrivateKeySignerFromFile(keyfile)
|
return makePrivateKeySignerFromFile(keyfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePrivateKeySignerFromFile(key string) (ssh.Signer, error) {
|
||||||
|
buffer, err := ioutil.ReadFile(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading SSH key %s: '%v'", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err := ssh.ParsePrivateKey(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing SSH key: '%v'", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return signer, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeSSHHosts returns SSH-able host names for all schedulable nodes - this
|
// NodeSSHHosts returns SSH-able host names for all schedulable nodes - this
|
||||||
@ -169,7 +185,7 @@ func SSH(cmd, host, provider string) (Result, error) {
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout, stderr, code, err := sshutil.RunSSHCommand(cmd, result.User, host, signer)
|
stdout, stderr, code, err := runSSHCommand(cmd, result.User, host, signer)
|
||||||
result.Stdout = stdout
|
result.Stdout = stdout
|
||||||
result.Stderr = stderr
|
result.Stderr = stderr
|
||||||
result.Code = code
|
result.Code = code
|
||||||
@ -177,6 +193,60 @@ func SSH(cmd, host, provider string) (Result, error) {
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runSSHCommandViaBastion returns the stdout, stderr, and exit code from running cmd on
|
||||||
|
// host as specific user, along with any SSH-level error.
|
||||||
|
func runSSHCommand(cmd, user, host string, signer ssh.Signer) (string, string, int, error) {
|
||||||
|
if user == "" {
|
||||||
|
user = os.Getenv("USER")
|
||||||
|
}
|
||||||
|
// Setup the config, dial the server, and open a session.
|
||||||
|
config := &ssh.ClientConfig{
|
||||||
|
User: user,
|
||||||
|
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
client, err := ssh.Dial("tcp", host, config)
|
||||||
|
if err != nil {
|
||||||
|
err = wait.Poll(5*time.Second, 20*time.Second, func() (bool, error) {
|
||||||
|
fmt.Printf("error dialing %s@%s: '%v', retrying\n", user, host, err)
|
||||||
|
if client, err = ssh.Dial("tcp", host, config); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, fmt.Errorf("error getting SSH client to %s@%s: '%v'", user, host, err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
session, err := client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, fmt.Errorf("error creating session to %s@%s: '%v'", user, host, err)
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
// Run the command.
|
||||||
|
code := 0
|
||||||
|
var bout, berr bytes.Buffer
|
||||||
|
session.Stdout, session.Stderr = &bout, &berr
|
||||||
|
if err = session.Run(cmd); err != nil {
|
||||||
|
// Check whether the command failed to run or didn't complete.
|
||||||
|
if exiterr, ok := err.(*ssh.ExitError); ok {
|
||||||
|
// If we got an ExitError and the exit code is nonzero, we'll
|
||||||
|
// consider the SSH itself successful (just that the command run
|
||||||
|
// errored on the host).
|
||||||
|
if code = exiterr.ExitStatus(); code != 0 {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Some other kind of error happened (e.g. an IOError); consider the
|
||||||
|
// SSH unsuccessful.
|
||||||
|
err = fmt.Errorf("failed running `%s` on %s@%s: '%v'", cmd, user, host, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bout.String(), berr.String(), code, err
|
||||||
|
}
|
||||||
|
|
||||||
// runSSHCommandViaBastion returns the stdout, stderr, and exit code from running cmd on
|
// runSSHCommandViaBastion returns the stdout, stderr, and exit code from running cmd on
|
||||||
// host as specific user, along with any SSH-level error. It uses an SSH proxy to connect
|
// host as specific user, along with any SSH-level error. It uses an SSH proxy to connect
|
||||||
// to bastion, then via that tunnel connects to the remote host. Similar to
|
// to bastion, then via that tunnel connects to the remote host. Similar to
|
||||||
|
Loading…
Reference in New Issue
Block a user