Skip to content
Merged
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
19 changes: 14 additions & 5 deletions examples/cobra/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,23 @@ This example shows that you can use library with cobra package.
## Build

```bash
go build -o cobra main.go
mkdir -p bin
go build -o bin/cobra main.go
```

## Example commands
```bash
./cobra kube-only --tmp-dir=/tmp/my-cobra --kubeconfig=~/my.kind.kubeconfig --kubeconfig-context=kind-my --print-warnin
bin/cobra kube-only --tmp-dir=/tmp/my-cobra --kubeconfig=~/my.kind.kubeconfig --kubeconfig-context=kind-my --print-warning

./cobra ssh --ssh-user=ubuntu --ssh-host=0.0.0.0
./cobra ssh --ssh-user=ubuntu --ssh-host=0.0.0.0 --use-standalone-kube --kubeconfig=~/my.kind.kubeconfig
./cobra ssh --ssh-user=ubuntu --ssh-host=0.0.0.0 --ssh-agent-private-keys=~/.ssh/id_rsa --ssh-agent-private-keys=~/.ssh/another
bin/cobra ssh --ssh-user=ubuntu --ssh-host=0.0.0.0

bin/cobra ssh --ssh-user=ubuntu --ssh-host=0.0.0.0 --use-standalone-kube --kubeconfig=~/my.kind.kubeconfig

bin/cobra ssh --ssh-user=ubuntu --ssh-host=0.0.0.0 --ssh-agent-private-keys=~/.ssh/id_rsa --ssh-agent-private-keys=~/.ssh/another

bin/cobra ssh-additional --ssh-user=ubuntu --kubeconfig=~/my.kind.kubeconfig

SSH_HOST_CONNECT=0.0.0.0 bin/cobra ssh-additional --ssh-user=ubuntu

SSH_HOST_CONNECT=0.0.0.0 bin/cobra ssh-additional --ssh-user=ubuntu --kubeconfig=~/kind.kubeconfig
```
12 changes: 7 additions & 5 deletions examples/cobra/cmd/kube_only.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ import (
"fmt"
"time"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

connection "github.com/deckhouse/lib-connection/pkg"
"github.com/deckhouse/lib-connection/pkg/kube"
"github.com/deckhouse/lib-connection/pkg/provider"
"github.com/deckhouse/lib-connection/pkg/settings"
"github.com/deckhouse/lib-dhctl/pkg/retry"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type SettingsProvider func() settings.Settings
Expand Down Expand Up @@ -103,8 +105,8 @@ func runKube(params *runKubeParams) error {
}

// default initialization way
providerErr := fmt.Errorf("should not use over ssh")
runner, err := provider.GetRunnerInterface(conf, sett, provider.NewErrorSSHProvider(providerErr))
initializer := provider.NewErrorSSHProviderForKubeInitializer(fmt.Errorf("should not use over ssh"))
runner, err := provider.GetRunnerInterface(ctx, conf, sett, initializer)
kubeProvider := provider.NewDefaultKubeProvider(sett, conf, runner)

// please clean up providers in the end of handler
Expand Down Expand Up @@ -135,7 +137,7 @@ func runKube(params *runKubeParams) error {
return nil
}

func getNodes(ctx context.Context, sett settings.Settings, kubeProvider *provider.DefaultKubeProvider) error {
func getNodes(ctx context.Context, sett settings.Settings, kubeProvider connection.KubeProvider) error {
loopParams := retry.NewEmptyParams(
retry.WithName("Getting nodes"),
retry.WithAttempts(5),
Expand Down
22 changes: 17 additions & 5 deletions examples/cobra/cmd/with_ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
package cmd

import (
"context"
"fmt"
"os"

"github.com/spf13/cobra"

connection "github.com/deckhouse/lib-connection/pkg"
"github.com/deckhouse/lib-connection/pkg/kube"
"github.com/deckhouse/lib-connection/pkg/provider"
sshconfig "github.com/deckhouse/lib-connection/pkg/ssh/config"
"github.com/spf13/cobra"
)

func AppendSSHCommand(settProvider SettingsProvider, rootCmd *cobra.Command) (*cobra.Command, error) {
Expand Down Expand Up @@ -136,7 +138,9 @@ func runSSH(params *runSSHParams) error {
sshProviderForKube = provider.NewErrorSSHProvider(providerErr)
}

runner, err := provider.GetRunnerInterface(kubeConfig, sett, sshProviderForKube)
initializer := provider.NewSimpleSSHProviderInitializer(sshProviderForKube)

runner, err := provider.GetRunnerInterface(ctx, kubeConfig, sett, initializer)
kubeProvider := provider.NewDefaultKubeProvider(sett, kubeConfig, runner)

// please clean up providers in the end of handler
Expand All @@ -152,7 +156,7 @@ func runSSH(params *runSSHParams) error {
}()

if err != nil {
return fmt.Errorf("failed to setup kube client", err)
return fmt.Errorf("failed to setup kube client: %w", err)
}

if err := getNodes(ctx, sett, kubeProvider); err != nil {
Expand All @@ -170,6 +174,16 @@ func runSSH(params *runSSHParams) error {
return fmt.Errorf("failed to setup ssh client: %w", err)
}

if err := doSSHCommand(ctx, sshClient); err != nil {
return err
}

sett.Logger().InfoF("SSH command succeeded")

return nil
}

func doSSHCommand(ctx context.Context, sshClient connection.SSHClient) error {
const echoStr = "SUCCESS"

cmd := sshClient.Command("echo", "-n", echoStr)
Expand All @@ -183,7 +197,5 @@ func runSSH(params *runSSHParams) error {
return fmt.Errorf("failed to run echo command, got output: %s", string(strOut))
}

sett.Logger().InfoF("SSH command succeeded")

return nil
}
228 changes: 228 additions & 0 deletions examples/cobra/cmd/with_ssh_additional_init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
// 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 cmd

import (
"context"
"errors"
"fmt"
"os"

"github.com/name212/govalue"
"github.com/spf13/cobra"

connection "github.com/deckhouse/lib-connection/pkg"
"github.com/deckhouse/lib-connection/pkg/kube"
"github.com/deckhouse/lib-connection/pkg/provider"
"github.com/deckhouse/lib-connection/pkg/settings"
sshconfig "github.com/deckhouse/lib-connection/pkg/ssh/config"
)

func AppendSSHAdditionalCommand(settProvider SettingsProvider, rootCmd *cobra.Command) (*cobra.Command, error) {
sshCmd := &cobra.Command{
Use: "ssh-additional",
Short: "Run example ssh with additional initialization",
Long: "Run example ssh with additional initialization",
}

// you should add cmd to parent
if rootCmd != nil {
rootCmd.AddCommand(sshCmd)
}

// example of usage another flags in command is allowed
// you should use PersistentFlags for getting flags from parent
flagSet := sshCmd.PersistentFlags()

// default initialization way for flags

kubeParser := kube.NewFlagsParser(settProvider())
kubeFlags, err := kubeParser.InitFlags(flagSet)
if err != nil {
return nil, err
}

sshParser := sshconfig.NewFlagsParser(settProvider())
sshFlags, err := sshParser.InitFlags(flagSet)
if err != nil {
return nil, err
}

// flags should pass to handler
// because in handler we have all parsed keys
// and we should extract configs in handler

sshCmd.RunE = func(cmd *cobra.Command, args []string) error {
err := runSSHAdditional(&runSSHParams{
sshFlags: sshFlags,
kubeFlags: kubeFlags,
settProvider: settProvider,
cmd: cmd,
commandArgs: args,
})

if err != nil {
cmd.SilenceUsage = true
}

return err
}

return sshCmd, nil
}

func runSSHAdditional(params *runSSHParams) error {
ctx := params.cmd.Context()

sett := params.settProvider()

kubeConfig, err := params.kubeFlags.ExtractConfig(params.commandArgs...)
if err != nil {
return fmt.Errorf("cannot parse kube config: %w", err)
}

consumer := newAdditionalProvidersConsumer(params, kubeConfig)

// please clean up providers in the end of handler
defer func() {
consumer.Cleanup(ctx)
}()

return doSSHAdditional(ctx, sett, consumer)
}

type providersConsumer interface {
provider.SSHProviderInitializerWithCleanup
provider.KubeProviderInitializerWithCleanup
}

func doSSHAdditional(ctx context.Context, sett settings.Settings, consumer providersConsumer) error {
kubeProvider, err := consumer.GetKubeProvider(ctx)
if err != nil {
return fmt.Errorf("Cannot initialize kube providder")
}

if err := getNodes(ctx, sett, kubeProvider); err != nil {
return fmt.Errorf("failed to get nodes: %w", err)
}

sshProvider, err := consumer.GetSSHProvider(ctx)
if errors.Is(err, errNotPassedSSHHost) {
sett.Logger().WarnF("SSH host not passed. Skip run ssh command. Pass SSH_HOST_CONNECT env")
return nil
}

sshClient, err := sshProvider.Client(ctx)
if err != nil {
return fmt.Errorf("failed to get ssh client")
}

if err := doSSHCommand(ctx, sshClient); err != nil {
return fmt.Errorf("fail to run ssh command: %w", err)
}

sett.Logger().InfoF("SSH command succeeded")

return nil
}

type additionalProvidersConsumer struct {
kubeConfig *kube.Config
sshFlags *sshconfig.Flags
args []string

sett settings.Settings

sshProvider connection.SSHProvider
kubeProvider connection.KubeProvider
}

func newAdditionalProvidersConsumer(params *runSSHParams, kubeConfig *kube.Config) *additionalProvidersConsumer {
return &additionalProvidersConsumer{
kubeConfig: kubeConfig,
sshFlags: params.sshFlags,
args: params.commandArgs,
sett: params.settProvider(),
}
}

func (i *additionalProvidersConsumer) GetKubeProvider(ctx context.Context) (connection.KubeProvider, error) {
if govalue.NotNil(i.kubeProvider) {
return i.kubeProvider, nil
}

runner, err := provider.GetRunnerInterface(ctx, i.kubeConfig, i.sett, i)
if err != nil {
return nil, fmt.Errorf("Cannot get runner for kube provider: %w", err)
}

i.kubeProvider = provider.NewDefaultKubeProvider(i.sett, i.kubeConfig, runner)

return i.kubeProvider, nil
}

var errNotPassedSSHHost = fmt.Errorf("ssh host not passed")

func (i *additionalProvidersConsumer) GetSSHProvider(_ context.Context) (connection.SSHProvider, error) {
if govalue.NotNil(i.sshProvider) {
return i.sshProvider, nil
}

sshConfig, err := i.sshFlags.ExtractConfig(i.args)
if err != nil {
return nil, fmt.Errorf("cannot parse ssh config: %w", err)
}

if len(sshConfig.Hosts) == 0 {
hostFromEnv := os.Getenv("SSH_HOST_CONNECT")
if hostFromEnv == "" {
return nil, errNotPassedSSHHost
}

sshConfig.Hosts = append(sshConfig.Hosts, sshconfig.Host{
Host: hostFromEnv,
})
}

i.sshProvider = provider.NewDefaultSSHProvider(i.sett, sshConfig, provider.SSHClientWithStartAfterCreate(true))

return i.sshProvider, nil
}

func (i *additionalProvidersConsumer) Cleanup(ctx context.Context) error {
logger := i.sett.Logger()

if govalue.NotNil(i.kubeProvider) {
if err := i.kubeProvider.Cleanup(ctx); err != nil {
logger.ErrorF("Failed to cleanup kube provider: %v", err)
} else {
logger.InfoF("Kube provider cleaned up successfully")

}
}

if govalue.NotNil(i.sshProvider) {
if err := i.sshProvider.Cleanup(ctx); err != nil {
logger.ErrorF("Failed to cleanup SSH provider: %v", err)
} else {
logger.InfoF("SSH provider cleaned up successfully")
}
}

i.sshProvider = nil
i.kubeProvider = nil

return nil
}
13 changes: 13 additions & 0 deletions examples/cobra/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ func main() {
envsPrefix + "_",
},
},
"ssh-additional": {
provider: cmd.AppendSSHAdditionalCommand,
shouldContainsInHelp: []string{
// global flags
"--tmp-dir",
// kube flags
"--kubeconfig-context",
// ssh flags
"--ssh-legacy-mode",
// envs prefix
envsPrefix + "_",
},
},
}

for name, c := range subCommands {
Expand Down
Loading
Loading