diff --git a/.changelog/11245.txt b/.changelog/11245.txt new file mode 100644 index 000000000000..b8ce6984d672 --- /dev/null +++ b/.changelog/11245.txt @@ -0,0 +1,3 @@ +```release-note:improvement +csi: add flag for providing secrets as a set of key/value pairs to delete a volume +``` 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..a9b3d604af29 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) } @@ -70,6 +73,8 @@ func (c *VolumeDeleteCommand) Name() string { return "volume delete" } func (c *VolumeDeleteCommand) Run(args []string) int { flags := c.Meta.FlagSet(c.Name(), FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } + var secrets string + flags.StringVar(&secrets, "secrets", "", "") if err := flags.Parse(args); err != nil { c.Ui.Error(fmt.Sprintf("Error parsing arguments %s", err)) @@ -78,8 +83,8 @@ func (c *VolumeDeleteCommand) Run(args []string) int { // Check that we get exactly two arguments args = flags.Args() - if l := len(args); l != 1 { - c.Ui.Error("This command takes one argument: ") + if l := len(args); l < 1 { + c.Ui.Error("This command takes at least one argument: ") c.Ui.Error(commandErrorText(c)) return 1 } @@ -92,7 +97,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..5b52cf906b5e 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 querySecrets { + 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