Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ci-bot] Sync feature branch to master/feature directory #2403

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
94 changes: 17 additions & 77 deletions feature/pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ import (
"os"

"k8s.io/klog/v2"
"k8s.io/utils/exec"
"k8s.io/utils/ptr"

_const "github.com/kubesphere/kubekey/v4/pkg/const"
"github.com/kubesphere/kubekey/v4/pkg/variable"
Expand All @@ -38,6 +36,8 @@ const (
connectedKubernetes = "kubernetes"
)

var localShell = commandShell()

// Connector is the interface for connecting to a remote host
type Connector interface {
// Init initializes the connection
Expand All @@ -63,52 +63,11 @@ func NewConnector(host string, connectorVars map[string]any) (Connector, error)

switch connectedType {
case connectedLocal:
return &localConnector{Cmd: exec.New()}, nil
return newLocalConnector(connectorVars), nil
case connectedSSH:
// get host in connector variable. if empty, set default host: host_name.
hostParam, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorHost)
if err != nil {
klog.InfoS("get ssh port failed use default port 22", "error", err)
hostParam = host
}
// get port in connector variable. if empty, set default port: 22.
portParam, err := variable.IntVar(nil, connectorVars, _const.VariableConnectorPort)
if err != nil {
klog.V(4).Infof("connector port is empty use: %v", defaultSSHPort)
portParam = ptr.To(defaultSSHPort)
}
// get user in connector variable. if empty, set default user: root.
userParam, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorUser)
if err != nil {
klog.V(4).Infof("connector user is empty use: %s", defaultSSHUser)
userParam = defaultSSHUser
}
// get password in connector variable. if empty, should connector by private key.
passwdParam, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorPassword)
if err != nil {
klog.V(4).InfoS("connector password is empty use public key")
}
// get private key path in connector variable. if empty, set default path: /root/.ssh/id_rsa.
keyParam, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorPrivateKey)
if err != nil {
klog.V(4).Infof("ssh public key is empty, use: %s", defaultSSHPrivateKey)
keyParam = defaultSSHPrivateKey
}

return &sshConnector{
Host: hostParam,
Port: *portParam,
User: userParam,
Password: passwdParam,
PrivateKey: keyParam,
}, nil
return newSSHConnector(host, connectorVars), nil
case connectedKubernetes:
kubeconfig, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorKubeconfig)
if err != nil && host != _const.VariableLocalHost {
return nil, err
}

return &kubernetesConnector{Cmd: exec.New(), clusterName: host, kubeconfig: kubeconfig}, nil
return newKubernetesConnector(host, connectorVars)
default:
localHost, _ := os.Hostname()
// get host in connector variable. if empty, set default host: host_name.
Expand All @@ -118,39 +77,10 @@ func NewConnector(host string, connectorVars map[string]any) (Connector, error)
hostParam = host
}
if host == _const.VariableLocalHost || host == localHost || isLocalIP(hostParam) {
return &localConnector{Cmd: exec.New()}, nil
}
// get port in connector variable. if empty, set default port: 22.
portParam, err := variable.IntVar(nil, connectorVars, _const.VariableConnectorPort)
if err != nil {
klog.V(4).Infof("connector port is empty use: %v", defaultSSHPort)
portParam = ptr.To(defaultSSHPort)
}
// get user in connector variable. if empty, set default user: root.
userParam, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorUser)
if err != nil {
klog.V(4).Infof("connector user is empty use: %s", defaultSSHUser)
userParam = defaultSSHUser
}
// get password in connector variable. if empty, should connector by private key.
passwdParam, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorPassword)
if err != nil {
klog.V(4).InfoS("connector password is empty use public key")
}
// get private key path in connector variable. if empty, set default path: /root/.ssh/id_rsa.
keyParam, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorPrivateKey)
if err != nil {
klog.V(4).Infof("ssh public key is empty, use: %s", defaultSSHPrivateKey)
keyParam = defaultSSHPrivateKey
return newLocalConnector(connectorVars), nil
}

return &sshConnector{
Host: hostParam,
Port: *portParam,
User: userParam,
Password: passwdParam,
PrivateKey: keyParam,
}, nil
return newSSHConnector(host, connectorVars), nil
}
}

Expand Down Expand Up @@ -188,3 +118,13 @@ func isLocalIP(ipAddr string) bool {

return false
}

func commandShell() string {
// find command interpreter in env. default /bin/bash
sl, ok := os.LookupEnv("SHELL")
if !ok {
return "/bin/bash"
}

return sl
}
30 changes: 22 additions & 8 deletions feature/pkg/connector/kubernetes_connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,45 @@ import (
"k8s.io/utils/exec"

_const "github.com/kubesphere/kubekey/v4/pkg/const"
"github.com/kubesphere/kubekey/v4/pkg/variable"
)

const kubeconfigRelPath = ".kube/config"

var _ Connector = &kubernetesConnector{}

func newKubernetesConnector(host string, connectorVars map[string]any) (*kubernetesConnector, error) {
kubeconfig, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorKubeconfig)
if err != nil && host != _const.VariableLocalHost {
return nil, err
}

return &kubernetesConnector{
Cmd: exec.New(),
ClusterName: host,
kubeconfig: kubeconfig,
}, nil
}

type kubernetesConnector struct {
clusterName string
ClusterName string
kubeconfig string
homeDir string
Cmd exec.Interface
}

// Init connector, create home dir in local for each kubernetes.
func (c *kubernetesConnector) Init(_ context.Context) error {
if c.clusterName == _const.VariableLocalHost && c.kubeconfig == "" {
if c.ClusterName == _const.VariableLocalHost && c.kubeconfig == "" {
klog.V(4).InfoS("kubeconfig is not set, using local kubeconfig")
// use default kubeconfig. skip
return nil
}
// set home dir for each kubernetes
c.homeDir = filepath.Join(_const.GetWorkDir(), _const.KubernetesDir, c.clusterName)
c.homeDir = filepath.Join(_const.GetWorkDir(), _const.KubernetesDir, c.ClusterName)
if _, err := os.Stat(c.homeDir); err != nil && os.IsNotExist(err) {
if err := os.MkdirAll(c.homeDir, os.ModePerm); err != nil {
klog.V(4).ErrorS(err, "Failed to create local dir", "cluster", c.clusterName)
klog.V(4).ErrorS(err, "Failed to create local dir", "cluster", c.ClusterName)
// if dir is not exist, create it.
return err
}
Expand All @@ -60,14 +74,14 @@ func (c *kubernetesConnector) Init(_ context.Context) error {
kubeconfigPath := filepath.Join(c.homeDir, kubeconfigRelPath)
if _, err := os.Stat(kubeconfigPath); err != nil && os.IsNotExist(err) {
if err := os.MkdirAll(filepath.Dir(kubeconfigPath), os.ModePerm); err != nil {
klog.V(4).ErrorS(err, "Failed to create local dir", "cluster", c.clusterName)
klog.V(4).ErrorS(err, "Failed to create local dir", "cluster", c.ClusterName)

return err
}
}
// write kubeconfig to home dir
if err := os.WriteFile(kubeconfigPath, []byte(c.kubeconfig), os.ModePerm); err != nil {
klog.V(4).ErrorS(err, "Failed to create kubeconfig file", "cluster", c.clusterName)
klog.V(4).ErrorS(err, "Failed to create kubeconfig file", "cluster", c.ClusterName)

return err
}
Expand Down Expand Up @@ -100,7 +114,7 @@ func (c *kubernetesConnector) PutFile(_ context.Context, src []byte, dst string,
func (c *kubernetesConnector) FetchFile(ctx context.Context, src string, dst io.Writer) error {
// add "--kubeconfig" to src command
klog.V(5).InfoS("exec local command", "cmd", src)
command := c.Cmd.CommandContext(ctx, "/bin/sh", "-c", src)
command := c.Cmd.CommandContext(ctx, localShell, "-c", src)
command.SetDir(c.homeDir)
command.SetEnv([]string{"KUBECONFIG=" + filepath.Join(c.homeDir, kubeconfigRelPath)})
command.SetStdout(dst)
Expand All @@ -113,7 +127,7 @@ func (c *kubernetesConnector) FetchFile(ctx context.Context, src string, dst io.
func (c *kubernetesConnector) ExecuteCommand(ctx context.Context, cmd string) ([]byte, error) {
// add "--kubeconfig" to src command
klog.V(5).InfoS("exec local command", "cmd", cmd)
command := c.Cmd.CommandContext(ctx, "/bin/sh", "-c", cmd)
command := c.Cmd.CommandContext(ctx, localShell, "-c", cmd)
command.SetDir(c.homeDir)
command.SetEnv([]string{"KUBECONFIG=" + filepath.Join(c.homeDir, kubeconfigRelPath)})

Expand Down
23 changes: 21 additions & 2 deletions feature/pkg/connector/local_connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,24 @@ import (
"k8s.io/utils/exec"

_const "github.com/kubesphere/kubekey/v4/pkg/const"
"github.com/kubesphere/kubekey/v4/pkg/variable"
)

var _ Connector = &localConnector{}
var _ GatherFacts = &localConnector{}

func newLocalConnector(connectorVars map[string]any) *localConnector {
sudo, err := variable.StringVar(nil, connectorVars, _const.VariableConnectorSudoPassword)
if err != nil {
klog.V(4).InfoS("get connector sudo password failed, execute command without sudo", "error", err)
}

return &localConnector{Sudo: sudo, Cmd: exec.New()}
}

type localConnector struct {
Cmd exec.Interface
Sudo string
Cmd exec.Interface
}

// Init connector. do nothing
Expand Down Expand Up @@ -84,8 +95,16 @@ func (c *localConnector) FetchFile(_ context.Context, src string, dst io.Writer)
// ExecuteCommand in local host
func (c *localConnector) ExecuteCommand(ctx context.Context, cmd string) ([]byte, error) {
klog.V(5).InfoS("exec local command", "cmd", cmd)
// find command interpreter in env. default /bin/bash

if c.Sudo != "" {
command := c.Cmd.CommandContext(ctx, "sudo", "-E", localShell, "-c", cmd)
command.SetStdin(bytes.NewBufferString(c.Sudo + "\n"))

return command.CombinedOutput()
}

return c.Cmd.CommandContext(ctx, "/bin/sh", "-c", cmd).CombinedOutput()
return c.Cmd.CommandContext(ctx, localShell, "-c", cmd).CombinedOutput()
}

// HostInfo for GatherFacts
Expand Down
52 changes: 11 additions & 41 deletions feature/pkg/connector/local_connector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,64 +18,34 @@ package connector

import (
"context"
"errors"
"fmt"
"strings"
"testing"
"time"

"github.com/stretchr/testify/assert"
"k8s.io/utils/exec"
testingexec "k8s.io/utils/exec/testing"
)

func newFakeLocalConnector(runCmd string, output string) *localConnector {
return &localConnector{
Cmd: &testingexec.FakeExec{CommandScript: []testingexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd {
if strings.TrimSpace(fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))) == "/bin/sh -c "+runCmd {
return &testingexec.FakeCmd{
CombinedOutputScript: []testingexec.FakeAction{func() ([]byte, []byte, error) {
return []byte(output), nil, nil
}},
}
}

return &testingexec.FakeCmd{
CombinedOutputScript: []testingexec.FakeAction{func() ([]byte, []byte, error) {
return nil, nil, errors.New("error command")
}},
}
},
}},
}
}

func TestSshConnector_ExecuteCommand(t *testing.T) {
func TestLocalConnector_ExecuteCommand(t *testing.T) {
testcases := []struct {
name string
cmd string
exceptedErr error
name string
cmd string
localConnector *localConnector
expectedStdout string
}{
{
name: "execute command succeed",
cmd: "echo 'hello'",
exceptedErr: nil,
},
{
name: "execute command failed",
cmd: "echo 'hello1'",
exceptedErr: errors.New("error command"),
name: "execute command succeed",
cmd: "echo 'hello'",
localConnector: &localConnector{Cmd: exec.New()},
expectedStdout: "hello\n",
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
lc := newFakeLocalConnector("echo 'hello'", "hello")
_, err := lc.ExecuteCommand(ctx, tc.cmd)
assert.Equal(t, tc.exceptedErr, err)
a, _ := tc.localConnector.ExecuteCommand(ctx, tc.cmd)
assert.Equal(t, tc.expectedStdout, string(a))
})
}
}
Loading