diff --git a/.changelog/11245.txt b/.changelog/11245.txt new file mode 100644 index 000000000..b8ce6984d --- /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 829e3a251..b42202b67 100644 --- a/api/csi.go +++ b/api/csi.go @@ -99,6 +99,7 @@ func (v *CSIVolumes) Create(vol *CSIVolume, w *WriteOptions) ([]*CSIVolume, *Wri 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 // passed as an argument here is for the storage provider's ID, so a volume // that's already been deregistered can be deleted. @@ -107,6 +108,19 @@ func (v *CSIVolumes) Delete(externalVolID string, w *WriteOptions) error { 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 // node. This is used in the case that the node is temporarily lost and the // allocations are unable to drop their claims automatically. @@ -422,6 +436,12 @@ type CSIVolumeDeregisterRequest struct { WriteRequest } +type CSIVolumeDeleteRequest struct { + ExternalVolumeID string + Secrets CSISecrets + WriteRequest +} + // CSISnapshot is the storage provider's view of a volume snapshot type CSISnapshot struct { ID string // storage provider's ID diff --git a/command/agent/csi_endpoint.go b/command/agent/csi_endpoint.go index 8ecc989ba..75cc54dc5 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) } + secrets := parseCSISecrets(req) args := structs.CSIVolumeDeleteRequest{ VolumeIDs: []string{id}, + Secrets: secrets, } s.parseWriteRequest(req, &args.WriteRequest) @@ -329,10 +331,8 @@ func (s *HTTPServer) csiSnapshotList(resp http.ResponseWriter, req *http.Request query := req.URL.Query() args.PluginID = query.Get("plugin_id") - secrets := parseCSISecrets(req) args.Secrets = secrets - var out structs.CSISnapshotListResponse if err := s.agent.RPC("CSIVolume.ListSnapshots", &args, &out); err != nil { return nil, err diff --git a/command/volume_delete.go b/command/volume_delete.go index d965de9a9..6b37d419b 100644 --- a/command/volume_delete.go +++ b/command/volume_delete.go @@ -4,12 +4,15 @@ import ( "fmt" "strings" + "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/api/contexts" + flaghelper "github.com/hashicorp/nomad/helper/flags" "github.com/posener/complete" ) type VolumeDeleteCommand struct { Meta + Secrets string } func (c *VolumeDeleteCommand) Help() string { @@ -30,6 +33,11 @@ General Options: ` + 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) } @@ -68,8 +76,10 @@ func (c *VolumeDeleteCommand) Synopsis() string { func (c *VolumeDeleteCommand) Name() string { return "volume delete" } func (c *VolumeDeleteCommand) Run(args []string) int { + var secretsArgs flaghelper.StringFlag flags := c.Meta.FlagSet(c.Name(), FlagSetClient) 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 { 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 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 +102,21 @@ func (c *VolumeDeleteCommand) Run(args []string) int { 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 { 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 e10dfbef9..89a0af1d5 100644 --- a/nomad/csi_endpoint.go +++ b/nomad/csi_endpoint.go @@ -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 // 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 } @@ -1072,12 +1072,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 19cd30d01..84fae9a48 100644 --- a/nomad/csi_endpoint_test.go +++ b/nomad/csi_endpoint_test.go @@ -1183,6 +1183,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 0a97cd9e5..9dfefaa84 100644 --- a/nomad/structs/csi.go +++ b/nomad/structs/csi.go @@ -879,6 +879,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 cd810dc3f..0b5348e42 100644 --- a/website/content/api-docs/volumes.mdx +++ b/website/content/api-docs/volumes.mdx @@ -468,22 +468,26 @@ The table below shows this endpoint's support for | ---------------- | ---------------------------- | | `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 - `:volume_id` `(string: )` - Specifies the ID of the volume. This must be the full ID. This is specified as part of the path. - ### Sample Request ```shell-session $ curl \ --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 ``` - ## Detach Volume 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` | +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 - `plugin_id` `(string: )` - Specifies the prefix of a CSI plugin ID @@ -691,6 +700,7 @@ The table below shows this endpoint's support for ```shell-session $ curl \ --request DELETE \ + -H "X-Nomad-CSI-Secrets: secret-key-1=value-1,secret-key-2=value-2" \ https://localhost:4646/v1/volumes/snapshot ``` @@ -713,6 +723,11 @@ The table below shows this endpoint's support for | ---------------- | --------------------------- | | `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 - `plugin_id` `(string: )` - 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 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 ```shell-session $ curl \ - https://localhost:4646/v1/volumes/snapshot?plugin_id=plugin-id1&per_page=2& \ - secrets=secret-key-1=secret-value-1,secret-key-2=secret-value-2 + -H "X-Nomad-CSI-Secrets: secret-key-1=value-1,secret-key-2=value-2" \ + https://localhost:4646/v1/volumes/snapshot?plugin_id=plugin-id1&per_page=2 ``` ### Sample Response diff --git a/website/content/docs/commands/volume/delete.mdx b/website/content/docs/commands/volume/delete.mdx index 35213d68e..0d8d928c5 100644 --- a/website/content/docs/commands/volume/delete.mdx +++ b/website/content/docs/commands/volume/delete.mdx @@ -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 + +- `-secret`: Secrets to pass to the plugin to delete the + snapshot. Accepts multiple flags in the form `-secret key=value`