diff --git a/api/csi.go b/api/csi.go index 120c239fde8f..cc228233a7b8 100644 --- a/api/csi.go +++ b/api/csi.go @@ -101,8 +101,13 @@ func (v *CSIVolumes) Create(vol *CSIVolume, w *WriteOptions) ([]*CSIVolume, *Wri // 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 // that's already been deregistered can be deleted. -func (v *CSIVolumes) Delete(externalVolID string, w *WriteOptions) error { - _, err := v.client.delete(fmt.Sprintf("/v1/volume/csi/%v/delete", url.PathEscape(externalVolID)), nil, w) +func (v *CSIVolumes) Delete(externalVolID string, secrets string, w *WriteOptions) error { + qp := url.Values{} + if secrets != "" { + qp.Set("secrets", secrets) + } + + _, err := v.client.delete(fmt.Sprintf("/v1/volume/csi/%v/delete?%s", url.PathEscape(externalVolID), qp.Encode()), nil, w) return err } diff --git a/command/agent/csi_endpoint.go b/command/agent/csi_endpoint.go index 34e20e3bbe08..bcea6f5a2009 100644 --- a/command/agent/csi_endpoint.go +++ b/command/agent/csi_endpoint.go @@ -223,8 +223,10 @@ func (s *HTTPServer) csiVolumeDelete(id string, resp http.ResponseWriter, req *h return nil, CodedError(405, ErrInvalidMethod) } + query := req.URL.Query() args := structs.CSIVolumeDeleteRequest{ VolumeIDs: []string{id}, + Secrets: parseSecretsParam(query["secrets"]), } s.parseWriteRequest(req, &args.WriteRequest) @@ -333,20 +335,7 @@ func (s *HTTPServer) csiSnapshotList(resp http.ResponseWriter, req *http.Request query := req.URL.Query() args.PluginID = query.Get("plugin_id") - querySecrets := query["secrets"] - - // Parse comma separated secrets only when provided - if len(querySecrets) >= 1 { - secrets := strings.Split(querySecrets[0], ",") - args.Secrets = make(structs.CSISecrets) - for _, raw := range secrets { - secret := strings.Split(raw, "=") - if len(secret) == 2 { - args.Secrets[secret[0]] = secret[1] - } - } - } - + args.Secrets = parseSecretsParam(query["secrets"]) var out structs.CSISnapshotListResponse if err := s.agent.RPC("CSIVolume.ListSnapshots", &args, &out); err != nil { return nil, err @@ -763,3 +752,21 @@ func structsCSISecretsToApi(secrets structs.CSISecrets) api.CSISecrets { } return out } + +// parseSecretsParam parses a comma separated list of secrets +func parseSecretsParam(querySecrets []string) structs.CSISecrets { + csiSecrets := make(structs.CSISecrets) + + // Parse comma separated secrets only when provided + if len(querySecrets) >= 1 { + secrets := strings.Split(querySecrets[0], ",") + for _, raw := range secrets { + secret := strings.Split(raw, "=") + if len(secret) == 2 { + csiSecrets[secret[0]] = secret[1] + } + } + } + + return csiSecrets +} diff --git a/command/volume_delete.go b/command/volume_delete.go index d965de9a9854..2f9c435be32e 100644 --- a/command/volume_delete.go +++ b/command/volume_delete.go @@ -30,6 +30,9 @@ General Options: ` + generalOptionsUsage(usageOptsDefault) + ` +Delete Options: + + -secrets: A set of key/value secrets to be used when deleting a volume. ` return strings.TrimSpace(helpText) } @@ -85,6 +88,10 @@ func (c *VolumeDeleteCommand) Run(args []string) int { } volID := args[0] + // Parse secrets + var secrets string + flags.StringVar(&secrets, "secrets", "", "") + // Get the HTTP client client, err := c.Meta.Client() if err != nil { @@ -92,7 +99,7 @@ func (c *VolumeDeleteCommand) Run(args []string) int { return 1 } - err = client.CSIVolumes().Delete(volID, nil) + err = client.CSIVolumes().Delete(volID, secrets, nil) if err != nil { c.Ui.Error(fmt.Sprintf("Error deleting volume: %s", err)) return 1 diff --git a/nomad/csi_endpoint.go b/nomad/csi_endpoint.go index eb6954eed2f1..887b65e40ea7 100644 --- a/nomad/csi_endpoint.go +++ b/nomad/csi_endpoint.go @@ -969,7 +969,7 @@ func (v *CSIVolume) Delete(args *structs.CSIVolumeDeleteRequest, reply *structs. // NOTE: deleting the volume in the external storage provider can't be // made atomic with deregistration. We can't delete a volume that's // 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 { return err } @@ -993,12 +993,18 @@ func (v *CSIVolume) Delete(args *structs.CSIVolumeDeleteRequest, reply *structs. 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 vol.Secrets { + combinedSecrets[k] = v + } method := "ClientCSI.ControllerDeleteVolume" cReq := &cstructs.ClientCSIControllerDeleteVolumeRequest{ ExternalVolumeID: vol.ExternalID, - Secrets: vol.Secrets, + Secrets: combinedSecrets, } cReq.PluginID = plugin.ID cResp := &cstructs.ClientCSIControllerDeleteVolumeResponse{} diff --git a/nomad/csi_endpoint_test.go b/nomad/csi_endpoint_test.go index 35cafb2696bd..fed7bef37fc6 100644 --- a/nomad/csi_endpoint_test.go +++ b/nomad/csi_endpoint_test.go @@ -891,6 +891,9 @@ func TestCSIVolumeEndpoint_Delete(t *testing.T) { Region: "global", Namespace: ns, }, + Secrets: structs.CSISecrets{ + "secret-key-1": "secret-val-1", + }, } resp1 := &structs.CSIVolumeCreateResponse{} err = msgpackrpc.CallWithCodec(codec, "CSIVolume.Delete", req1, resp1) diff --git a/nomad/structs/csi.go b/nomad/structs/csi.go index 539501301258..41d1eaaded86 100644 --- a/nomad/structs/csi.go +++ b/nomad/structs/csi.go @@ -719,6 +719,7 @@ type CSIVolumeCreateResponse struct { type CSIVolumeDeleteRequest struct { VolumeIDs []string + Secrets CSISecrets WriteRequest } diff --git a/website/content/api-docs/volumes.mdx b/website/content/api-docs/volumes.mdx index b0175ccf9a85..ef8f9404ae83 100644 --- a/website/content/api-docs/volumes.mdx +++ b/website/content/api-docs/volumes.mdx @@ -459,14 +459,16 @@ The table below shows this endpoint's support for - `:volume_id` `(string: )` - Specifies the ID of the volume. This must be the full ID. This is specified as part of the path. - +- `secrets` `(string: "")` - Specifies a list of key/value secrets for deleting a volume. + These key/value pairs are comma-separated and are passed directly to the CSI plugin. ### Sample Request ```shell-session $ curl \ --request DELETE \ - https://localhost:4646/v1/volume/csi/volume-id1/delete + https://localhost:4646/v1/volume/csi/volume-id1/delete? \ + secrets=secret-key-1=secret-value-1,secret-key-2=secret-value-2 ``` diff --git a/website/content/docs/commands/volume/delete.mdx b/website/content/docs/commands/volume/delete.mdx index 35213d68ea5c..42d40b30bcc1 100644 --- a/website/content/docs/commands/volume/delete.mdx +++ b/website/content/docs/commands/volume/delete.mdx @@ -16,7 +16,7 @@ deleted. ## Usage ```plaintext -nomad volume delete [options] [volume] +nomad volume delete [options] [volume] [-secrets key=value,key2=value2] ``` The `volume delete` command requires a single argument, specifying the ID of @@ -36,3 +36,8 @@ When ACLs are enabled, this command requires a token with the [csi_plugins_internals]: /docs/internals/plugins/csi#csi-plugins [deregistered]: /docs/commands/volume/deregister [registered]: /docs/commands/volume/register + +## Delete Options + +- `-secrets`: A list of comma separated secret key/value pairs to be passed + to the CSI driver. \ No newline at end of file