CSI: Add secrets flag support for delete volume (#11245)

This commit is contained in:
Grant Griffiths 2022-04-05 07:59:11 -05:00 committed by GitHub
parent e7e8ce212e
commit 18a0a2c9a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 89 additions and 15 deletions

3
.changelog/11245.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
csi: add flag for providing secrets as a set of key/value pairs to delete a volume
```

View File

@ -99,6 +99,7 @@ func (v *CSIVolumes) Create(vol *CSIVolume, w *WriteOptions) ([]*CSIVolume, *Wri
return resp.Volumes, meta, err return resp.Volumes, meta, err
} }
// DEPRECATED: will be removed in Nomad 1.4.0
// Delete deletes a CSI volume from an external storage provider. The ID // Delete deletes a CSI volume from an external storage provider. The ID
// passed as an argument here is for the storage provider's ID, so a volume // passed as an argument here is for the storage provider's ID, so a volume
// that's already been deregistered can be deleted. // that's already been deregistered can be deleted.
@ -107,6 +108,19 @@ func (v *CSIVolumes) Delete(externalVolID string, w *WriteOptions) error {
return err return err
} }
// DeleteOpts deletes a CSI volume from an external storage
// provider. The ID passed in the request is for the storage
// provider's ID, so a volume that's already been deregistered can be
// deleted.
func (v *CSIVolumes) DeleteOpts(req *CSIVolumeDeleteRequest, w *WriteOptions) error {
if w == nil {
w = &WriteOptions{}
}
w.SetHeadersFromCSISecrets(req.Secrets)
_, err := v.client.delete(fmt.Sprintf("/v1/volume/csi/%v/delete", url.PathEscape(req.ExternalVolumeID)), nil, w)
return err
}
// Detach causes Nomad to attempt to detach a CSI volume from a client // Detach causes Nomad to attempt to detach a CSI volume from a client
// node. This is used in the case that the node is temporarily lost and the // node. This is used in the case that the node is temporarily lost and the
// allocations are unable to drop their claims automatically. // allocations are unable to drop their claims automatically.
@ -422,6 +436,12 @@ type CSIVolumeDeregisterRequest struct {
WriteRequest WriteRequest
} }
type CSIVolumeDeleteRequest struct {
ExternalVolumeID string
Secrets CSISecrets
WriteRequest
}
// CSISnapshot is the storage provider's view of a volume snapshot // CSISnapshot is the storage provider's view of a volume snapshot
type CSISnapshot struct { type CSISnapshot struct {
ID string // storage provider's ID ID string // storage provider's ID

View File

@ -223,8 +223,10 @@ func (s *HTTPServer) csiVolumeDelete(id string, resp http.ResponseWriter, req *h
return nil, CodedError(405, ErrInvalidMethod) return nil, CodedError(405, ErrInvalidMethod)
} }
secrets := parseCSISecrets(req)
args := structs.CSIVolumeDeleteRequest{ args := structs.CSIVolumeDeleteRequest{
VolumeIDs: []string{id}, VolumeIDs: []string{id},
Secrets: secrets,
} }
s.parseWriteRequest(req, &args.WriteRequest) s.parseWriteRequest(req, &args.WriteRequest)
@ -329,10 +331,8 @@ func (s *HTTPServer) csiSnapshotList(resp http.ResponseWriter, req *http.Request
query := req.URL.Query() query := req.URL.Query()
args.PluginID = query.Get("plugin_id") args.PluginID = query.Get("plugin_id")
secrets := parseCSISecrets(req) secrets := parseCSISecrets(req)
args.Secrets = secrets args.Secrets = secrets
var out structs.CSISnapshotListResponse var out structs.CSISnapshotListResponse
if err := s.agent.RPC("CSIVolume.ListSnapshots", &args, &out); err != nil { if err := s.agent.RPC("CSIVolume.ListSnapshots", &args, &out); err != nil {
return nil, err return nil, err

View File

@ -4,12 +4,15 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/api/contexts" "github.com/hashicorp/nomad/api/contexts"
flaghelper "github.com/hashicorp/nomad/helper/flags"
"github.com/posener/complete" "github.com/posener/complete"
) )
type VolumeDeleteCommand struct { type VolumeDeleteCommand struct {
Meta Meta
Secrets string
} }
func (c *VolumeDeleteCommand) Help() string { func (c *VolumeDeleteCommand) Help() string {
@ -30,6 +33,11 @@ General Options:
` + generalOptionsUsage(usageOptsDefault) + ` ` + generalOptionsUsage(usageOptsDefault) + `
Delete Options:
-secret
Secrets to pass to the plugin to delete the snapshot. Accepts multiple
flags in the form -secret key=value
` `
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
@ -68,8 +76,10 @@ func (c *VolumeDeleteCommand) Synopsis() string {
func (c *VolumeDeleteCommand) Name() string { return "volume delete" } func (c *VolumeDeleteCommand) Name() string { return "volume delete" }
func (c *VolumeDeleteCommand) Run(args []string) int { func (c *VolumeDeleteCommand) Run(args []string) int {
var secretsArgs flaghelper.StringFlag
flags := c.Meta.FlagSet(c.Name(), FlagSetClient) flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) } flags.Usage = func() { c.Ui.Output(c.Help()) }
flags.Var(&secretsArgs, "secret", "secrets for snapshot, ex. -secret key=value")
if err := flags.Parse(args); err != nil { if err := flags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing arguments %s", err)) c.Ui.Error(fmt.Sprintf("Error parsing arguments %s", err))
@ -78,8 +88,8 @@ func (c *VolumeDeleteCommand) Run(args []string) int {
// Check that we get exactly two arguments // Check that we get exactly two arguments
args = flags.Args() args = flags.Args()
if l := len(args); l != 1 { if l := len(args); l < 1 {
c.Ui.Error("This command takes one argument: <vol id>") c.Ui.Error("This command takes at least one argument: <vol id>")
c.Ui.Error(commandErrorText(c)) c.Ui.Error(commandErrorText(c))
return 1 return 1
} }
@ -92,7 +102,21 @@ func (c *VolumeDeleteCommand) Run(args []string) int {
return 1 return 1
} }
err = client.CSIVolumes().Delete(volID, nil) secrets := api.CSISecrets{}
for _, kv := range secretsArgs {
s := strings.Split(kv, "=")
if len(s) == 2 {
secrets[s[0]] = s[1]
} else {
c.Ui.Error("Secret must be in the format: -secret key=value")
return 1
}
}
err = client.CSIVolumes().DeleteOpts(&api.CSIVolumeDeleteRequest{
ExternalVolumeID: volID,
Secrets: secrets,
}, nil)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Error deleting volume: %s", err)) c.Ui.Error(fmt.Sprintf("Error deleting volume: %s", err))
return 1 return 1

View File

@ -1048,7 +1048,7 @@ func (v *CSIVolume) Delete(args *structs.CSIVolumeDeleteRequest, reply *structs.
// NOTE: deleting the volume in the external storage provider can't be // NOTE: deleting the volume in the external storage provider can't be
// made atomic with deregistration. We can't delete a volume that's // made atomic with deregistration. We can't delete a volume that's
// not registered because we need to be able to lookup its plugin. // not registered because we need to be able to lookup its plugin.
err = v.deleteVolume(vol, plugin) err = v.deleteVolume(vol, plugin, args.Secrets)
if err != nil { if err != nil {
return err return err
} }
@ -1072,12 +1072,18 @@ func (v *CSIVolume) Delete(args *structs.CSIVolumeDeleteRequest, reply *structs.
return nil return nil
} }
func (v *CSIVolume) deleteVolume(vol *structs.CSIVolume, plugin *structs.CSIPlugin) error { func (v *CSIVolume) deleteVolume(vol *structs.CSIVolume, plugin *structs.CSIPlugin, querySecrets structs.CSISecrets) error {
// Combine volume and query secrets into one map.
// Query secrets override any secrets stored with the volume.
combinedSecrets := vol.Secrets
for k, v := range querySecrets {
combinedSecrets[k] = v
}
method := "ClientCSI.ControllerDeleteVolume" method := "ClientCSI.ControllerDeleteVolume"
cReq := &cstructs.ClientCSIControllerDeleteVolumeRequest{ cReq := &cstructs.ClientCSIControllerDeleteVolumeRequest{
ExternalVolumeID: vol.ExternalID, ExternalVolumeID: vol.ExternalID,
Secrets: vol.Secrets, Secrets: combinedSecrets,
} }
cReq.PluginID = plugin.ID cReq.PluginID = plugin.ID
cResp := &cstructs.ClientCSIControllerDeleteVolumeResponse{} cResp := &cstructs.ClientCSIControllerDeleteVolumeResponse{}

View File

@ -1183,6 +1183,9 @@ func TestCSIVolumeEndpoint_Delete(t *testing.T) {
Region: "global", Region: "global",
Namespace: ns, Namespace: ns,
}, },
Secrets: structs.CSISecrets{
"secret-key-1": "secret-val-1",
},
} }
resp1 := &structs.CSIVolumeCreateResponse{} resp1 := &structs.CSIVolumeCreateResponse{}
err = msgpackrpc.CallWithCodec(codec, "CSIVolume.Delete", req1, resp1) err = msgpackrpc.CallWithCodec(codec, "CSIVolume.Delete", req1, resp1)

View File

@ -879,6 +879,7 @@ type CSIVolumeCreateResponse struct {
type CSIVolumeDeleteRequest struct { type CSIVolumeDeleteRequest struct {
VolumeIDs []string VolumeIDs []string
Secrets CSISecrets
WriteRequest WriteRequest
} }

View File

@ -468,22 +468,26 @@ The table below shows this endpoint's support for
| ---------------- | ---------------------------- | | ---------------- | ---------------------------- |
| `NO` | `namespace:csi-write-volume` | | `NO` | `namespace:csi-write-volume` |
This endpoint accepts a `X-Nomad-CSI-Secrets` header to set secrets
for deleting the volume as comma-separated key-value pairs (see the
example below). These secrets will be merged with any secrets already
stored when the CSI volume was created.
### Parameters ### Parameters
- `:volume_id` `(string: <required>)` - Specifies the ID of the - `:volume_id` `(string: <required>)` - Specifies the ID of the
volume. This must be the full ID. This is specified as part of the volume. This must be the full ID. This is specified as part of the
path. path.
### Sample Request ### Sample Request
```shell-session ```shell-session
$ curl \ $ curl \
--request DELETE \ --request DELETE \
-H "X-Nomad-CSI-Secrets: secret-key-1=value-1,secret-key-2=value-2" \
https://localhost:4646/v1/volume/csi/volume-id1/delete https://localhost:4646/v1/volume/csi/volume-id1/delete
``` ```
## Detach Volume ## Detach Volume
This endpoint detaches an external volume from a Nomad client node. It is an This endpoint detaches an external volume from a Nomad client node. It is an
@ -676,6 +680,11 @@ The table below shows this endpoint's support for
| ---------------- | ---------------------------- | | ---------------- | ---------------------------- |
| `NO` | `namespace:csi-write-volume` | | `NO` | `namespace:csi-write-volume` |
This endpoint accepts a `X-Nomad-CSI-Secrets` header to set secrets
for deleting the snapshot as comma-separated key-value pairs (see the
example below). These secrets will be merged with any secrets already
stored when the CSI snapshot was created.
### Parameters ### Parameters
- `plugin_id` `(string: <required>)` - Specifies the prefix of a CSI plugin ID - `plugin_id` `(string: <required>)` - Specifies the prefix of a CSI plugin ID
@ -691,6 +700,7 @@ The table below shows this endpoint's support for
```shell-session ```shell-session
$ curl \ $ curl \
--request DELETE \ --request DELETE \
-H "X-Nomad-CSI-Secrets: secret-key-1=value-1,secret-key-2=value-2" \
https://localhost:4646/v1/volumes/snapshot https://localhost:4646/v1/volumes/snapshot
``` ```
@ -713,6 +723,11 @@ The table below shows this endpoint's support for
| ---------------- | --------------------------- | | ---------------- | --------------------------- |
| `YES` | `namespace:csi-list-volume` | | `YES` | `namespace:csi-list-volume` |
This endpoint accepts a `X-Nomad-CSI-Secrets` header to set secrets
for deleting the snapshot as comma-separated key-value pairs (see the
example below). These secrets will be merged with any secrets already
stored when the CSI snapshot was created.
### Parameters ### Parameters
- `plugin_id` `(string: <required>)` - Specifies the prefix of a CSI plugin ID - `plugin_id` `(string: <required>)` - Specifies the prefix of a CSI plugin ID
@ -728,15 +743,12 @@ The table below shows this endpoint's support for
return for this request. The response will include a `NextToken` field that return for this request. The response will include a `NextToken` field that
can be passed to the next request to fetch additional pages. can be passed to the next request to fetch additional pages.
- `secrets` `(string: "")` - Specifies a list of key/value secrets for listing snapshots.
These key/value pairs are comma-separated and are passed directly to the CSI plugin.
### Sample Request ### Sample Request
```shell-session ```shell-session
$ curl \ $ curl \
https://localhost:4646/v1/volumes/snapshot?plugin_id=plugin-id1&per_page=2& \ -H "X-Nomad-CSI-Secrets: secret-key-1=value-1,secret-key-2=value-2" \
secrets=secret-key-1=secret-value-1,secret-key-2=secret-value-2 https://localhost:4646/v1/volumes/snapshot?plugin_id=plugin-id1&per_page=2
``` ```
### Sample Response ### Sample Response

View File

@ -36,3 +36,8 @@ When ACLs are enabled, this command requires a token with the
[csi_plugins_internals]: /docs/internals/plugins/csi#csi-plugins [csi_plugins_internals]: /docs/internals/plugins/csi#csi-plugins
[deregistered]: /docs/commands/volume/deregister [deregistered]: /docs/commands/volume/deregister
[registered]: /docs/commands/volume/register [registered]: /docs/commands/volume/register
## Delete Options
- `-secret`: Secrets to pass to the plugin to delete the
snapshot. Accepts multiple flags in the form `-secret key=value`