Skip to content

Commit ebf5696

Browse files
authored
add initializers for kube and ssh provider (#19)
Signed-off-by: Nikolay Mitrofanov <nikolay.mitrofanov@flant.com>
1 parent 3f60072 commit ebf5696

10 files changed

Lines changed: 507 additions & 79 deletions

File tree

examples/cobra/README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,23 @@ This example shows that you can use library with cobra package.
55
## Build
66

77
```bash
8-
go build -o cobra main.go
8+
mkdir -p bin
9+
go build -o bin/cobra main.go
910
```
1011

1112
## Example commands
1213
```bash
13-
./cobra kube-only --tmp-dir=/tmp/my-cobra --kubeconfig=~/my.kind.kubeconfig --kubeconfig-context=kind-my --print-warnin
14+
bin/cobra kube-only --tmp-dir=/tmp/my-cobra --kubeconfig=~/my.kind.kubeconfig --kubeconfig-context=kind-my --print-warning
1415

15-
./cobra ssh --ssh-user=ubuntu --ssh-host=0.0.0.0
16-
./cobra ssh --ssh-user=ubuntu --ssh-host=0.0.0.0 --use-standalone-kube --kubeconfig=~/my.kind.kubeconfig
17-
./cobra ssh --ssh-user=ubuntu --ssh-host=0.0.0.0 --ssh-agent-private-keys=~/.ssh/id_rsa --ssh-agent-private-keys=~/.ssh/another
16+
bin/cobra ssh --ssh-user=ubuntu --ssh-host=0.0.0.0
17+
18+
bin/cobra ssh --ssh-user=ubuntu --ssh-host=0.0.0.0 --use-standalone-kube --kubeconfig=~/my.kind.kubeconfig
19+
20+
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
21+
22+
bin/cobra ssh-additional --ssh-user=ubuntu --kubeconfig=~/my.kind.kubeconfig
23+
24+
SSH_HOST_CONNECT=0.0.0.0 bin/cobra ssh-additional --ssh-user=ubuntu
25+
26+
SSH_HOST_CONNECT=0.0.0.0 bin/cobra ssh-additional --ssh-user=ubuntu --kubeconfig=~/kind.kubeconfig
1827
```

examples/cobra/cmd/kube_only.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ import (
1919
"fmt"
2020
"time"
2121

22+
"github.com/spf13/cobra"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
25+
connection "github.com/deckhouse/lib-connection/pkg"
2226
"github.com/deckhouse/lib-connection/pkg/kube"
2327
"github.com/deckhouse/lib-connection/pkg/provider"
2428
"github.com/deckhouse/lib-connection/pkg/settings"
2529
"github.com/deckhouse/lib-dhctl/pkg/retry"
26-
"github.com/spf13/cobra"
27-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2830
)
2931

3032
type SettingsProvider func() settings.Settings
@@ -103,8 +105,8 @@ func runKube(params *runKubeParams) error {
103105
}
104106

105107
// default initialization way
106-
providerErr := fmt.Errorf("should not use over ssh")
107-
runner, err := provider.GetRunnerInterface(conf, sett, provider.NewErrorSSHProvider(providerErr))
108+
initializer := provider.NewErrorSSHProviderForKubeInitializer(fmt.Errorf("should not use over ssh"))
109+
runner, err := provider.GetRunnerInterface(ctx, conf, sett, initializer)
108110
kubeProvider := provider.NewDefaultKubeProvider(sett, conf, runner)
109111

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

138-
func getNodes(ctx context.Context, sett settings.Settings, kubeProvider *provider.DefaultKubeProvider) error {
140+
func getNodes(ctx context.Context, sett settings.Settings, kubeProvider connection.KubeProvider) error {
139141
loopParams := retry.NewEmptyParams(
140142
retry.WithName("Getting nodes"),
141143
retry.WithAttempts(5),

examples/cobra/cmd/with_ssh.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
package cmd
1616

1717
import (
18+
"context"
1819
"fmt"
1920
"os"
2021

22+
"github.com/spf13/cobra"
23+
2124
connection "github.com/deckhouse/lib-connection/pkg"
2225
"github.com/deckhouse/lib-connection/pkg/kube"
2326
"github.com/deckhouse/lib-connection/pkg/provider"
2427
sshconfig "github.com/deckhouse/lib-connection/pkg/ssh/config"
25-
"github.com/spf13/cobra"
2628
)
2729

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

139-
runner, err := provider.GetRunnerInterface(kubeConfig, sett, sshProviderForKube)
141+
initializer := provider.NewSimpleSSHProviderInitializer(sshProviderForKube)
142+
143+
runner, err := provider.GetRunnerInterface(ctx, kubeConfig, sett, initializer)
140144
kubeProvider := provider.NewDefaultKubeProvider(sett, kubeConfig, runner)
141145

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

154158
if err != nil {
155-
return fmt.Errorf("failed to setup kube client", err)
159+
return fmt.Errorf("failed to setup kube client: %w", err)
156160
}
157161

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

177+
if err := doSSHCommand(ctx, sshClient); err != nil {
178+
return err
179+
}
180+
181+
sett.Logger().InfoF("SSH command succeeded")
182+
183+
return nil
184+
}
185+
186+
func doSSHCommand(ctx context.Context, sshClient connection.SSHClient) error {
173187
const echoStr = "SUCCESS"
174188

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

186-
sett.Logger().InfoF("SSH command succeeded")
187-
188200
return nil
189201
}
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
// Copyright 2026 Flant JSC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cmd
16+
17+
import (
18+
"context"
19+
"errors"
20+
"fmt"
21+
"os"
22+
23+
"github.com/name212/govalue"
24+
"github.com/spf13/cobra"
25+
26+
connection "github.com/deckhouse/lib-connection/pkg"
27+
"github.com/deckhouse/lib-connection/pkg/kube"
28+
"github.com/deckhouse/lib-connection/pkg/provider"
29+
"github.com/deckhouse/lib-connection/pkg/settings"
30+
sshconfig "github.com/deckhouse/lib-connection/pkg/ssh/config"
31+
)
32+
33+
func AppendSSHAdditionalCommand(settProvider SettingsProvider, rootCmd *cobra.Command) (*cobra.Command, error) {
34+
sshCmd := &cobra.Command{
35+
Use: "ssh-additional",
36+
Short: "Run example ssh with additional initialization",
37+
Long: "Run example ssh with additional initialization",
38+
}
39+
40+
// you should add cmd to parent
41+
if rootCmd != nil {
42+
rootCmd.AddCommand(sshCmd)
43+
}
44+
45+
// example of usage another flags in command is allowed
46+
// you should use PersistentFlags for getting flags from parent
47+
flagSet := sshCmd.PersistentFlags()
48+
49+
// default initialization way for flags
50+
51+
kubeParser := kube.NewFlagsParser(settProvider())
52+
kubeFlags, err := kubeParser.InitFlags(flagSet)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
sshParser := sshconfig.NewFlagsParser(settProvider())
58+
sshFlags, err := sshParser.InitFlags(flagSet)
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
// flags should pass to handler
64+
// because in handler we have all parsed keys
65+
// and we should extract configs in handler
66+
67+
sshCmd.RunE = func(cmd *cobra.Command, args []string) error {
68+
err := runSSHAdditional(&runSSHParams{
69+
sshFlags: sshFlags,
70+
kubeFlags: kubeFlags,
71+
settProvider: settProvider,
72+
cmd: cmd,
73+
commandArgs: args,
74+
})
75+
76+
if err != nil {
77+
cmd.SilenceUsage = true
78+
}
79+
80+
return err
81+
}
82+
83+
return sshCmd, nil
84+
}
85+
86+
func runSSHAdditional(params *runSSHParams) error {
87+
ctx := params.cmd.Context()
88+
89+
sett := params.settProvider()
90+
91+
kubeConfig, err := params.kubeFlags.ExtractConfig(params.commandArgs...)
92+
if err != nil {
93+
return fmt.Errorf("cannot parse kube config: %w", err)
94+
}
95+
96+
consumer := newAdditionalProvidersConsumer(params, kubeConfig)
97+
98+
// please clean up providers in the end of handler
99+
defer func() {
100+
consumer.Cleanup(ctx)
101+
}()
102+
103+
return doSSHAdditional(ctx, sett, consumer)
104+
}
105+
106+
type providersConsumer interface {
107+
provider.SSHProviderInitializerWithCleanup
108+
provider.KubeProviderInitializerWithCleanup
109+
}
110+
111+
func doSSHAdditional(ctx context.Context, sett settings.Settings, consumer providersConsumer) error {
112+
kubeProvider, err := consumer.GetKubeProvider(ctx)
113+
if err != nil {
114+
return fmt.Errorf("Cannot initialize kube providder")
115+
}
116+
117+
if err := getNodes(ctx, sett, kubeProvider); err != nil {
118+
return fmt.Errorf("failed to get nodes: %w", err)
119+
}
120+
121+
sshProvider, err := consumer.GetSSHProvider(ctx)
122+
if errors.Is(err, errNotPassedSSHHost) {
123+
sett.Logger().WarnF("SSH host not passed. Skip run ssh command. Pass SSH_HOST_CONNECT env")
124+
return nil
125+
}
126+
127+
sshClient, err := sshProvider.Client(ctx)
128+
if err != nil {
129+
return fmt.Errorf("failed to get ssh client")
130+
}
131+
132+
if err := doSSHCommand(ctx, sshClient); err != nil {
133+
return fmt.Errorf("fail to run ssh command: %w", err)
134+
}
135+
136+
sett.Logger().InfoF("SSH command succeeded")
137+
138+
return nil
139+
}
140+
141+
type additionalProvidersConsumer struct {
142+
kubeConfig *kube.Config
143+
sshFlags *sshconfig.Flags
144+
args []string
145+
146+
sett settings.Settings
147+
148+
sshProvider connection.SSHProvider
149+
kubeProvider connection.KubeProvider
150+
}
151+
152+
func newAdditionalProvidersConsumer(params *runSSHParams, kubeConfig *kube.Config) *additionalProvidersConsumer {
153+
return &additionalProvidersConsumer{
154+
kubeConfig: kubeConfig,
155+
sshFlags: params.sshFlags,
156+
args: params.commandArgs,
157+
sett: params.settProvider(),
158+
}
159+
}
160+
161+
func (i *additionalProvidersConsumer) GetKubeProvider(ctx context.Context) (connection.KubeProvider, error) {
162+
if govalue.NotNil(i.kubeProvider) {
163+
return i.kubeProvider, nil
164+
}
165+
166+
runner, err := provider.GetRunnerInterface(ctx, i.kubeConfig, i.sett, i)
167+
if err != nil {
168+
return nil, fmt.Errorf("Cannot get runner for kube provider: %w", err)
169+
}
170+
171+
i.kubeProvider = provider.NewDefaultKubeProvider(i.sett, i.kubeConfig, runner)
172+
173+
return i.kubeProvider, nil
174+
}
175+
176+
var errNotPassedSSHHost = fmt.Errorf("ssh host not passed")
177+
178+
func (i *additionalProvidersConsumer) GetSSHProvider(_ context.Context) (connection.SSHProvider, error) {
179+
if govalue.NotNil(i.sshProvider) {
180+
return i.sshProvider, nil
181+
}
182+
183+
sshConfig, err := i.sshFlags.ExtractConfig(i.args)
184+
if err != nil {
185+
return nil, fmt.Errorf("cannot parse ssh config: %w", err)
186+
}
187+
188+
if len(sshConfig.Hosts) == 0 {
189+
hostFromEnv := os.Getenv("SSH_HOST_CONNECT")
190+
if hostFromEnv == "" {
191+
return nil, errNotPassedSSHHost
192+
}
193+
194+
sshConfig.Hosts = append(sshConfig.Hosts, sshconfig.Host{
195+
Host: hostFromEnv,
196+
})
197+
}
198+
199+
i.sshProvider = provider.NewDefaultSSHProvider(i.sett, sshConfig, provider.SSHClientWithStartAfterCreate(true))
200+
201+
return i.sshProvider, nil
202+
}
203+
204+
func (i *additionalProvidersConsumer) Cleanup(ctx context.Context) error {
205+
logger := i.sett.Logger()
206+
207+
if govalue.NotNil(i.kubeProvider) {
208+
if err := i.kubeProvider.Cleanup(ctx); err != nil {
209+
logger.ErrorF("Failed to cleanup kube provider: %v", err)
210+
} else {
211+
logger.InfoF("Kube provider cleaned up successfully")
212+
213+
}
214+
}
215+
216+
if govalue.NotNil(i.sshProvider) {
217+
if err := i.sshProvider.Cleanup(ctx); err != nil {
218+
logger.ErrorF("Failed to cleanup SSH provider: %v", err)
219+
} else {
220+
logger.InfoF("SSH provider cleaned up successfully")
221+
}
222+
}
223+
224+
i.sshProvider = nil
225+
i.kubeProvider = nil
226+
227+
return nil
228+
}

examples/cobra/main.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,19 @@ func main() {
113113
envsPrefix + "_",
114114
},
115115
},
116+
"ssh-additional": {
117+
provider: cmd.AppendSSHAdditionalCommand,
118+
shouldContainsInHelp: []string{
119+
// global flags
120+
"--tmp-dir",
121+
// kube flags
122+
"--kubeconfig-context",
123+
// ssh flags
124+
"--ssh-legacy-mode",
125+
// envs prefix
126+
envsPrefix + "_",
127+
},
128+
},
116129
}
117130

118131
for name, c := range subCommands {

0 commit comments

Comments
 (0)