Skip to content

Commit

Permalink
feat(block): add snapshot wait command (#4455)
Browse files Browse the repository at this point in the history
  • Loading branch information
Codelax authored Jan 23, 2025
1 parent cb06435 commit 080a371
Show file tree
Hide file tree
Showing 19 changed files with 1,338 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ ARGS:

FLAGS:
-h, --help help for create
-w, --wait wait until the snapshot is ready

GLOBAL FLAGS:
-c, --config string The path to the config file
Expand Down
3 changes: 3 additions & 0 deletions cmd/scw/testdata/test-all-usage-block-snapshot-usage.golden
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ AVAILABLE COMMANDS:
list List all snapshots
update Update a snapshot

WORKFLOW COMMANDS:
wait Wait for snapshot to reach a stable state

FLAGS:
-h, --help help for snapshot

Expand Down
25 changes: 25 additions & 0 deletions cmd/scw/testdata/test-all-usage-block-snapshot-wait-usage.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
🟥🟥🟥 STDERR️️ 🟥🟥🟥️
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.

USAGE:
scw block snapshot wait <snapshot-id ...> [arg=value ...]

EXAMPLES:
Wait for a snapshot to be available
scw block snapshot wait 11111111-1111-1111-1111-111111111111 terminal-status=available

ARGS:
[timeout=5m0s] Timeout of the wait
snapshot-id ID of the snapshot affected by the action.
[terminal-status] Expected terminal status, will wait until this status is reached. (unknown_status | creating | available | error | deleting | deleted | in_use | locked | exporting)
[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)

FLAGS:
-h, --help help for wait

GLOBAL FLAGS:
-c, --config string The path to the config file
-D, --debug Enable debug mode
-o, --output string Output format: json or human, see 'scw help output' for more info (default "human")
-p, --profile string The config profile to use
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ ARGS:

FLAGS:
-h, --help help for create
-w, --wait wait until the volume is ready

GLOBAL FLAGS:
-c, --config string The path to the config file
Expand Down
33 changes: 33 additions & 0 deletions docs/commands/block.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This API allows you to manage your Block Storage volumes.
- [Import a snapshot from a Scaleway Object Storage bucket](#import-a-snapshot-from-a-scaleway-object-storage-bucket)
- [List all snapshots](#list-all-snapshots)
- [Update a snapshot](#update-a-snapshot)
- [Wait for snapshot to reach a stable state](#wait-for-snapshot-to-reach-a-stable-state)
- [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)
- [Create a volume](#create-a-volume)
- [Delete a detached volume](#delete-a-detached-volume)
Expand Down Expand Up @@ -185,6 +186,38 @@ scw block snapshot update <snapshot-id ...> [arg=value ...]



### Wait for snapshot to reach a stable state

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.

**Usage:**

```
scw block snapshot wait <snapshot-id ...> [arg=value ...]
```


**Args:**

| Name | | Description |
|------|---|-------------|
| timeout | Default: `5m0s` | Timeout of the wait |
| snapshot-id | Required | ID of the snapshot affected by the action. |
| 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. |
| 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 |


**Examples:**


Wait for a snapshot to be available
```
scw block snapshot wait 11111111-1111-1111-1111-111111111111 terminal-status=available
```




## 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

Block volumes can be snapshotted, mounted or unmounted.
Expand Down
4 changes: 4 additions & 0 deletions internal/namespaces/block/v1alpha1/custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ func GetCommands() *core.Commands {
cmds := GetGeneratedCommands()

cmds.Add(volumeWaitCommand())
cmds.Add(snapshotWaitCommand())

cmds.MustFind("block", "snapshot", "create").Override(blockSnapshotCreateBuilder)
cmds.MustFind("block", "volume", "create").Override(blockVolumeCreateBuilder)

human.RegisterMarshalerFunc(block.VolumeStatus(""), human.EnumMarshalFunc(volumeStatusMarshalSpecs))
human.RegisterMarshalerFunc(block.SnapshotStatus(""), human.EnumMarshalFunc(snapshotStatusMarshalSpecs))
Expand Down
88 changes: 88 additions & 0 deletions internal/namespaces/block/v1alpha1/custom_snapshot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package block

import (
"context"
"reflect"
"time"

"github.com/scaleway/scaleway-cli/v2/core"
block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)

const (
snapshotActionTimeout = 5 * time.Minute
)

type snapshotWaitRequest struct {
Zone scw.Zone
SnapshotID string
Timeout time.Duration

TerminalStatus *block.SnapshotStatus
}

func snapshotWaitCommand() *core.Command {
snapshotsStatuses := block.SnapshotStatus("").Values()
snapshotsStatusStrings := make([]string, len(snapshotsStatuses))
for k, v := range snapshotsStatuses {
snapshotsStatusStrings[k] = v.String()
}

return &core.Command{
Short: `Wait for snapshot to reach a stable state`,
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.`,
Namespace: "block",
Resource: "snapshot",
Verb: "wait",
Groups: []string{"workflow"},
ArgsType: reflect.TypeOf(snapshotWaitRequest{}),
Run: func(ctx context.Context, argsI interface{}) (i interface{}, err error) {
args := argsI.(*snapshotWaitRequest)

return block.NewAPI(core.ExtractClient(ctx)).WaitForSnapshot(&block.WaitForSnapshotRequest{
Zone: args.Zone,
SnapshotID: args.SnapshotID,
Timeout: scw.TimeDurationPtr(args.Timeout),
RetryInterval: core.DefaultRetryInterval,

TerminalStatus: args.TerminalStatus,
})
},
ArgSpecs: core.ArgSpecs{
core.WaitTimeoutArgSpec(snapshotActionTimeout),
{
Name: "snapshot-id",
Short: `ID of the snapshot affected by the action.`,
Required: true,
Positional: true,
},
{
Name: "terminal-status",
Short: `Expected terminal status, will wait until this status is reached.`,
EnumValues: snapshotsStatusStrings,
},
core.ZoneArgSpec((*instance.API)(nil).Zones()...),
},
Examples: []*core.Example{
{
Short: "Wait for a snapshot to be available",
ArgsJSON: `{"snapshot_id": "11111111-1111-1111-1111-111111111111", "terminal_status": "available"}`,
},
},
}
}

func blockSnapshotCreateBuilder(c *core.Command) *core.Command {
c.WaitFunc = func(ctx context.Context, _, respI interface{}) (interface{}, error) {
resp := respI.(*block.Snapshot)

return block.NewAPI(core.ExtractClient(ctx)).WaitForSnapshot(&block.WaitForSnapshotRequest{
SnapshotID: resp.ID,
Zone: resp.Zone,
})
}

return c
}
49 changes: 49 additions & 0 deletions internal/namespaces/block/v1alpha1/custom_snapshot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package block_test

import (
"testing"

"github.com/scaleway/scaleway-cli/v2/core"
block "github.com/scaleway/scaleway-cli/v2/internal/namespaces/block/v1alpha1"
"github.com/scaleway/scaleway-cli/v2/internal/testhelpers"
blockSDK "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
"github.com/stretchr/testify/require"
)

func Test_SnapshotWait(t *testing.T) {
t.Run("Wait command", core.Test(&core.TestConfig{
Commands: block.GetCommands(),
BeforeFunc: core.BeforeFuncCombine(
core.ExecStoreBeforeCmd("Volume", "scw block volume create perf-iops=5000 from-empty.size=20GB -w"),
core.ExecStoreBeforeCmd("Snapshot", "scw block snapshot create volume-id={{ .Volume.ID }}"),
),
Cmd: "scw block snapshot wait {{ .Snapshot.ID }}",
Check: core.TestCheckCombine(
core.TestCheckGolden(),
core.TestCheckExitCode(0),
),
AfterFunc: core.AfterFuncCombine(
deleteSnapshot("Snapshot"),
deleteVolume("Volume"),
),
}))

t.Run("Wait flag", core.Test(&core.TestConfig{
Commands: block.GetCommands(),
BeforeFunc: core.ExecStoreBeforeCmd("Volume", "scw block volume create perf-iops=5000 from-empty.size=20GB -w"),
Cmd: "scw block snapshot create volume-id={{ .Volume.ID }} -w",
Check: core.TestCheckCombine(
func(t *testing.T, ctx *core.CheckFuncCtx) {
t.Helper()
snap := testhelpers.Value[*blockSDK.Snapshot](t, ctx.Result)
require.Equal(t, blockSDK.SnapshotStatusAvailable, snap.Status)
},
core.TestCheckGolden(),
core.TestCheckExitCode(0),
),
AfterFunc: core.AfterFuncCombine(
deleteSnapshot("CmdResult"),
deleteVolume("Volume"),
),
}))
}
23 changes: 18 additions & 5 deletions internal/namespaces/block/v1alpha1/custom_volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ type volumeWaitRequest struct {
}

func volumeWaitCommand() *core.Command {
terminalStatus := block.VolumeStatus("").Values()
terminalStatusStrings := make([]string, len(terminalStatus))
for k, v := range terminalStatus {
terminalStatusStrings[k] = v.String()
volumeStatuses := block.VolumeStatus("").Values()
volumeStatusStrings := make([]string, len(volumeStatuses))
for k, v := range volumeStatuses {
volumeStatusStrings[k] = v.String()
}

return &core.Command{
Expand Down Expand Up @@ -61,7 +61,7 @@ func volumeWaitCommand() *core.Command {
{
Name: "terminal-status",
Short: `Expected terminal status, will wait until this status is reached.`,
EnumValues: terminalStatusStrings,
EnumValues: volumeStatusStrings,
},
core.ZoneArgSpec((*instance.API)(nil).Zones()...),
},
Expand All @@ -73,3 +73,16 @@ func volumeWaitCommand() *core.Command {
},
}
}

func blockVolumeCreateBuilder(c *core.Command) *core.Command {
c.WaitFunc = func(ctx context.Context, _, respI interface{}) (interface{}, error) {
resp := respI.(*block.Volume)

return block.NewAPI(core.ExtractClient(ctx)).WaitForVolume(&block.WaitForVolumeRequest{
VolumeID: resp.ID,
Zone: resp.Zone,
})
}

return c
}
41 changes: 41 additions & 0 deletions internal/namespaces/block/v1alpha1/custom_volume_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package block_test

import (
"testing"

"github.com/scaleway/scaleway-cli/v2/core"
block "github.com/scaleway/scaleway-cli/v2/internal/namespaces/block/v1alpha1"
"github.com/scaleway/scaleway-cli/v2/internal/testhelpers"
blockSDK "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
"github.com/stretchr/testify/require"
)

func Test_VolumeWait(t *testing.T) {
t.Run("Wait command", core.Test(&core.TestConfig{
Commands: block.GetCommands(),
BeforeFunc: core.BeforeFuncCombine(
core.ExecStoreBeforeCmd("Volume", "scw block volume create perf-iops=5000 from-empty.size=20GB"),
),
Cmd: "scw block volume wait {{ .Volume.ID }}",
Check: core.TestCheckCombine(
core.TestCheckGolden(),
core.TestCheckExitCode(0),
),
AfterFunc: deleteVolume("Volume"),
}))

t.Run("Wait flag", core.Test(&core.TestConfig{
Commands: block.GetCommands(),
Cmd: "scw block volume create perf-iops=5000 from-empty.size=20GB -w",
Check: core.TestCheckCombine(
func(t *testing.T, ctx *core.CheckFuncCtx) {
t.Helper()
vol := testhelpers.Value[*blockSDK.Volume](t, ctx.Result)
require.Equal(t, blockSDK.VolumeStatusAvailable, vol.Status)
},
core.TestCheckGolden(),
core.TestCheckExitCode(0),
),
AfterFunc: deleteVolume("CmdResult"),
}))
}
15 changes: 15 additions & 0 deletions internal/namespaces/block/v1alpha1/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package block_test

import "github.com/scaleway/scaleway-cli/v2/core"

// deleteVolume deletes a volume.
// metaKey must be the key in meta where the volume state is stored.
func deleteVolume(metaKey string) core.AfterFunc {
return core.ExecAfterCmd("scw block volume delete {{." + metaKey + ".ID}}")
}

// deleteSnapshot deletes a snapshot.
// metaKey must be the key in meta where the volume state is stored.
func deleteSnapshot(metaKey string) core.AfterFunc {
return core.ExecAfterCmd("scw block snapshot delete {{." + metaKey + ".ID}}")
}
Loading

0 comments on commit 080a371

Please sign in to comment.