CSI ListSnapshots secrets implementation

Signed-off-by: Grant Griffiths <ggriffiths@purestorage.com>
This commit is contained in:
Grant Griffiths 2021-07-02 17:46:41 -07:00
parent 5c45d74f3c
commit fecbbaee22
12 changed files with 55 additions and 6 deletions

3
.changelog/10848.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 list snapshots
```

View File

@ -137,7 +137,7 @@ func (v *CSIVolumes) DeleteSnapshot(snap *CSISnapshot, w *WriteOptions) error {
} }
// ListSnapshots lists external storage volume snapshots. // ListSnapshots lists external storage volume snapshots.
func (v *CSIVolumes) ListSnapshots(pluginID string, q *QueryOptions) (*CSISnapshotListResponse, *QueryMeta, error) { func (v *CSIVolumes) ListSnapshots(pluginID string, secrets string, q *QueryOptions) (*CSISnapshotListResponse, *QueryMeta, error) {
var resp *CSISnapshotListResponse var resp *CSISnapshotListResponse
qp := url.Values{} qp := url.Values{}
@ -150,6 +150,9 @@ func (v *CSIVolumes) ListSnapshots(pluginID string, q *QueryOptions) (*CSISnapsh
if q.PerPage != 0 { if q.PerPage != 0 {
qp.Set("per_page", fmt.Sprint(q.PerPage)) qp.Set("per_page", fmt.Sprint(q.PerPage))
} }
if secrets != "" {
qp.Set("secrets", secrets)
}
qm, err := v.client.query("/v1/volumes/snapshot?"+qp.Encode(), &resp, q) qm, err := v.client.query("/v1/volumes/snapshot?"+qp.Encode(), &resp, q)
if err != nil { if err != nil {
@ -406,6 +409,7 @@ type CSISnapshotCreateResponse struct {
// fields // fields
type CSISnapshotListRequest struct { type CSISnapshotListRequest struct {
PluginID string PluginID string
Secrets CSISecrets
QueryOptions QueryOptions
} }

View File

@ -840,6 +840,10 @@ func TestCSIController_ListSnapshots(t *testing.T) {
CSIControllerQuery: structs.CSIControllerQuery{ CSIControllerQuery: structs.CSIControllerQuery{
PluginID: fakePlugin.Name, PluginID: fakePlugin.Name,
}, },
Secrets: map[string]string{
"secret-key-1": "secret-val-1",
"secret-key-2": "secret-val-2",
},
StartingToken: "1", StartingToken: "1",
MaxEntries: 100, MaxEntries: 100,
}, },

View File

@ -362,6 +362,7 @@ type ClientCSIControllerListSnapshotsRequest struct {
// not Nomad's own fields, for clarity when mapping between the two RPCs // not Nomad's own fields, for clarity when mapping between the two RPCs
MaxEntries int32 MaxEntries int32
StartingToken string StartingToken string
Secrets structs.CSISecrets
CSIControllerQuery CSIControllerQuery
} }
@ -370,6 +371,7 @@ func (req *ClientCSIControllerListSnapshotsRequest) ToCSIRequest() *csi.Controll
return &csi.ControllerListSnapshotsRequest{ return &csi.ControllerListSnapshotsRequest{
MaxEntries: req.MaxEntries, MaxEntries: req.MaxEntries,
StartingToken: req.StartingToken, StartingToken: req.StartingToken,
Secrets: req.Secrets,
} }
} }

View File

@ -333,6 +333,19 @@ 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")
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]
}
}
}
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 {

View File

@ -35,6 +35,8 @@ List Options:
-plugin: Display only snapshots managed by a particular plugin. By default -plugin: Display only snapshots managed by a particular plugin. By default
this command will query all plugins for their snapshots. this command will query all plugins for their snapshots.
-secrets: A set of key/value secrets to be used when listing snapshots.
` `
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
@ -68,11 +70,13 @@ func (c *VolumeSnapshotListCommand) Name() string { return "volume snapshot list
func (c *VolumeSnapshotListCommand) Run(args []string) int { func (c *VolumeSnapshotListCommand) Run(args []string) int {
var pluginID string var pluginID string
var verbose bool var verbose bool
var secrets string
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.StringVar(&pluginID, "plugin", "", "") flags.StringVar(&pluginID, "plugin", "", "")
flags.BoolVar(&verbose, "verbose", false, "") flags.BoolVar(&verbose, "verbose", false, "")
flags.StringVar(&secrets, "secrets", "", "")
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))
@ -121,7 +125,7 @@ func (c *VolumeSnapshotListCommand) Run(args []string) int {
q := &api.QueryOptions{PerPage: 30} // TODO: tune page size q := &api.QueryOptions{PerPage: 30} // TODO: tune page size
for { for {
resp, _, err := client.CSIVolumes().ListSnapshots(pluginID, q) resp, _, err := client.CSIVolumes().ListSnapshots(pluginID, secrets, q)
if err != nil && !errors.Is(err, io.EOF) { if err != nil && !errors.Is(err, io.EOF) {
c.Ui.Error(fmt.Sprintf( c.Ui.Error(fmt.Sprintf(
"Error querying CSI external snapshots for plugin %q: %s", pluginID, err)) "Error querying CSI external snapshots for plugin %q: %s", pluginID, err))

View File

@ -1247,6 +1247,7 @@ func (v *CSIVolume) ListSnapshots(args *structs.CSISnapshotListRequest, reply *s
cReq := &cstructs.ClientCSIControllerListSnapshotsRequest{ cReq := &cstructs.ClientCSIControllerListSnapshotsRequest{
MaxEntries: args.PerPage, MaxEntries: args.PerPage,
StartingToken: args.NextToken, StartingToken: args.NextToken,
Secrets: args.Secrets,
} }
cReq.PluginID = plugin.ID cReq.PluginID = plugin.ID
cResp := &cstructs.ClientCSIControllerListSnapshotsResponse{} cResp := &cstructs.ClientCSIControllerListSnapshotsResponse{}

View File

@ -1280,6 +1280,9 @@ func TestCSIVolumeEndpoint_ListSnapshots(t *testing.T) {
// List snapshots // List snapshots
req := &structs.CSISnapshotListRequest{ req := &structs.CSISnapshotListRequest{
Secrets: structs.CSISecrets{
"secret-key-1": "secret-val-1",
},
QueryOptions: structs.QueryOptions{ QueryOptions: structs.QueryOptions{
Region: "global", Region: "global",
Namespace: structs.DefaultNamespace, Namespace: structs.DefaultNamespace,

View File

@ -897,6 +897,7 @@ type CSISnapshotDeleteResponse struct {
// fields // fields
type CSISnapshotListRequest struct { type CSISnapshotListRequest struct {
PluginID string PluginID string
Secrets CSISecrets
QueryOptions QueryOptions
} }

View File

@ -756,12 +756,14 @@ func (r *ControllerDeleteSnapshotRequest) Validate() error {
type ControllerListSnapshotsRequest struct { type ControllerListSnapshotsRequest struct {
MaxEntries int32 MaxEntries int32
StartingToken string StartingToken string
Secrets structs.CSISecrets
} }
func (r *ControllerListSnapshotsRequest) ToCSIRepresentation() *csipbv1.ListSnapshotsRequest { func (r *ControllerListSnapshotsRequest) ToCSIRepresentation() *csipbv1.ListSnapshotsRequest {
return &csipbv1.ListSnapshotsRequest{ return &csipbv1.ListSnapshotsRequest{
MaxEntries: r.MaxEntries, MaxEntries: r.MaxEntries,
StartingToken: r.StartingToken, StartingToken: r.StartingToken,
Secrets: r.Secrets,
} }
} }

View File

@ -714,12 +714,15 @@ 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 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
``` ```
### Sample Response ### Sample Response

View File

@ -15,7 +15,7 @@ command.
## Usage ## Usage
```plaintext ```plaintext
nomad volume snapshot list [-plugin plugin_id] nomad volume snapshot list [-plugin plugin_id -secrets key=value]
``` ```
The `volume snapshot list` command returns a list of snapshots along with their The `volume snapshot list` command returns a list of snapshots along with their
@ -27,7 +27,7 @@ Nomad.
@include 'general_options.mdx' @include 'general_options.mdx'
## Status Options ## List Options
- `-plugin`: Display only snapshots managed by a particular [CSI - `-plugin`: Display only snapshots managed by a particular [CSI
plugin][csi_plugin]. By default the `snapshot list` command will query all plugin][csi_plugin]. By default the `snapshot list` command will query all
@ -35,6 +35,8 @@ Nomad.
there is an exact match based on the provided plugin, then that specific there is an exact match based on the provided plugin, then that specific
plugin will be queried. Otherwise, a list of matching plugins will be plugin will be queried. Otherwise, a list of matching plugins will be
displayed. displayed.
- `-secrets`: A list of comma separated secret key/value pairs to be passed
to the CSI driver.
When ACLs are enabled, this command requires a token with the When ACLs are enabled, this command requires a token with the
`csi-list-volumes` capability for the plugin's namespace. `csi-list-volumes` capability for the plugin's namespace.
@ -50,6 +52,13 @@ snap-12345 vol-abcdef 50GiB 2021-01-03T12:15:02Z true
snap-67890 vol-fedcba 50GiB 2021-01-04T15:45:00Z true snap-67890 vol-fedcba 50GiB 2021-01-04T15:45:00Z true
``` ```
List volume snapshots with two secret key/value pairs:
```shell-session
$ nomad volume snapshot list -secrets key1=value1,key2=val2
Snapshot ID External ID Size Creation Time Ready?
snap-12345 vol-abcdef 50GiB 2021-01-03T12:15:02Z true
```
[csi]: https://github.com/container-storage-interface/spec [csi]: https://github.com/container-storage-interface/spec
[csi_plugin]: /docs/job-specification/csi_plugin [csi_plugin]: /docs/job-specification/csi_plugin
[registered]: /docs/commands/volume/register [registered]: /docs/commands/volume/register