Skip to content

Commit 080a371

Browse files
authored
feat(block): add snapshot wait command (#4455)
1 parent cb06435 commit 080a371

19 files changed

+1338
-5
lines changed

cmd/scw/testdata/test-all-usage-block-snapshot-create-usage.golden

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ ARGS:
1515

1616
FLAGS:
1717
-h, --help help for create
18+
-w, --wait wait until the snapshot is ready
1819

1920
GLOBAL FLAGS:
2021
-c, --config string The path to the config file

cmd/scw/testdata/test-all-usage-block-snapshot-usage.golden

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ AVAILABLE COMMANDS:
1414
list List all snapshots
1515
update Update a snapshot
1616

17+
WORKFLOW COMMANDS:
18+
wait Wait for snapshot to reach a stable state
19+
1720
FLAGS:
1821
-h, --help help for snapshot
1922

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
2+
🟥🟥🟥 STDERR️️ 🟥🟥🟥️
3+
Wait for snapshot to reach a stable state. This is similar to using --wait flag on other action commands, but without requiring a new action on the snapshot.
4+
5+
USAGE:
6+
scw block snapshot wait <snapshot-id ...> [arg=value ...]
7+
8+
EXAMPLES:
9+
Wait for a snapshot to be available
10+
scw block snapshot wait 11111111-1111-1111-1111-111111111111 terminal-status=available
11+
12+
ARGS:
13+
[timeout=5m0s] Timeout of the wait
14+
snapshot-id ID of the snapshot affected by the action.
15+
[terminal-status] Expected terminal status, will wait until this status is reached. (unknown_status | creating | available | error | deleting | deleted | in_use | locked | exporting)
16+
[zone=fr-par-1] Zone to target. If none is passed will use default zone from the config (fr-par-1 | fr-par-2 | fr-par-3 | nl-ams-1 | nl-ams-2 | nl-ams-3 | pl-waw-1 | pl-waw-2 | pl-waw-3)
17+
18+
FLAGS:
19+
-h, --help help for wait
20+
21+
GLOBAL FLAGS:
22+
-c, --config string The path to the config file
23+
-D, --debug Enable debug mode
24+
-o, --output string Output format: json or human, see 'scw help output' for more info (default "human")
25+
-p, --profile string The config profile to use

cmd/scw/testdata/test-all-usage-block-volume-create-usage.golden

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ ARGS:
1818

1919
FLAGS:
2020
-h, --help help for create
21+
-w, --wait wait until the volume is ready
2122

2223
GLOBAL FLAGS:
2324
-c, --config string The path to the config file

docs/commands/block.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This API allows you to manage your Block Storage volumes.
1010
- [Import a snapshot from a Scaleway Object Storage bucket](#import-a-snapshot-from-a-scaleway-object-storage-bucket)
1111
- [List all snapshots](#list-all-snapshots)
1212
- [Update a snapshot](#update-a-snapshot)
13+
- [Wait for snapshot to reach a stable state](#wait-for-snapshot-to-reach-a-stable-state)
1314
- [A Block Storage volume is a logical storage drive on a network-connected storage system. It is exposed to Instances as if it were a physical disk, and can be attached and detached like a hard drive. Several Block volumes can be attached to one Instance at a time](#a-block-storage-volume-is-a-logical-storage-drive-on-a-network-connected-storage-system.-it-is-exposed-to-instances-as-if-it-were-a-physical-disk,-and-can-be-attached-and-detached-like-a-hard-drive.-several-block-volumes-can-be-attached-to-one-instance-at-a-time)
1415
- [Create a volume](#create-a-volume)
1516
- [Delete a detached volume](#delete-a-detached-volume)
@@ -185,6 +186,38 @@ scw block snapshot update <snapshot-id ...> [arg=value ...]
185186

186187

187188

189+
### Wait for snapshot to reach a stable state
190+
191+
Wait for snapshot to reach a stable state. This is similar to using --wait flag on other action commands, but without requiring a new action on the snapshot.
192+
193+
**Usage:**
194+
195+
```
196+
scw block snapshot wait <snapshot-id ...> [arg=value ...]
197+
```
198+
199+
200+
**Args:**
201+
202+
| Name | | Description |
203+
|------|---|-------------|
204+
| timeout | Default: `5m0s` | Timeout of the wait |
205+
| snapshot-id | Required | ID of the snapshot affected by the action. |
206+
| terminal-status | One of: `unknown_status`, `creating`, `available`, `error`, `deleting`, `deleted`, `in_use`, `locked`, `exporting` | Expected terminal status, will wait until this status is reached. |
207+
| zone | Default: `fr-par-1`<br />One of: `fr-par-1`, `fr-par-2`, `fr-par-3`, `nl-ams-1`, `nl-ams-2`, `nl-ams-3`, `pl-waw-1`, `pl-waw-2`, `pl-waw-3` | Zone to target. If none is passed will use default zone from the config |
208+
209+
210+
**Examples:**
211+
212+
213+
Wait for a snapshot to be available
214+
```
215+
scw block snapshot wait 11111111-1111-1111-1111-111111111111 terminal-status=available
216+
```
217+
218+
219+
220+
188221
## A Block Storage volume is a logical storage drive on a network-connected storage system. It is exposed to Instances as if it were a physical disk, and can be attached and detached like a hard drive. Several Block volumes can be attached to one Instance at a time
189222

190223
Block volumes can be snapshotted, mounted or unmounted.

internal/namespaces/block/v1alpha1/custom.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ func GetCommands() *core.Commands {
4646
cmds := GetGeneratedCommands()
4747

4848
cmds.Add(volumeWaitCommand())
49+
cmds.Add(snapshotWaitCommand())
50+
51+
cmds.MustFind("block", "snapshot", "create").Override(blockSnapshotCreateBuilder)
52+
cmds.MustFind("block", "volume", "create").Override(blockVolumeCreateBuilder)
4953

5054
human.RegisterMarshalerFunc(block.VolumeStatus(""), human.EnumMarshalFunc(volumeStatusMarshalSpecs))
5155
human.RegisterMarshalerFunc(block.SnapshotStatus(""), human.EnumMarshalFunc(snapshotStatusMarshalSpecs))
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package block
2+
3+
import (
4+
"context"
5+
"reflect"
6+
"time"
7+
8+
"github.com/scaleway/scaleway-cli/v2/core"
9+
block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
10+
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
11+
"github.com/scaleway/scaleway-sdk-go/scw"
12+
)
13+
14+
const (
15+
snapshotActionTimeout = 5 * time.Minute
16+
)
17+
18+
type snapshotWaitRequest struct {
19+
Zone scw.Zone
20+
SnapshotID string
21+
Timeout time.Duration
22+
23+
TerminalStatus *block.SnapshotStatus
24+
}
25+
26+
func snapshotWaitCommand() *core.Command {
27+
snapshotsStatuses := block.SnapshotStatus("").Values()
28+
snapshotsStatusStrings := make([]string, len(snapshotsStatuses))
29+
for k, v := range snapshotsStatuses {
30+
snapshotsStatusStrings[k] = v.String()
31+
}
32+
33+
return &core.Command{
34+
Short: `Wait for snapshot to reach a stable state`,
35+
Long: `Wait for snapshot to reach a stable state. This is similar to using --wait flag on other action commands, but without requiring a new action on the snapshot.`,
36+
Namespace: "block",
37+
Resource: "snapshot",
38+
Verb: "wait",
39+
Groups: []string{"workflow"},
40+
ArgsType: reflect.TypeOf(snapshotWaitRequest{}),
41+
Run: func(ctx context.Context, argsI interface{}) (i interface{}, err error) {
42+
args := argsI.(*snapshotWaitRequest)
43+
44+
return block.NewAPI(core.ExtractClient(ctx)).WaitForSnapshot(&block.WaitForSnapshotRequest{
45+
Zone: args.Zone,
46+
SnapshotID: args.SnapshotID,
47+
Timeout: scw.TimeDurationPtr(args.Timeout),
48+
RetryInterval: core.DefaultRetryInterval,
49+
50+
TerminalStatus: args.TerminalStatus,
51+
})
52+
},
53+
ArgSpecs: core.ArgSpecs{
54+
core.WaitTimeoutArgSpec(snapshotActionTimeout),
55+
{
56+
Name: "snapshot-id",
57+
Short: `ID of the snapshot affected by the action.`,
58+
Required: true,
59+
Positional: true,
60+
},
61+
{
62+
Name: "terminal-status",
63+
Short: `Expected terminal status, will wait until this status is reached.`,
64+
EnumValues: snapshotsStatusStrings,
65+
},
66+
core.ZoneArgSpec((*instance.API)(nil).Zones()...),
67+
},
68+
Examples: []*core.Example{
69+
{
70+
Short: "Wait for a snapshot to be available",
71+
ArgsJSON: `{"snapshot_id": "11111111-1111-1111-1111-111111111111", "terminal_status": "available"}`,
72+
},
73+
},
74+
}
75+
}
76+
77+
func blockSnapshotCreateBuilder(c *core.Command) *core.Command {
78+
c.WaitFunc = func(ctx context.Context, _, respI interface{}) (interface{}, error) {
79+
resp := respI.(*block.Snapshot)
80+
81+
return block.NewAPI(core.ExtractClient(ctx)).WaitForSnapshot(&block.WaitForSnapshotRequest{
82+
SnapshotID: resp.ID,
83+
Zone: resp.Zone,
84+
})
85+
}
86+
87+
return c
88+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package block_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/scaleway/scaleway-cli/v2/core"
7+
block "github.com/scaleway/scaleway-cli/v2/internal/namespaces/block/v1alpha1"
8+
"github.com/scaleway/scaleway-cli/v2/internal/testhelpers"
9+
blockSDK "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func Test_SnapshotWait(t *testing.T) {
14+
t.Run("Wait command", core.Test(&core.TestConfig{
15+
Commands: block.GetCommands(),
16+
BeforeFunc: core.BeforeFuncCombine(
17+
core.ExecStoreBeforeCmd("Volume", "scw block volume create perf-iops=5000 from-empty.size=20GB -w"),
18+
core.ExecStoreBeforeCmd("Snapshot", "scw block snapshot create volume-id={{ .Volume.ID }}"),
19+
),
20+
Cmd: "scw block snapshot wait {{ .Snapshot.ID }}",
21+
Check: core.TestCheckCombine(
22+
core.TestCheckGolden(),
23+
core.TestCheckExitCode(0),
24+
),
25+
AfterFunc: core.AfterFuncCombine(
26+
deleteSnapshot("Snapshot"),
27+
deleteVolume("Volume"),
28+
),
29+
}))
30+
31+
t.Run("Wait flag", core.Test(&core.TestConfig{
32+
Commands: block.GetCommands(),
33+
BeforeFunc: core.ExecStoreBeforeCmd("Volume", "scw block volume create perf-iops=5000 from-empty.size=20GB -w"),
34+
Cmd: "scw block snapshot create volume-id={{ .Volume.ID }} -w",
35+
Check: core.TestCheckCombine(
36+
func(t *testing.T, ctx *core.CheckFuncCtx) {
37+
t.Helper()
38+
snap := testhelpers.Value[*blockSDK.Snapshot](t, ctx.Result)
39+
require.Equal(t, blockSDK.SnapshotStatusAvailable, snap.Status)
40+
},
41+
core.TestCheckGolden(),
42+
core.TestCheckExitCode(0),
43+
),
44+
AfterFunc: core.AfterFuncCombine(
45+
deleteSnapshot("CmdResult"),
46+
deleteVolume("Volume"),
47+
),
48+
}))
49+
}

internal/namespaces/block/v1alpha1/custom_volume.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ type volumeWaitRequest struct {
2424
}
2525

2626
func volumeWaitCommand() *core.Command {
27-
terminalStatus := block.VolumeStatus("").Values()
28-
terminalStatusStrings := make([]string, len(terminalStatus))
29-
for k, v := range terminalStatus {
30-
terminalStatusStrings[k] = v.String()
27+
volumeStatuses := block.VolumeStatus("").Values()
28+
volumeStatusStrings := make([]string, len(volumeStatuses))
29+
for k, v := range volumeStatuses {
30+
volumeStatusStrings[k] = v.String()
3131
}
3232

3333
return &core.Command{
@@ -61,7 +61,7 @@ func volumeWaitCommand() *core.Command {
6161
{
6262
Name: "terminal-status",
6363
Short: `Expected terminal status, will wait until this status is reached.`,
64-
EnumValues: terminalStatusStrings,
64+
EnumValues: volumeStatusStrings,
6565
},
6666
core.ZoneArgSpec((*instance.API)(nil).Zones()...),
6767
},
@@ -73,3 +73,16 @@ func volumeWaitCommand() *core.Command {
7373
},
7474
}
7575
}
76+
77+
func blockVolumeCreateBuilder(c *core.Command) *core.Command {
78+
c.WaitFunc = func(ctx context.Context, _, respI interface{}) (interface{}, error) {
79+
resp := respI.(*block.Volume)
80+
81+
return block.NewAPI(core.ExtractClient(ctx)).WaitForVolume(&block.WaitForVolumeRequest{
82+
VolumeID: resp.ID,
83+
Zone: resp.Zone,
84+
})
85+
}
86+
87+
return c
88+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package block_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/scaleway/scaleway-cli/v2/core"
7+
block "github.com/scaleway/scaleway-cli/v2/internal/namespaces/block/v1alpha1"
8+
"github.com/scaleway/scaleway-cli/v2/internal/testhelpers"
9+
blockSDK "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func Test_VolumeWait(t *testing.T) {
14+
t.Run("Wait command", core.Test(&core.TestConfig{
15+
Commands: block.GetCommands(),
16+
BeforeFunc: core.BeforeFuncCombine(
17+
core.ExecStoreBeforeCmd("Volume", "scw block volume create perf-iops=5000 from-empty.size=20GB"),
18+
),
19+
Cmd: "scw block volume wait {{ .Volume.ID }}",
20+
Check: core.TestCheckCombine(
21+
core.TestCheckGolden(),
22+
core.TestCheckExitCode(0),
23+
),
24+
AfterFunc: deleteVolume("Volume"),
25+
}))
26+
27+
t.Run("Wait flag", core.Test(&core.TestConfig{
28+
Commands: block.GetCommands(),
29+
Cmd: "scw block volume create perf-iops=5000 from-empty.size=20GB -w",
30+
Check: core.TestCheckCombine(
31+
func(t *testing.T, ctx *core.CheckFuncCtx) {
32+
t.Helper()
33+
vol := testhelpers.Value[*blockSDK.Volume](t, ctx.Result)
34+
require.Equal(t, blockSDK.VolumeStatusAvailable, vol.Status)
35+
},
36+
core.TestCheckGolden(),
37+
core.TestCheckExitCode(0),
38+
),
39+
AfterFunc: deleteVolume("CmdResult"),
40+
}))
41+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package block_test
2+
3+
import "github.com/scaleway/scaleway-cli/v2/core"
4+
5+
// deleteVolume deletes a volume.
6+
// metaKey must be the key in meta where the volume state is stored.
7+
func deleteVolume(metaKey string) core.AfterFunc {
8+
return core.ExecAfterCmd("scw block volume delete {{." + metaKey + ".ID}}")
9+
}
10+
11+
// deleteSnapshot deletes a snapshot.
12+
// metaKey must be the key in meta where the volume state is stored.
13+
func deleteSnapshot(metaKey string) core.AfterFunc {
14+
return core.ExecAfterCmd("scw block snapshot delete {{." + metaKey + ".ID}}")
15+
}

0 commit comments

Comments
 (0)