Skip to content

Commit 93c012b

Browse files
committed
interfaces
1 parent b92cb09 commit 93c012b

File tree

10 files changed

+305
-127
lines changed

10 files changed

+305
-127
lines changed

pkg/cmd/deregister/deregister.go

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"context"
66
"fmt"
77
"os/user"
8-
"runtime"
98

109
nodev1connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/devplaneapi/v1/devplaneapiv1connect"
1110
nodev1 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/devplaneapi/v1"
@@ -28,30 +27,60 @@ type DeregisterStore interface {
2827
GetAccessToken() (string, error)
2928
}
3029

30+
// PlatformChecker checks whether the current platform is supported.
31+
type PlatformChecker interface {
32+
IsCompatible() bool
33+
}
34+
35+
// Selector prompts the user to choose from a list of items.
36+
type Selector interface {
37+
Select(label string, items []string) string
38+
}
39+
40+
// NetBirdUninstaller uninstalls the NetBird network agent.
41+
type NetBirdUninstaller interface {
42+
Uninstall() error
43+
}
44+
45+
// NodeClientFactory creates ConnectRPC ExternalNodeService clients.
46+
type NodeClientFactory interface {
47+
NewNodeClient(provider register.TokenProvider, baseURL string) nodev1connect.ExternalNodeServiceClient
48+
}
49+
50+
// SSHKeyRemover removes Brev-managed SSH keys.
51+
type SSHKeyRemover interface {
52+
RemoveBrevKeys(u *user.User) error
53+
}
54+
55+
// brevSSHKeyRemover delegates to enablessh.RemoveBrevAuthorizedKeys.
56+
type brevSSHKeyRemover struct{}
57+
58+
func (brevSSHKeyRemover) RemoveBrevKeys(u *user.User) error {
59+
if err := enablessh.RemoveBrevAuthorizedKeys(u); err != nil {
60+
return fmt.Errorf("removing brev authorized keys: %w", err)
61+
}
62+
return nil
63+
}
64+
3165
// deregisterDeps bundles the side-effecting dependencies of runDeregister so
3266
// they can be replaced in tests.
3367
type deregisterDeps struct {
34-
goos string
35-
promptSelect func(label string, items []string) string
36-
uninstallNetbird func() error
37-
newNodeClient func(provider register.TokenProvider, baseURL string) nodev1connect.ExternalNodeServiceClient
68+
platform PlatformChecker
69+
prompter Selector
70+
netbird NetBirdUninstaller
71+
nodeClients NodeClientFactory
3872
registrationStore register.RegistrationStore
39-
removeBrevKeys func(*user.User) error
73+
sshKeys SSHKeyRemover
4074
}
4175

42-
func prodDeregisterDeps(brevHome string) deregisterDeps {
76+
func defaultDeregisterDeps(brevHome string) deregisterDeps {
4377
return deregisterDeps{
44-
goos: runtime.GOOS,
45-
promptSelect: func(label string, items []string) string {
46-
return terminal.PromptSelectInput(terminal.PromptSelectContent{
47-
Label: label,
48-
Items: items,
49-
})
50-
},
51-
uninstallNetbird: register.UninstallNetbird,
52-
newNodeClient: register.NewNodeServiceClient,
78+
platform: register.LinuxPlatform{},
79+
prompter: register.TerminalPrompter{},
80+
netbird: register.NetBirdManager{},
81+
nodeClients: register.DefaultNodeClientFactory{},
5382
registrationStore: register.NewFileRegistrationStore(brevHome),
54-
removeBrevKeys: enablessh.RemoveBrevAuthorizedKeys,
83+
sshKeys: brevSSHKeyRemover{},
5584
}
5685
}
5786

@@ -77,15 +106,15 @@ func NewCmdDeregister(t *terminal.Terminal, store DeregisterStore) *cobra.Comman
77106
if err != nil {
78107
return breverrors.WrapAndTrace(err)
79108
}
80-
return runDeregister(cmd.Context(), t, store, prodDeregisterDeps(brevHome))
109+
return runDeregister(cmd.Context(), t, store, defaultDeregisterDeps(brevHome))
81110
},
82111
}
83112

84113
return cmd
85114
}
86115

87116
func runDeregister(ctx context.Context, t *terminal.Terminal, s DeregisterStore, deps deregisterDeps) error { //nolint:funlen // deregistration flow
88-
if deps.goos != "linux" {
117+
if !deps.platform.IsCompatible() {
89118
return fmt.Errorf("brev deregister is only supported on Linux")
90119
}
91120

@@ -109,7 +138,7 @@ func runDeregister(ctx context.Context, t *terminal.Terminal, s DeregisterStore,
109138
t.Vprintf(" Name: %s\n", reg.DisplayName)
110139
t.Vprint("")
111140

112-
confirm := deps.promptSelect(
141+
confirm := deps.prompter.Select(
113142
"Proceed with deregistration?",
114143
[]string{"Yes, proceed", "No, cancel"},
115144
)
@@ -120,7 +149,7 @@ func runDeregister(ctx context.Context, t *terminal.Terminal, s DeregisterStore,
120149

121150
t.Vprint("")
122151
t.Vprint(t.Yellow("Removing node from Brev..."))
123-
client := deps.newNodeClient(s, config.GlobalConfig.GetBrevPublicAPIURL())
152+
client := deps.nodeClients.NewNodeClient(s, config.GlobalConfig.GetBrevPublicAPIURL())
124153
if _, err := client.RemoveNode(ctx, connect.NewRequest(&nodev1.RemoveNodeRequest{
125154
ExternalNodeId: reg.ExternalNodeID,
126155
})); err != nil {
@@ -134,21 +163,21 @@ func runDeregister(ctx context.Context, t *terminal.Terminal, s DeregisterStore,
134163
if err != nil {
135164
t.Vprintf(" Warning: could not determine current user for SSH key cleanup: %v\n", err)
136165
} else {
137-
if err := deps.removeBrevKeys(u); err != nil {
166+
if err := deps.sshKeys.RemoveBrevKeys(u); err != nil {
138167
t.Vprintf(" Warning: failed to remove Brev SSH keys: %v\n", err)
139168
} else {
140169
t.Vprint(t.Green(" Brev SSH keys removed from authorized_keys."))
141170
}
142171
}
143172
t.Vprint("")
144173

145-
removeNetbird := deps.promptSelect(
174+
removeNetbird := deps.prompter.Select(
146175
"Would you also like to uninstall NetBird?",
147176
[]string{"Yes, uninstall NetBird", "No, keep NetBird installed"},
148177
)
149178
if removeNetbird == "Yes, uninstall NetBird" {
150179
t.Vprint("Removing NetBird...")
151-
if err := deps.uninstallNetbird(); err != nil {
180+
if err := deps.netbird.Uninstall(); err != nil {
152181
t.Vprintf(" Warning: failed to uninstall NetBird: %v\n", err)
153182
} else {
154183
t.Vprint(t.Green(" NetBird uninstalled."))

pkg/cmd/deregister/deregister_test.go

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -73,29 +73,69 @@ func (m *mockRegistrationStore) Exists() (bool, error) {
7373
return m.reg != nil, nil
7474
}
7575

76+
// mock types for deregisterDeps interfaces
77+
78+
type mockPlatform struct{ compatible bool }
79+
80+
func (m mockPlatform) IsCompatible() bool { return m.compatible }
81+
82+
type mockSelector struct {
83+
fn func(label string, items []string) string
84+
}
85+
86+
func (m mockSelector) Select(label string, items []string) string {
87+
return m.fn(label, items)
88+
}
89+
90+
type mockNetBirdUninstaller struct {
91+
called bool
92+
err error
93+
}
94+
95+
func (m *mockNetBirdUninstaller) Uninstall() error {
96+
m.called = true
97+
return m.err
98+
}
99+
100+
type mockNodeClientFactory struct {
101+
serverURL string
102+
}
103+
104+
func (m mockNodeClientFactory) NewNodeClient(provider register.TokenProvider, _ string) nodev1connect.ExternalNodeServiceClient {
105+
return register.NewNodeServiceClient(provider, m.serverURL)
106+
}
107+
108+
type mockSSHKeyRemover struct {
109+
called bool
110+
err error
111+
}
112+
113+
func (m *mockSSHKeyRemover) RemoveBrevKeys(_ *user.User) error {
114+
m.called = true
115+
return m.err
116+
}
117+
76118
// testDeregisterDeps returns deps with all side-effects stubbed. The
77-
// promptSelect defaults to confirming all prompts.
119+
// prompter defaults to confirming all prompts.
78120
func testDeregisterDeps(t *testing.T, svc *fakeNodeService, regStore register.RegistrationStore) (deregisterDeps, *httptest.Server) {
79121
t.Helper()
80122

81123
_, handler := nodev1connect.NewExternalNodeServiceHandler(svc)
82124
server := httptest.NewServer(handler)
83125

84126
return deregisterDeps{
85-
goos: "linux",
86-
promptSelect: func(_ string, items []string) string {
127+
platform: mockPlatform{compatible: true},
128+
prompter: mockSelector{fn: func(_ string, items []string) string {
87129
// Default: pick first item (Yes, ...)
88130
if len(items) > 0 {
89131
return items[0]
90132
}
91133
return ""
92-
},
93-
uninstallNetbird: func() error { return nil },
94-
newNodeClient: func(provider register.TokenProvider, _ string) nodev1connect.ExternalNodeServiceClient {
95-
return register.NewNodeServiceClient(provider, server.URL)
96-
},
134+
}},
135+
netbird: &mockNetBirdUninstaller{},
136+
nodeClients: mockNodeClientFactory{serverURL: server.URL},
97137
registrationStore: regStore,
98-
removeBrevKeys: func(_ *user.User) error { return nil },
138+
sshKeys: &mockSSHKeyRemover{},
99139
}, server
100140
}
101141

@@ -166,14 +206,14 @@ func Test_runDeregister_UserCancels(t *testing.T) {
166206
defer server.Close()
167207

168208
callCount := 0
169-
deps.promptSelect = func(_ string, _ []string) string {
209+
deps.prompter = mockSelector{fn: func(_ string, _ []string) string {
170210
callCount++
171211
if callCount == 2 {
172212
// Second prompt is the confirmation — cancel it
173213
return "No, cancel"
174214
}
175215
return "No, keep NetBird installed"
176-
}
216+
}}
177217

178218
term := terminal.New()
179219
err := runDeregister(context.Background(), term, store, deps)
@@ -272,28 +312,25 @@ func Test_runDeregister_SkipsNetbirdUninstall(t *testing.T) {
272312
},
273313
}
274314

275-
uninstallCalled := false
315+
netbird := &mockNetBirdUninstaller{}
276316
deps, server := testDeregisterDeps(t, svc, regStore)
277317
defer server.Close()
278318

279-
deps.promptSelect = func(label string, items []string) string {
319+
deps.prompter = mockSelector{fn: func(label string, _ []string) string {
280320
if label == "Would you also like to uninstall NetBird?" {
281321
return "No, keep NetBird installed"
282322
}
283323
return "Yes, proceed"
284-
}
285-
deps.uninstallNetbird = func() error {
286-
uninstallCalled = true
287-
return nil
288-
}
324+
}}
325+
deps.netbird = netbird
289326

290327
term := terminal.New()
291328
err := runDeregister(context.Background(), term, store, deps)
292329
if err != nil {
293330
t.Fatalf("runDeregister failed: %v", err)
294331
}
295332

296-
if uninstallCalled {
333+
if netbird.called {
297334
t.Error("NetBird uninstall should not be called when user declines")
298335
}
299336
}
@@ -319,21 +356,18 @@ func Test_runDeregister_CallsRemoveBrevKeys(t *testing.T) {
319356
},
320357
}
321358

322-
removeBrevKeysCalled := false
359+
sshKeys := &mockSSHKeyRemover{}
323360
deps, server := testDeregisterDeps(t, svc, regStore)
324361
defer server.Close()
325-
deps.removeBrevKeys = func(_ *user.User) error {
326-
removeBrevKeysCalled = true
327-
return nil
328-
}
362+
deps.sshKeys = sshKeys
329363

330364
term := terminal.New()
331365
err := runDeregister(context.Background(), term, store, deps)
332366
if err != nil {
333367
t.Fatalf("runDeregister failed: %v", err)
334368
}
335369

336-
if !removeBrevKeysCalled {
370+
if !sshKeys.called {
337371
t.Error("expected removeBrevKeys to be called during deregistration")
338372
}
339373
}
@@ -361,9 +395,7 @@ func Test_runDeregister_RemoveBrevKeysFailureIsNonFatal(t *testing.T) {
361395

362396
deps, server := testDeregisterDeps(t, svc, regStore)
363397
defer server.Close()
364-
deps.removeBrevKeys = func(_ *user.User) error {
365-
return fmt.Errorf("permission denied")
366-
}
398+
deps.sshKeys = &mockSSHKeyRemover{err: fmt.Errorf("permission denied")}
367399

368400
term := terminal.New()
369401
err := runDeregister(context.Background(), term, store, deps)

pkg/cmd/enablessh/enablessh.go

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"os/exec"
1010
"os/user"
1111
"path/filepath"
12-
"runtime"
1312
"strings"
1413

1514
nodev1connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/devplaneapi/v1/devplaneapiv1connect"
@@ -32,18 +31,28 @@ type EnableSSHStore interface {
3231
GetAccessToken() (string, error)
3332
}
3433

34+
// PlatformChecker checks whether the current platform is supported.
35+
type PlatformChecker interface {
36+
IsCompatible() bool
37+
}
38+
39+
// NodeClientFactory creates ConnectRPC ExternalNodeService clients.
40+
type NodeClientFactory interface {
41+
NewNodeClient(provider register.TokenProvider, baseURL string) nodev1connect.ExternalNodeServiceClient
42+
}
43+
3544
// enableSSHDeps bundles the side-effecting dependencies of runEnableSSH so they
3645
// can be replaced in tests.
3746
type enableSSHDeps struct {
38-
goos string
39-
newNodeClient func(provider register.TokenProvider, baseURL string) nodev1connect.ExternalNodeServiceClient
47+
platform PlatformChecker
48+
nodeClients NodeClientFactory
4049
registrationStore register.RegistrationStore
4150
}
4251

43-
func prodEnableSSHDeps(brevHome string) enableSSHDeps {
52+
func defaultEnableSSHDeps(brevHome string) enableSSHDeps {
4453
return enableSSHDeps{
45-
goos: runtime.GOOS,
46-
newNodeClient: register.NewNodeServiceClient,
54+
platform: register.LinuxPlatform{},
55+
nodeClients: register.DefaultNodeClientFactory{},
4756
registrationStore: register.NewFileRegistrationStore(brevHome),
4857
}
4958
}
@@ -61,15 +70,15 @@ func NewCmdEnableSSH(t *terminal.Terminal, store EnableSSHStore) *cobra.Command
6170
if err != nil {
6271
return breverrors.WrapAndTrace(err)
6372
}
64-
return runEnableSSH(cmd.Context(), t, store, prodEnableSSHDeps(brevHome))
73+
return runEnableSSH(cmd.Context(), t, store, defaultEnableSSHDeps(brevHome))
6574
},
6675
}
6776

6877
return cmd
6978
}
7079

7180
func runEnableSSH(ctx context.Context, t *terminal.Terminal, s EnableSSHStore, deps enableSSHDeps) error {
72-
if deps.goos != "linux" {
81+
if !deps.platform.IsCompatible() {
7382
return fmt.Errorf("brev enable-ssh is only supported on Linux")
7483
}
7584

@@ -91,15 +100,15 @@ func runEnableSSH(ctx context.Context, t *terminal.Terminal, s EnableSSHStore, d
91100
return breverrors.WrapAndTrace(err)
92101
}
93102

94-
return EnableSSH(ctx, t, deps.newNodeClient, s, reg, brevUser)
103+
return EnableSSH(ctx, t, deps.nodeClients, s, reg, brevUser)
95104
}
96105

97106
// EnableSSH grants SSH access to the given node for the specified Brev user.
98107
// It is exported so that the register command can reuse it after registration.
99108
func EnableSSH(
100109
ctx context.Context,
101110
t *terminal.Terminal,
102-
newClient func(register.TokenProvider, string) nodev1connect.ExternalNodeServiceClient,
111+
nodeClients NodeClientFactory,
103112
tokenProvider register.TokenProvider,
104113
reg *register.DeviceRegistration,
105114
brevUser *entity.User,
@@ -128,7 +137,7 @@ func EnableSSH(
128137
}
129138
}
130139

131-
client := newClient(tokenProvider, config.GlobalConfig.GetBrevPublicAPIURL())
140+
client := nodeClients.NewNodeClient(tokenProvider, config.GlobalConfig.GetBrevPublicAPIURL())
132141
if _, err := client.GrantNodeSSHAccess(ctx, connect.NewRequest(&nodev1.GrantNodeSSHAccessRequest{
133142
ExternalNodeId: reg.ExternalNodeID,
134143
UserId: brevUser.ID,

0 commit comments

Comments
 (0)