diff --git a/Makefile b/Makefile index 4b5cadc..c003c5f 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,8 @@ GOFUMPT_VERSION=0.9.2 JQ_VERSION=1.8.1 KIND_VERSION=0.31.0 +export DHCTL_TESTS_OPENSSH_IMAGE = lscr.io/linuxserver/openssh-server:10.0_p1-r9-ls209 + PLATFORM_NAME := $(shell uname -m) OS_NAME := $(shell uname) @@ -127,7 +129,10 @@ go-deps/ci/check/no-tidy: go-installed go-deps/tidy exit 1; \ fi -test: go-installed docker-installed bin/kind +test/pull-ssh-image: docker-installed + @docker pull $(DHCTL_TESTS_OPENSSH_IMAGE) + +test: go-installed docker-installed bin/kind test/pull-ssh-image ./hack/run_tests.sh $(MAKE) clean/test diff --git a/pkg/kube/README.md b/pkg/kube/README.md new file mode 100644 index 0000000..e04922f --- /dev/null +++ b/pkg/kube/README.md @@ -0,0 +1,5 @@ +# kube package + +Contains kube clients implementations. + +Kube clients e2e tests located in `../tests/e2e/kube/*_test.go`. diff --git a/pkg/provider/README.md b/pkg/provider/README.md index bfc96c4..016a80b 100644 --- a/pkg/provider/README.md +++ b/pkg/provider/README.md @@ -2,7 +2,7 @@ Contains kube and ssh provider. -SSH provider integration tests located in `../ssh/testssh/provider_test.go`. +SSH provider e2e tests located in `../tests/e2e/ssh/provider_test.go`. Kube provider does not contain unit tests only integration. -Integration tests located in `../tests/provider/kube_test.go`. \ No newline at end of file +Integration tests located in `../tests/e2e/kube/provider_test.go`. \ No newline at end of file diff --git a/pkg/ssh.go b/pkg/ssh.go index c49bdd0..7690164 100644 --- a/pkg/ssh.go +++ b/pkg/ssh.go @@ -20,6 +20,7 @@ import ( "regexp" "time" + "github.com/deckhouse/lib-dhctl/pkg/log" "github.com/deckhouse/lib-dhctl/pkg/retry" "github.com/deckhouse/lib-connection/pkg/ssh/session" @@ -127,6 +128,7 @@ type BundlerOptions struct { NoLogStepOutOnError bool StepsDelimiter string Retries int + ProcessLogger log.ProcessLogger } func (o *BundlerOptions) IsValid() error { @@ -179,6 +181,12 @@ func BundlerWithRetries(retries int) BundlerOption { } } +func BundlerWithProcessLogger(logger log.ProcessLogger) BundlerOption { + return func(opts *BundlerOptions) { + opts.ProcessLogger = logger + } +} + type Bundler interface { Execute(ctx context.Context, parentDir, bundleDir string) ([]byte, error) } diff --git a/pkg/ssh/README.md b/pkg/ssh/README.md new file mode 100644 index 0000000..4cfe00f --- /dev/null +++ b/pkg/ssh/README.md @@ -0,0 +1,5 @@ +# ssh package + +Contains ssh client implementations. + +SSH clients e2e tests located in `../tests/e2e/ssh/*/*_test.go`. diff --git a/pkg/ssh/gossh/common_test.go b/pkg/ssh/gossh/common_test.go index 53c8187..8d296e3 100644 --- a/pkg/ssh/gossh/common_test.go +++ b/pkg/ssh/gossh/common_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/require" "github.com/deckhouse/lib-connection/pkg/tests" + kind "github.com/deckhouse/lib-connection/pkg/tests/e2e/kube/kind" ) func registerStopClient(t *testing.T, sshClient *Client) { @@ -107,10 +108,10 @@ func registerStopTunnel(t *testing.T, tunnel *Tunnel) { func startContainerAndClientAndKind(t *testing.T, test *tests.Test, opts ...tests.TestContainerWrapperSettingsOpts) (*Client, *tests.TestContainerWrapper) { sshClient, container := startContainerAndClientWithContainer(t, test, opts...) - kindCluster := tests.CreateKINDCluster(t, &tests.KINDClusterCreateParams{ + kindCluster := kind.CreateKINDCluster(t, &kind.KINDClusterCreateParams{ Test: test, ClusterName: "kube-proxy", - Containers: []*tests.SSHContainersForKind{ + Containers: []*kind.SSHContainersForKind{ { Client: sshClient, Container: container, diff --git a/pkg/ssh/gossh/kube_proxy_test.go b/pkg/ssh/gossh/kube_proxy_test.go index 92d6c18..2b2afc5 100644 --- a/pkg/ssh/gossh/kube_proxy_test.go +++ b/pkg/ssh/gossh/kube_proxy_test.go @@ -17,6 +17,7 @@ package gossh import ( "context" "fmt" + "os" "testing" "time" @@ -29,6 +30,10 @@ import ( func TestKubeProxy(t *testing.T) { test := tests.ShouldNewIntegrationTest(t, "TestKubeGoProxy") + if kindBinEnv := os.Getenv("TEST_KIND_BINARY"); kindBinEnv == "" { + t.Setenv("TEST_KIND_BINARY", "../../../bin/kind") + } + sshClient, container := prepareContainerForTestKubeProxy(t, test) waitRestart := func(op string) { diff --git a/pkg/ssh/local/command_test.go b/pkg/ssh/local/command_test.go deleted file mode 100644 index 38d48a4..0000000 --- a/pkg/ssh/local/command_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2024 Flant JSC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package local - -import ( - "context" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - sshtesting "github.com/deckhouse/lib-connection/pkg/ssh/testssh" -) - -func TestCommandOutput(t *testing.T) { - s := require.New(t) - testFilePath := filepath.Join(os.TempDir(), "test") - tmpFile, err := os.Create(testFilePath) - s.NoError(err) - t.Cleanup(func() { - _ = os.Remove(testFilePath) - }) - - _, err = tmpFile.WriteString("Hello world") - s.NoError(err) - - cmd := NewCommand(sshtesting.CreateSettings(), "cat", testFilePath) - stdout, _, err := cmd.Output(context.Background()) - s.NoError(err) - s.Equal("Hello world", string(stdout)) -} - -func TestCommandCombinedOutput(t *testing.T) { - s := require.New(t) - testFilePath := filepath.Join(os.TempDir(), "test") - tmpFile, err := os.Create(testFilePath) - s.NoError(err) - t.Cleanup(func() { - _ = os.Remove(testFilePath) - }) - - _, err = tmpFile.WriteString("Hello world") - s.NoError(err) - - cmd := NewCommand(sshtesting.CreateSettings(), "cat", testFilePath) - stdout, err := cmd.CombinedOutput(context.Background()) - s.NoError(err) - s.Equal("Hello world", string(stdout)) -} - -func TestCommandRun(t *testing.T) { - s := require.New(t) - testFilePath := filepath.Join(os.TempDir(), "test") - tmpFile, err := os.Create(testFilePath) - s.NoError(err) - t.Cleanup(func() { - _ = os.Remove(testFilePath) - }) - - _, err = tmpFile.WriteString("Hello world") - s.NoError(err) - - cmd := NewCommand(sshtesting.CreateSettings(), "cat", testFilePath) - err = cmd.Run(context.Background()) - s.NoError(err) - s.Equal("Hello world", string(cmd.StdoutBytes())) - s.Nil(cmd.StderrBytes()) -} - -func TestCommandPipe(t *testing.T) { - s := require.New(t) - - cmd := NewCommand(sshtesting.CreateSettings(), "bash", "-c", `echo "Goodbye world" | sed "s/Goodbye/Hello/g"`) - s.NoError(cmd.Run(context.Background())) - s.Equal("Hello world", string(cmd.StdoutBytes())) - s.Nil(cmd.StderrBytes()) -} diff --git a/pkg/ssh/local/script.go b/pkg/ssh/local/script.go index 80e479e..ae49a96 100644 --- a/pkg/ssh/local/script.go +++ b/pkg/ssh/local/script.go @@ -147,6 +147,16 @@ func (s *Script) WithNoLogStepOutOnError(f bool) { s.noOutError = f } +func (s *Script) WithBundleDest(d string) *Script { + s.bundleDest = d + return s +} + +func (s *Script) WithForceNoSudoForBundle(f bool) *Script { + s.forceBundleNoSudo = f + return s +} + func (s *Script) WithExecuteUploadDir(string) {} func (s *Script) bundleCmdProvider(ctx context.Context, node connection.Interface, parentDir, bundleDir string) (connection.Command, error) { diff --git a/pkg/ssh/utils/bundle.go b/pkg/ssh/utils/bundle.go index ce41e57..59f2b6a 100644 --- a/pkg/ssh/utils/bundle.go +++ b/pkg/ssh/utils/bundle.go @@ -90,6 +90,12 @@ func BundleWithCommandPreparator(p CommandPreparator) BundleOpt { } } +func BundleWithProcessLogger(logger log.ProcessLogger) BundleOpt { + return func(b *Bundle) { + b.processLogger = logger + } +} + func UserBundleOptsOrBashible(inputOpts ...connection.BundlerOption) ([]BundleOpt, error) { userOpts := make([]connection.BundlerOption, len(inputOpts)) copy(userOpts, inputOpts) @@ -117,6 +123,8 @@ type Bundle struct { commandPreparator CommandPreparator bundleCmdProvider BundleCmdProvider + + processLogger log.ProcessLogger } func NewBundle(sett settings.Settings, client connection.Interface, scriptPath string, args []string, opts ...BundleOpt) (*Bundle, error) { @@ -161,7 +169,10 @@ func (b *Bundle) Execute(ctx context.Context, parentDir, bundleDir string) ([]by bundleCmd.Sudo(ctx) logger := b.sett.Logger() - processLogger := logger.ProcessLogger() + processLogger := b.processLogger + if govalue.Nil(processLogger) { + processLogger = logger.ProcessLogger() + } handler := newOutputHandler(b, bundleCmd, logger, processLogger) @@ -413,5 +424,6 @@ func convertBundleOption(opts ...connection.BundlerOption) ([]BundleOpt, error) BundleWithStepsDelimiter(options.StepsDelimiter), BundleWithNoLogStepOutOnError(options.NoLogStepOutOnError), BundleWithShouldInfoOutChecker(options.ShouldInfoOutChecker), + BundleWithProcessLogger(options.ProcessLogger), }, nil } diff --git a/pkg/tests/kube/kube_exec_test.go b/pkg/tests/e2e/kube/exec_test.go similarity index 95% rename from pkg/tests/kube/kube_exec_test.go rename to pkg/tests/e2e/kube/exec_test.go index 9122214..1417445 100644 --- a/pkg/tests/kube/kube_exec_test.go +++ b/pkg/tests/e2e/kube/exec_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package provider +package kube_test import ( "bytes" @@ -32,6 +32,7 @@ import ( "github.com/deckhouse/lib-connection/pkg/kube" "github.com/deckhouse/lib-connection/pkg/provider" "github.com/deckhouse/lib-connection/pkg/tests" + kind "github.com/deckhouse/lib-connection/pkg/tests/e2e/kube/kind" ) func TestKubeExec(t *testing.T) { @@ -41,7 +42,7 @@ func TestKubeExec(t *testing.T) { tests.TestWithParallelRun(false), ) - kindCluster := tests.CreateKINDCluster(t, &tests.KINDClusterCreateParams{ + kindCluster := kind.CreateKINDCluster(t, &kind.KINDClusterCreateParams{ Test: execTest, ClusterName: "exec-in-pod", NoPrepareLocalKubectlInSSHContainer: true, @@ -49,7 +50,7 @@ func TestKubeExec(t *testing.T) { kindCluster.RegisterCleanup(t) - kubeProvider := getKubeProvider(t, execTest, kindCluster) + kubeProvider := getKubeProviderForExec(t, execTest, kindCluster) pythonImage := "registry.deckhouse.io/base_images@sha256:b15f9150f3b51f2e11cd73db39c89eef8a075953659caf802363d4f544335fb5" if envImage := os.Getenv("TEST_KUBE_EXEC_PYTHON_IMAGE"); envImage != "" { @@ -161,7 +162,7 @@ print(data, end="") }) } -func getKubeProvider(t *testing.T, test *tests.Test, cluster *tests.KINDCluster) *provider.DefaultKubeProvider { +func getKubeProviderForExec(t *testing.T, test *tests.Test, cluster *kind.KINDCluster) *provider.DefaultKubeProvider { sett := test.Settings() restCfg, err := cluster.RESTConfig() diff --git a/pkg/tests/kind.go b/pkg/tests/e2e/kube/kind/cluster.go similarity index 96% rename from pkg/tests/kind.go rename to pkg/tests/e2e/kube/kind/cluster.go index 6d53868..ac19efc 100644 --- a/pkg/tests/kind.go +++ b/pkg/tests/e2e/kube/kind/cluster.go @@ -18,6 +18,7 @@ import ( "context" "encoding/json" "fmt" + "os" "os/exec" "regexp" "strings" @@ -30,10 +31,10 @@ import ( "k8s.io/client-go/tools/clientcmd" connection "github.com/deckhouse/lib-connection/pkg" + "github.com/deckhouse/lib-connection/pkg/tests" ) const ( - KindBinary = "../../../bin/kind" kindConfig = ` kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 @@ -48,11 +49,11 @@ var ( type SSHContainersForKind struct { Client connection.SSHClient - Container *TestContainerWrapper + Container *tests.TestContainerWrapper } type KINDClusterCreateParams struct { - Test *Test + Test *tests.Test ClusterName string Containers []*SSHContainersForKind @@ -64,7 +65,7 @@ type KINDCluster struct { ControlPlaneIP string ControlPlanePort string - test *Test + test *tests.Test kubeconfig string restConfig *rest.Config } @@ -74,7 +75,7 @@ func (c *KINDCluster) appendClusterNameArg(args []string) []string { } func (c *KINDCluster) runKind(args ...string) (string, error) { - cmd := exec.Command(KindBinary, c.appendClusterNameArg(args)...) + cmd := exec.Command(getKINDBinary(), c.appendClusterNameArg(args)...) out, err := cmd.CombinedOutput() return string(out), err } @@ -238,7 +239,7 @@ func CreateKINDCluster(t *testing.T, params *KINDClusterCreateParams) *KINDClust test.GetLogger().InfoF("Creating KIND cluster %s...", clusterName) out, err := cluster.runKind(args...) - require.NoError(t, err, "not create kind cluster: %w:%s\n", out) + require.NoError(t, err, "not create kind cluster: %w:%s\n", err, out) test.GetLogger().InfoF("KIND cluster %s created:\n%s", clusterName, out) @@ -292,7 +293,7 @@ func runDockerForKINDContainer(cluster *KINDCluster, name string, args ...string err := retry.NewLoopWithParams(params).Run(func() error { var err error - out, err = RunDockerWithOut(args...) + out, err = tests.RunDockerWithOut(args...) out = strings.TrimSpace(out) return err }) @@ -462,3 +463,11 @@ func (p *localKubectlPreparator) prepareLocalKubeCtlInSSHContainer(t *testing.T, ) checkErrorDuringCreateCluster(t, cluster, err, "failed to create link to kube config on ssh container %s", containerName) } + +func getKINDBinary() string { + if bin := os.Getenv("TEST_KIND_BINARY"); bin != "" { + return bin + } + + return "../../../../bin/kind" +} diff --git a/pkg/tests/provider/kube_test.go b/pkg/tests/e2e/kube/provider_test.go similarity index 98% rename from pkg/tests/provider/kube_test.go rename to pkg/tests/e2e/kube/provider_test.go index c644478..bbe411a 100644 --- a/pkg/tests/provider/kube_test.go +++ b/pkg/tests/e2e/kube/provider_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package provider +package kube_test import ( "context" @@ -35,6 +35,7 @@ import ( sshconfig "github.com/deckhouse/lib-connection/pkg/ssh/config" "github.com/deckhouse/lib-connection/pkg/ssh/gossh" "github.com/deckhouse/lib-connection/pkg/tests" + kind "github.com/deckhouse/lib-connection/pkg/tests/e2e/kube/kind" ) func TestDefaultKubeProvider(t *testing.T) { @@ -423,7 +424,7 @@ func TestDefaultKubeProvider(t *testing.T) { t.Run("OverKubeconfig", func(t *testing.T) { rt := runTest{name: "overKubeconfig"} - getKubeconfigKubeProviderWithPort := func(t *testing.T, test *tests.Test, kind *tests.KINDCluster, port string) (*provider.DefaultKubeProvider, *provider.DefaultSSHProvider) { + getKubeconfigKubeProviderWithPort := func(t *testing.T, test *tests.Test, kind *kind.KINDCluster, port string) (*provider.DefaultKubeProvider, *provider.DefaultSSHProvider) { defaultConfig := connectionConfigForContainer(firstContainer, rt.mode) sshProvider := getSSHProvider(test, defaultConfig) registerCleanupSSHProvider(t, test, sshProvider) @@ -442,7 +443,7 @@ func TestDefaultKubeProvider(t *testing.T) { return kubeProvider, sshProvider } - getKubeconfigKubeProvider := func(t *testing.T, test *tests.Test, kind *tests.KINDCluster) (*provider.DefaultKubeProvider, *provider.DefaultSSHProvider) { + getKubeconfigKubeProvider := func(t *testing.T, test *tests.Test, kind *kind.KINDCluster) (*provider.DefaultKubeProvider, *provider.DefaultSSHProvider) { return getKubeconfigKubeProviderWithPort(t, test, kind, kind.ControlPlanePort) } @@ -498,7 +499,7 @@ func TestDefaultKubeProvider(t *testing.T) { t.Run("OverRESTConfig", func(t *testing.T) { rt := runTest{name: "overRESTKubeconfig"} - getKubeconfigKubeProvider := func(t *testing.T, test *tests.Test, kind *tests.KINDCluster, token ...string) (*provider.DefaultKubeProvider, *provider.DefaultSSHProvider) { + getKubeconfigKubeProvider := func(t *testing.T, test *tests.Test, kind *kind.KINDCluster, token ...string) (*provider.DefaultKubeProvider, *provider.DefaultSSHProvider) { defaultConfig := connectionConfigForContainer(firstContainer, rt.mode) sshProvider := getSSHProvider(test, defaultConfig) registerCleanupSSHProvider(t, test, sshProvider) @@ -572,7 +573,7 @@ func TestDefaultKubeProvider(t *testing.T) { t.Run("LocalRun", func(t *testing.T) { rt := runTest{name: "local_run"} - getKubeconfigKubeProvider := func(t *testing.T, test *tests.Test, kind *tests.KINDCluster, rewriteEnv ...string) *provider.DefaultKubeProvider { + getKubeconfigKubeProvider := func(t *testing.T, test *tests.Test, kind *kind.KINDCluster, rewriteEnv ...string) *provider.DefaultKubeProvider { path := "" if len(rewriteEnv) > 0 { @@ -654,8 +655,8 @@ func (r runTest) getName(t *testing.T) string { return fmt.Sprintf("KubeProvider%s%s", name, r.name) } -func createKINDCluster(t *testing.T, test *tests.Test, containers ...*tests.TestContainerWrapper) *tests.KINDCluster { - forKind := make([]*tests.SSHContainersForKind, 0, len(containers)) +func createKINDCluster(t *testing.T, test *tests.Test, containers ...*tests.TestContainerWrapper) *kind.KINDCluster { + forKind := make([]*kind.SSHContainersForKind, 0, len(containers)) for _, container := range containers { client := gossh.NewClient( context.TODO(), @@ -667,13 +668,13 @@ func createKINDCluster(t *testing.T, test *tests.Test, containers ...*tests.Test err := client.Start() require.NoError(t, err, "client should start for %s", container.Container.ContainerSettings().ContainerName) - forKind = append(forKind, &tests.SSHContainersForKind{ + forKind = append(forKind, &kind.SSHContainersForKind{ Container: container, Client: client, }) } - kindCluster := tests.CreateKINDCluster(t, &tests.KINDClusterCreateParams{ + kindCluster := kind.CreateKINDCluster(t, &kind.KINDClusterCreateParams{ Test: test, ClusterName: "kube-provider-client", Containers: forKind, diff --git a/pkg/ssh/testssh/command_test.go b/pkg/tests/e2e/ssh/command_test.go similarity index 99% rename from pkg/ssh/testssh/command_test.go rename to pkg/tests/e2e/ssh/command_test.go index 81748e2..8b9c617 100644 --- a/pkg/ssh/testssh/command_test.go +++ b/pkg/tests/e2e/ssh/command_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package testssh +package ssh_test import ( "context" diff --git a/pkg/ssh/testssh/common_test.go b/pkg/tests/e2e/ssh/common_test.go similarity index 99% rename from pkg/ssh/testssh/common_test.go rename to pkg/tests/e2e/ssh/common_test.go index f1ab271..07c4287 100644 --- a/pkg/ssh/testssh/common_test.go +++ b/pkg/tests/e2e/ssh/common_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package testssh +package ssh_test import ( "context" diff --git a/pkg/ssh/testssh/file_test.go b/pkg/tests/e2e/ssh/file_test.go similarity index 99% rename from pkg/ssh/testssh/file_test.go rename to pkg/tests/e2e/ssh/file_test.go index bc6de18..3bd1097 100644 --- a/pkg/ssh/testssh/file_test.go +++ b/pkg/tests/e2e/ssh/file_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package testssh +package ssh_test import ( "context" diff --git a/pkg/ssh/testssh/kube_proxy_test.go b/pkg/tests/e2e/ssh/kube_proxy_test.go similarity index 96% rename from pkg/ssh/testssh/kube_proxy_test.go rename to pkg/tests/e2e/ssh/kube_proxy_test.go index dde84f8..bd41801 100644 --- a/pkg/ssh/testssh/kube_proxy_test.go +++ b/pkg/tests/e2e/ssh/kube_proxy_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package testssh +package ssh_test import ( "context" @@ -30,6 +30,7 @@ import ( sshconfig "github.com/deckhouse/lib-connection/pkg/ssh/config" "github.com/deckhouse/lib-connection/pkg/ssh/gossh" "github.com/deckhouse/lib-connection/pkg/tests" + kind "github.com/deckhouse/lib-connection/pkg/tests/e2e/kube/kind" ) func TestKubeProxy(t *testing.T) { @@ -146,10 +147,10 @@ func startContainerAndKind(t *testing.T, test *tests.Test, opts ...tests.TestCon }, } - kindCluster := tests.CreateKINDCluster(t, &tests.KINDClusterCreateParams{ + kindCluster := kind.CreateKINDCluster(t, &kind.KINDClusterCreateParams{ Test: test, ClusterName: "kube-proxy-general", - Containers: []*tests.SSHContainersForKind{ + Containers: []*kind.SSHContainersForKind{ { Client: startClientForContainer(t, test, rt, container), Container: container, diff --git a/pkg/tests/e2e/ssh/local/command_test.go b/pkg/tests/e2e/ssh/local/command_test.go new file mode 100644 index 0000000..2a340fe --- /dev/null +++ b/pkg/tests/e2e/ssh/local/command_test.go @@ -0,0 +1,96 @@ +// Copyright 2026 Flant JSC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package local_test + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/deckhouse/lib-connection/pkg/ssh/local" + "github.com/deckhouse/lib-connection/pkg/tests" +) + +func TestCommandOutput(t *testing.T) { + tst := tests.ShouldNewIntegrationTest(t, "LocalOutputCommand") + + content := "Hello world" + + path := tst.MustCreateTmpFile(t, content, false, "test_out.txt") + + cmd := local.NewCommand(tst.Settings(), "cat", path) + stdout, _, err := cmd.Output(context.Background()) + require.NoError(t, err, "should exec") + require.Equal(t, content, string(stdout), "content should equal") +} + +func TestCommandCombinedOutput(t *testing.T) { + tst := tests.ShouldNewIntegrationTest(t, "LocalCombineOutput") + + scriptPath := stdoutStdErrScript(t, tst) + + cmd := local.NewCommand(tst.Settings(), scriptPath) + stdout, err := cmd.CombinedOutput(context.Background()) + + require.NoError(t, err, "combine out should run") + require.Equal(t, "Stdout\nStderr\n", string(stdout)) +} + +func TestCommandRun(t *testing.T) { + tst := tests.ShouldNewIntegrationTest(t, "LocalRun") + + scriptPath := stdoutStdErrScript(t, tst) + + cmd := local.NewCommand(tst.Settings(), scriptPath) + + stdout := "" + stderr := "" + + cmd.WithStdoutHandler(func(line string) { + stdout = fmt.Sprintf("%s%s", stdout, line) + }) + + cmd.WithStderrHandler(func(line string) { + stderr = fmt.Sprintf("%s%s", stderr, line) + }) + + err := cmd.Run(context.Background()) + + require.NoError(t, err, "run should success") + require.Equal(t, "Stdout", stdout, "stdout from handler") + require.Equal(t, "Stdout", string(cmd.StdoutBytes()), "stdout from cmd") + + require.Equal(t, "Stderr", stderr, "stderr from handler") + require.Equal(t, "Stderr", string(cmd.StderrBytes()), "stderr from cmd") +} + +func TestCommandPipe(t *testing.T) { + tst := tests.ShouldNewIntegrationTest(t, "LocalRunPipe") + + cmd := local.NewCommand( + tst.Settings(), + "bash", + "-c", + `echo "Goodbye world" | sed "s/Goodbye/Hello/g"`, + ) + + err := cmd.Run(context.Background()) + + require.NoError(t, err, "should run") + require.Equal(t, "Hello world", string(cmd.StdoutBytes()), "should equal stdout") + require.Len(t, cmd.StderrBytes(), 0, "should no std err") +} diff --git a/pkg/tests/e2e/ssh/local/common_test.go b/pkg/tests/e2e/ssh/local/common_test.go new file mode 100644 index 0000000..991eb72 --- /dev/null +++ b/pkg/tests/e2e/ssh/local/common_test.go @@ -0,0 +1,29 @@ +// Copyright 2026 Flant JSC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package local_test + +import ( + "testing" + + "github.com/deckhouse/lib-connection/pkg/tests" +) + +func stdoutStdErrScript(t *testing.T, tst *tests.Test) string { + script := `#!/bin/bash +echo "Stdout" +echo "Stderr" >&2 +` + return tst.MustCreateTmpFile(t, script, true, "script.sh") +} diff --git a/pkg/ssh/local/script_test.go b/pkg/tests/e2e/ssh/local/script_test.go similarity index 87% rename from pkg/ssh/local/script_test.go rename to pkg/tests/e2e/ssh/local/script_test.go index e52dfa6..dc9d7c6 100644 --- a/pkg/ssh/local/script_test.go +++ b/pkg/tests/e2e/ssh/local/script_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package local +package local_test import ( "bytes" @@ -22,24 +22,25 @@ import ( "github.com/stretchr/testify/require" + "github.com/deckhouse/lib-connection/pkg/ssh/local" "github.com/deckhouse/lib-connection/pkg/tests" ) const testRunScript = `#! /bin/bash -echo $@ +echo -n $@ exit 0` func TestScriptExecute(t *testing.T) { - tst := tests.ShouldNewTest(t, "LocalExecuteCommand") + tst := tests.ShouldNewIntegrationTest(t, "LocalExecuteScript") path, err := tst.CreateTmpFile(testRunScript, true, "run-local") require.NoError(t, err, "Script for local run should created") - script := NewNodeInterface(tst.Settings()).UploadScript(path, "arg 1", "arg 2") + script := local.NewNodeInterface(tst.Settings()).UploadScript(path, "arg 1", "arg 2") stdout, err := script.Execute(context.Background()) require.NoError(t, err, "local script should executed") - require.Equal(t, string(stdout), "arg 1 arg 2") + require.Equal(t, "arg 1 arg 2", string(stdout)) } func TestExecuteBundle(t *testing.T) { @@ -60,9 +61,9 @@ func TestExecuteBundle(t *testing.T) { testDir := tests.PrepareFakeBashibleBundle(t, tst, entrypoint, "bashible") - node := NewNodeInterface(tst.Settings()) + node := local.NewNodeInterface(tst.Settings()) - t.Run("Upload and execute bundle to container via existing ssh client", func(t *testing.T) { + t.Run("Upload and execute bundle local", func(t *testing.T) { cases := []struct { title string scriptArgs []string @@ -118,11 +119,10 @@ func TestExecuteBundle(t *testing.T) { s := node.UploadScript(entrypoint, c.scriptArgs...) - sImpl, ok := s.(*Script) + sImpl, ok := s.(*local.Script) require.True(t, ok, "should convert to impl") - sImpl.bundleDest = bundleDest - sImpl.forceBundleNoSudo = true + sImpl.WithBundleDest(bundleDest).WithForceNoSudoForBundle(true) loggerBuf.Reset() diff --git a/pkg/ssh/testssh/provider_test.go b/pkg/tests/e2e/ssh/provider_test.go similarity index 99% rename from pkg/ssh/testssh/provider_test.go rename to pkg/tests/e2e/ssh/provider_test.go index 82e6c00..04e9f7f 100644 --- a/pkg/ssh/testssh/provider_test.go +++ b/pkg/tests/e2e/ssh/provider_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package testssh +package ssh_test import ( "context" diff --git a/pkg/ssh/testssh/upload-script_test.go b/pkg/tests/e2e/ssh/upload-script_test.go similarity index 99% rename from pkg/ssh/testssh/upload-script_test.go rename to pkg/tests/e2e/ssh/upload-script_test.go index 80bf01c..97e6ca6 100644 --- a/pkg/ssh/testssh/upload-script_test.go +++ b/pkg/tests/e2e/ssh/upload-script_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package testssh +package ssh_test import ( "bytes"