Skip to content

Commit 4671921

Browse files
committed
feat: engine management API
Signed-off-by: Johannes Großmann <grossmann.johannes@t-online.de>
1 parent a56837f commit 4671921

File tree

8 files changed

+1225
-519
lines changed

8 files changed

+1225
-519
lines changed

client/client.go

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ import (
2727
"github.com/docker/secrets-engine/x/api"
2828
healthv1 "github.com/docker/secrets-engine/x/api/health/v1"
2929
"github.com/docker/secrets-engine/x/api/health/v1/healthv1connect"
30+
pluginsv1 "github.com/docker/secrets-engine/x/api/plugins/v1"
31+
"github.com/docker/secrets-engine/x/api/plugins/v1/pluginsv1connect"
3032
"github.com/docker/secrets-engine/x/api/resolver"
31-
v1 "github.com/docker/secrets-engine/x/api/resolver/v1"
32-
"github.com/docker/secrets-engine/x/api/resolver/v1/resolverv1connect"
3333
"github.com/docker/secrets-engine/x/secrets"
3434
)
3535

@@ -83,7 +83,7 @@ func WithDialContext(dialContext func(ctx context.Context, network, addr string)
8383
// It is useful to set if there are hard-limits to when the client must wait
8484
// for the server to accept the request.
8585
//
86-
// A timout of 0 means no request timeout will be applied.
86+
// A timeout of 0 means no request timeout will be applied.
8787
// Negative durations are not allowed and will result in an error.
8888
func WithTimeout(timeout time.Duration) Option {
8989
return func(s *config) error {
@@ -120,9 +120,14 @@ type config struct {
120120
responseTimeout time.Duration
121121
}
122122

123+
var (
124+
_ Client = &client{}
125+
_ PluginManagement = &client{}
126+
)
127+
123128
type client struct {
124129
resolverClient secrets.Resolver
125-
listClient resolverv1connect.ListServiceClient
130+
engineClient pluginsv1connect.PluginManagementServiceClient
126131
versionClient healthv1connect.VersionServiceClient
127132
}
128133

@@ -158,8 +163,20 @@ type Client interface {
158163

159164
// Version returns the name and version reported by the daemon.
160165
Version(ctx context.Context) (DaemonVersion, error)
166+
}
161167

168+
type PluginManagement interface {
162169
ListPlugins(ctx context.Context) ([]PluginInfo, error)
170+
EnablePlugin(ctx context.Context, name string) error
171+
DisablePlugin(ctx context.Context, name string) error
172+
}
173+
174+
func PluginManagementFromClient(c Client) (PluginManagement, error) {
175+
m, ok := c.(PluginManagement)
176+
if !ok {
177+
return nil, errors.New("client does not implement PluginManagement")
178+
}
179+
return m, nil
163180
}
164181

165182
func isDialError(err error) bool {
@@ -209,14 +226,14 @@ func New(options ...Option) (Client, error) {
209226
}
210227
return &client{
211228
resolverClient: resolver.NewResolverClient(c),
212-
listClient: resolverv1connect.NewListServiceClient(c, "http://unix"),
229+
engineClient: pluginsv1connect.NewPluginManagementServiceClient(c, "http://unix"),
213230
versionClient: healthv1connect.NewVersionServiceClient(c, "http://unix"),
214231
}, nil
215232
}
216233

217234
func (c client) ListPlugins(ctx context.Context) ([]PluginInfo, error) {
218-
req := connect.NewRequest(v1.ListPluginsRequest_builder{}.Build())
219-
resp, err := c.listClient.ListPlugins(ctx, req)
235+
req := connect.NewRequest(pluginsv1.ListPluginsRequest_builder{}.Build())
236+
resp, err := c.engineClient.ListPlugins(ctx, req)
220237
if isDialError(err) {
221238
return nil, fmt.Errorf("%w: %w", ErrSecretsEngineNotAvailable, err)
222239
}
@@ -233,27 +250,56 @@ func (c client) ListPlugins(ctx context.Context) ([]PluginInfo, error) {
233250
if err != nil {
234251
continue
235252
}
236-
pattern, err := secrets.ParsePattern(item.GetPattern())
237-
if err != nil {
238-
continue
239-
}
240-
result = append(result, PluginInfo{
253+
info := PluginInfo{
241254
Name: name,
242255
Version: version,
243-
Pattern: pattern,
256+
Disabled: item.GetDisabled(),
244257
External: item.GetExternal(),
245258
Configurable: item.GetConfigurable(),
246-
})
259+
}
260+
if sp := item.GetSecretsProvider(); sp != nil {
261+
pattern, err := secrets.ParsePattern(sp.GetPattern())
262+
if err != nil {
263+
continue
264+
}
265+
info.SecretsProvider = &SecretsProviderMetadata{Pattern: pattern}
266+
}
267+
result = append(result, info)
247268
}
248269
return result, nil
249270
}
250271

272+
func (c client) EnablePlugin(ctx context.Context, name string) error {
273+
r := pluginsv1.EnablePluginRequest_builder{}.Build()
274+
r.SetName(name)
275+
_, err := c.engineClient.EnablePlugin(ctx, connect.NewRequest(r))
276+
if isDialError(err) {
277+
return fmt.Errorf("%w: %w", ErrSecretsEngineNotAvailable, err)
278+
}
279+
return err
280+
}
281+
282+
func (c client) DisablePlugin(ctx context.Context, name string) error {
283+
r := pluginsv1.DisablePluginRequest_builder{}.Build()
284+
r.SetName(name)
285+
_, err := c.engineClient.DisablePlugin(ctx, connect.NewRequest(r))
286+
if isDialError(err) {
287+
return fmt.Errorf("%w: %w", ErrSecretsEngineNotAvailable, err)
288+
}
289+
return err
290+
}
291+
251292
type PluginInfo struct {
252-
Name api.Name
253-
Version api.Version
254-
Pattern secrets.Pattern
255-
External bool
256-
Configurable bool
293+
Name api.Name
294+
Version api.Version
295+
Disabled bool
296+
External bool
297+
Configurable bool
298+
SecretsProvider *SecretsProviderMetadata
299+
}
300+
301+
type SecretsProviderMetadata struct {
302+
Pattern secrets.Pattern
257303
}
258304

259305
func dialFromPath(path string) dial {

client/client_test.go

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ import (
3131
"github.com/docker/secrets-engine/x/api"
3232
healthv1 "github.com/docker/secrets-engine/x/api/health/v1"
3333
"github.com/docker/secrets-engine/x/api/health/v1/healthv1connect"
34-
resolverv1 "github.com/docker/secrets-engine/x/api/resolver/v1"
35-
"github.com/docker/secrets-engine/x/api/resolver/v1/resolverv1connect"
34+
pluginsv1 "github.com/docker/secrets-engine/x/api/plugins/v1"
35+
"github.com/docker/secrets-engine/x/api/plugins/v1/pluginsv1connect"
3636
"github.com/docker/secrets-engine/x/secrets"
3737
"github.com/docker/secrets-engine/x/testhelper"
3838
)
@@ -60,14 +60,14 @@ func mockVersionEngine(t *testing.T, version, date, commitHash string) string {
6060
return socketPath
6161
}
6262

63-
var _ resolverv1connect.ListServiceHandler = &mockPluginsList{}
63+
var _ pluginsv1connect.PluginManagementServiceHandler = &mockPluginsList{}
6464

6565
type mockPluginsList struct {
6666
list []PluginInfo
6767
}
6868

69-
func (m mockPluginsList) ListPlugins(context.Context, *connect.Request[resolverv1.ListPluginsRequest]) (*connect.Response[resolverv1.ListPluginsResponse], error) {
70-
var plugins []*resolverv1.Plugin
69+
func (m mockPluginsList) ListPlugins(_ context.Context, _ *connect.Request[pluginsv1.ListPluginsRequest]) (*connect.Response[pluginsv1.ListPluginsResponse], error) {
70+
var plugins []*pluginsv1.Plugin
7171
for _, plugin := range m.list {
7272
var name string
7373
if plugin.Name != nil {
@@ -77,23 +77,33 @@ func (m mockPluginsList) ListPlugins(context.Context, *connect.Request[resolverv
7777
if plugin.Version != nil {
7878
version = plugin.Version.String()
7979
}
80-
var pattern string
81-
if plugin.Pattern != nil {
82-
pattern = plugin.Pattern.String()
83-
}
84-
plugins = append(plugins, resolverv1.Plugin_builder{
80+
b := pluginsv1.Plugin_builder{
8581
Name: proto.String(name),
8682
Version: proto.String(version),
87-
Pattern: proto.String(pattern),
83+
Disabled: proto.Bool(plugin.Disabled),
8884
External: proto.Bool(plugin.External),
8985
Configurable: proto.Bool(plugin.Configurable),
90-
}.Build())
86+
}
87+
if plugin.SecretsProvider != nil {
88+
b.SecretsProvider = pluginsv1.SecretsProvider_builder{
89+
Pattern: proto.String(plugin.SecretsProvider.Pattern.String()),
90+
}.Build()
91+
}
92+
plugins = append(plugins, b.Build())
9193
}
92-
return connect.NewResponse(resolverv1.ListPluginsResponse_builder{
94+
return connect.NewResponse(pluginsv1.ListPluginsResponse_builder{
9395
Plugins: plugins,
9496
}.Build()), nil
9597
}
9698

99+
func (m mockPluginsList) EnablePlugin(_ context.Context, _ *connect.Request[pluginsv1.EnablePluginRequest]) (*connect.Response[pluginsv1.EnablePluginResponse], error) {
100+
return connect.NewResponse(pluginsv1.EnablePluginResponse_builder{}.Build()), nil
101+
}
102+
103+
func (m mockPluginsList) DisablePlugin(_ context.Context, _ *connect.Request[pluginsv1.DisablePluginRequest]) (*connect.Response[pluginsv1.DisablePluginResponse], error) {
104+
return connect.NewResponse(pluginsv1.DisablePluginResponse_builder{}.Build()), nil
105+
}
106+
97107
type handler struct {
98108
pattern string
99109
handler http.Handler
@@ -133,7 +143,7 @@ func wrapHandler(pattern string, h http.Handler) handler {
133143
func mockListPluginsEngine(t *testing.T, plugins []PluginInfo) string {
134144
t.Helper()
135145
socketPath := testhelper.RandomShortSocketName()
136-
muxServer(t, socketPath, []handler{wrapHandler(resolverv1connect.NewListServiceHandler(&mockPluginsList{list: plugins}))})
146+
muxServer(t, socketPath, []handler{wrapHandler(pluginsv1connect.NewPluginManagementServiceHandler(&mockPluginsList{list: plugins}))})
137147
return socketPath
138148
}
139149

@@ -142,22 +152,25 @@ func Test_ListPlugins(t *testing.T) {
142152
t.Run("external and internal configurable plugins", func(t *testing.T) {
143153
plugins := []PluginInfo{
144154
{
145-
Name: api.MustNewName("foo"),
146-
Version: api.MustNewVersion("v1"),
147-
Pattern: secrets.MustParsePattern("**"),
148-
Configurable: true,
155+
Name: api.MustNewName("foo"),
156+
Version: api.MustNewVersion("v1"),
157+
SecretsProvider: &SecretsProviderMetadata{Pattern: secrets.MustParsePattern("**")},
158+
Configurable: true,
159+
Disabled: true,
149160
},
150161
{
151-
Name: api.MustNewName("bar"),
152-
Version: api.MustNewVersion("v1"),
153-
Pattern: secrets.MustParsePattern("**"),
154-
External: true,
162+
Name: api.MustNewName("bar"),
163+
Version: api.MustNewVersion("v1"),
164+
SecretsProvider: &SecretsProviderMetadata{Pattern: secrets.MustParsePattern("**")},
165+
External: true,
155166
},
156167
}
157168
socket := mockListPluginsEngine(t, plugins)
158169
client, err := New(WithSocketPath(socket))
159170
require.NoError(t, err)
160-
result, err := client.ListPlugins(t.Context())
171+
m, err := PluginManagementFromClient(client)
172+
require.NoError(t, err)
173+
result, err := m.ListPlugins(t.Context())
161174
require.NoError(t, err)
162175
assert.Equal(t, plugins, result)
163176
})
@@ -166,7 +179,9 @@ func Test_ListPlugins(t *testing.T) {
166179
socket := mockListPluginsEngine(t, plugins)
167180
client, err := New(WithSocketPath(socket))
168181
require.NoError(t, err)
169-
result, err := client.ListPlugins(t.Context())
182+
m, err := PluginManagementFromClient(client)
183+
require.NoError(t, err)
184+
result, err := m.ListPlugins(t.Context())
170185
require.NoError(t, err)
171186
assert.Empty(t, result)
172187
})
@@ -176,21 +191,24 @@ func Test_ListPlugins(t *testing.T) {
176191
Name: api.MustNewName("foo"),
177192
},
178193
{
179-
Name: api.MustNewName("bar"),
180-
Version: api.MustNewVersion("v1"),
181-
Pattern: secrets.MustParsePattern("**"),
194+
Name: api.MustNewName("bar"),
195+
Version: api.MustNewVersion("v1"),
196+
SecretsProvider: &SecretsProviderMetadata{Pattern: secrets.MustParsePattern("**")},
182197
},
183198
}
184199
socket := mockListPluginsEngine(t, plugins)
185200
client, err := New(WithSocketPath(socket))
186201
require.NoError(t, err)
187-
result, err := client.ListPlugins(t.Context())
202+
m, err := PluginManagementFromClient(client)
188203
require.NoError(t, err)
204+
result, err := m.ListPlugins(t.Context())
205+
require.NoError(t, err)
206+
189207
assert.Equal(t, []PluginInfo{
190208
{
191-
Name: api.MustNewName("bar"),
192-
Version: api.MustNewVersion("v1"),
193-
Pattern: secrets.MustParsePattern("**"),
209+
Name: api.MustNewName("bar"),
210+
Version: api.MustNewVersion("v1"),
211+
SecretsProvider: &SecretsProviderMetadata{Pattern: secrets.MustParsePattern("**")},
194212
},
195213
}, result)
196214
})
@@ -221,7 +239,9 @@ func TestSecretsEngineUnavailable(t *testing.T) {
221239
socketPath := testhelper.RandomShortSocketName()
222240
client, err := New(WithSocketPath(socketPath))
223241
require.NoError(t, err)
224-
_, err = client.ListPlugins(t.Context())
242+
m, err := PluginManagementFromClient(client)
243+
require.NoError(t, err)
244+
_, err = m.ListPlugins(t.Context())
225245
require.ErrorIs(t, err, ErrSecretsEngineNotAvailable)
226246
_, err = client.GetSecrets(t.Context(), secrets.MustParsePattern("**"))
227247
require.ErrorIs(t, err, ErrSecretsEngineNotAvailable)

0 commit comments

Comments
 (0)