Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New database plugin API to reload by plugin name #24472

Merged
merged 5 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions builtin/logical/database/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func Backend(conf *logical.BackendConfig) *databaseBackend {
pathListPluginConnection(&b),
pathConfigurePluginConnection(&b),
pathResetConnection(&b),
pathReloadPlugin(&b),
},
pathListRoles(&b),
pathRoles(&b),
Expand Down
43 changes: 32 additions & 11 deletions builtin/logical/database/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -717,17 +717,38 @@ func TestBackend_connectionCrud(t *testing.T) {
t.Fatal(diff)
}

// Reset Connection
data = map[string]interface{}{}
req = &logical.Request{
Operation: logical.UpdateOperation,
Path: "reset/plugin-test",
Storage: config.StorageView,
Data: data,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
// Test endpoints for reloading plugins.
for _, reloadPath := range []string{
"reset/plugin-test",
"reload/postgresql-database-plugin",
} {
dbBackend, ok := b.(*databaseBackend)
if !ok {
t.Fatal("could not convert logical.Backend to databaseBackend")
}
dbi := dbBackend.connections.Get("plugin-test")
if dbi == nil {
t.Fatal("no plugin-test dbi")
}
initialID := dbi.ID()
data = map[string]interface{}{}
req = &logical.Request{
Operation: logical.UpdateOperation,
Path: reloadPath,
Storage: config.StorageView,
Data: data,
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
}
dbi = dbBackend.connections.Get("plugin-test")
if dbi == nil {
t.Fatal("no plugin-test dbi")
}
if initialID == dbi.ID() {
t.Fatal("ID unchanged after connection reset")
}
}

// Get creds
Expand Down
89 changes: 85 additions & 4 deletions builtin/logical/database/path_config_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,87 @@ func (b *databaseBackend) pathConnectionReset() framework.OperationFunc {
return logical.ErrorResponse(respErrEmptyName), nil
}

// Close plugin and delete the entry in the connections cache.
if err := b.ClearConnection(name); err != nil {
if err := b.reloadConnection(ctx, name, req.Storage); err != nil {
return nil, err
}

// Execute plugin again, we don't need the object so throw away.
if _, err := b.GetConnection(ctx, req.Storage, name); err != nil {
return nil, nil
}
}

func (b *databaseBackend) reloadConnection(ctx context.Context, name string, storage logical.Storage) error {
// Close plugin and delete the entry in the connections cache.
if err := b.ClearConnection(name); err != nil {
return err
}

// Execute plugin again, we don't need the object so throw away.
if _, err := b.GetConnection(ctx, storage, name); err != nil {
return err
}

return nil
}

// pathReloadPlugin reloads all connections using a named plugin.
func pathReloadPlugin(b *databaseBackend) *framework.Path {
return &framework.Path{
Pattern: fmt.Sprintf("reload/%s", framework.GenericNameRegex("plugin_name")),

DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixDatabase,
OperationVerb: "reload",
OperationSuffix: "plugin",
},

Fields: map[string]*framework.FieldSchema{
"plugin_name": {
Type: framework.TypeString,
Description: "Name of the database plugin",
},
},

Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: b.reloadPlugin(),
},

HelpSynopsis: pathResetConnectionHelpSyn,
HelpDescription: pathResetConnectionHelpDesc,
tomhjp marked this conversation as resolved.
Show resolved Hide resolved
}
}

// reloadPlugin reloads all instances of a named plugin by closing the existing
// instances and creating new ones.
func (b *databaseBackend) reloadPlugin() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
pluginName := data.Get("plugin_name").(string)
if pluginName == "" {
return logical.ErrorResponse(respErrEmptyPluginName), nil
}

connNames, err := req.Storage.List(ctx, "config/")
if err != nil {
return nil, err
}
for _, connName := range connNames {
entry, err := req.Storage.Get(ctx, fmt.Sprintf("config/%s", connName))
if err != nil {
return nil, fmt.Errorf("failed to read connection configuration: %w", err)
}
if entry == nil {
continue
}

var config DatabaseConfig
if err := entry.DecodeJSON(&config); err != nil {
return nil, err
}
if config.PluginName == pluginName {
if err := b.reloadConnection(ctx, connName, req.Storage); err != nil {
return nil, err
}
}
}

return nil, nil
}
Expand Down Expand Up @@ -551,3 +623,12 @@ const pathResetConnectionHelpDesc = `
This path resets the database connection by closing the existing database plugin
instance and running a new one.
`

const pathReloadPluginHelpSyn = `
Reloads all connections using a named database plugin.
`

const pathReloadPluginHelpDesc = `
This path resets each database connection using a named plugin by closing each
existing database plugin instance and running a new one.
`
3 changes: 3 additions & 0 deletions changelog/24472.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
secrets/database: Add new reload/:plugin_name API to reload database plugins by name for a specific mount.
```
Loading