Skip to content

Commit 0e26d90

Browse files
feat(instance): detach and attach volume (#691)
1 parent 82c569d commit 0e26d90

12 files changed

+5876
-0
lines changed

internal/namespaces/instance/v1/custom.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ func GetCommands() *core.Commands {
3838
serverStandbyCommand(),
3939
serverRebootCommand(),
4040
serverDeleteCommand(),
41+
serverAttachVolumeCommand(),
42+
serverDetachVolumeCommand(),
4143
))
4244

4345
//

internal/namespaces/instance/v1/custom_server.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,73 @@ func serverUpdateBuilder(c *core.Command) *core.Command {
275275
// Commands
276276
//
277277

278+
func serverAttachVolumeCommand() *core.Command {
279+
return &core.Command{
280+
Short: `Attach a volume to a server`,
281+
Namespace: "instance",
282+
Resource: "server",
283+
Verb: "attach-volume",
284+
ArgsType: reflect.TypeOf(instance.AttachVolumeRequest{}),
285+
ArgSpecs: core.ArgSpecs{
286+
{
287+
Name: "server-id",
288+
Short: `ID of the server`,
289+
Required: true,
290+
},
291+
{
292+
Name: "volume-id",
293+
Short: `ID of the volume to attach`,
294+
Required: true,
295+
},
296+
core.ZoneArgSpec(),
297+
},
298+
Run: func(ctx context.Context, argsI interface{}) (i interface{}, err error) {
299+
request := argsI.(*instance.AttachVolumeRequest)
300+
301+
client := core.ExtractClient(ctx)
302+
api := instance.NewAPI(client)
303+
return api.AttachVolume(request)
304+
},
305+
Examples: []*core.Example{
306+
{
307+
Short: "Attach a volume to a server",
308+
Request: `{"server_id": "11111111-1111-1111-1111-111111111111","volume_id": "22222222-1111-5555-2222-666666111111"}`,
309+
},
310+
},
311+
}
312+
}
313+
314+
func serverDetachVolumeCommand() *core.Command {
315+
return &core.Command{
316+
Short: `Detach a volume from its server`,
317+
Namespace: "instance",
318+
Resource: "server",
319+
Verb: "detach-volume",
320+
ArgsType: reflect.TypeOf(instance.DetachVolumeRequest{}),
321+
ArgSpecs: core.ArgSpecs{
322+
{
323+
Name: "volume-id",
324+
Short: `ID of the volume to detach`,
325+
Required: true,
326+
},
327+
core.ZoneArgSpec(),
328+
},
329+
Run: func(ctx context.Context, argsI interface{}) (i interface{}, err error) {
330+
request := argsI.(*instance.DetachVolumeRequest)
331+
332+
client := core.ExtractClient(ctx)
333+
api := instance.NewAPI(client)
334+
return api.DetachVolume(request)
335+
},
336+
Examples: []*core.Example{
337+
{
338+
Short: "Detach a volume from its server",
339+
Request: `{"volume_id": "22222222-1111-5555-2222-666666111111"}`,
340+
},
341+
},
342+
}
343+
}
344+
278345
type instanceActionRequest struct {
279346
Zone scw.Zone
280347
ServerID string

internal/namespaces/instance/v1/custom_server_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,116 @@ import (
66
"github.com/alecthomas/assert"
77
"github.com/scaleway/scaleway-cli/internal/core"
88
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
9+
"github.com/scaleway/scaleway-sdk-go/scw"
10+
"github.com/stretchr/testify/require"
911
)
1012

13+
func createVanillaServer(ctx *core.BeforeFuncCtx) error {
14+
ctx.Meta["Server"] = ctx.ExecuteCmd("scw instance server create stopped=true image=ubuntu-bionic")
15+
return nil
16+
}
17+
18+
func deleteVanillaServer(ctx *core.AfterFuncCtx) error {
19+
ctx.ExecuteCmd("scw instance server delete server-id={{ .Server.ID }} delete-ip=true delete-volumes=true")
20+
return nil
21+
}
22+
23+
func Test_ServerVolumeUpdate(t *testing.T) {
24+
t.Run("Attach", func(t *testing.T) {
25+
t.Run("help", core.Test(&core.TestConfig{
26+
Commands: GetCommands(),
27+
Cmd: "scw instance server attach-volume -h",
28+
Check: core.TestCheckCombine(
29+
core.TestCheckExitCode(0),
30+
core.TestCheckGolden(),
31+
),
32+
}))
33+
34+
t.Run("simple block volume", core.Test(&core.TestConfig{
35+
Commands: GetCommands(),
36+
BeforeFunc: func(ctx *core.BeforeFuncCtx) error {
37+
ctx.Meta["Response"] = ctx.ExecuteCmd("scw instance volume create name=cli-test size=10G volume-type=b_ssd")
38+
return createVanillaServer(ctx)
39+
},
40+
Cmd: "scw instance server attach-volume server-id={{ .Server.ID }} volume-id={{ .Response.Volume.ID }}",
41+
Check: func(t *testing.T, ctx *core.CheckFuncCtx) {
42+
require.NoError(t, ctx.Err)
43+
assert.Equal(t, 20*scw.GB, ctx.Result.(*instance.AttachVolumeResponse).Server.Volumes["0"].Size)
44+
assert.Equal(t, 10*scw.GB, ctx.Result.(*instance.AttachVolumeResponse).Server.Volumes["1"].Size)
45+
assert.Equal(t, instance.VolumeTypeBSSD, ctx.Result.(*instance.AttachVolumeResponse).Server.Volumes["1"].VolumeType)
46+
},
47+
AfterFunc: deleteVanillaServer,
48+
}))
49+
50+
t.Run("simple local volume", core.Test(&core.TestConfig{
51+
Commands: GetCommands(),
52+
BeforeFunc: func(ctx *core.BeforeFuncCtx) error {
53+
ctx.Meta["Response"] = ctx.ExecuteCmd("scw instance volume create name=cli-test size=10G volume-type=l_ssd")
54+
return createVanillaServer(ctx)
55+
},
56+
Cmd: "scw instance server attach-volume server-id={{ .Server.ID }} volume-id={{ .Response.Volume.ID }}",
57+
Check: func(t *testing.T, ctx *core.CheckFuncCtx) {
58+
require.NoError(t, ctx.Err)
59+
assert.Equal(t, 20*scw.GB, ctx.Result.(*instance.AttachVolumeResponse).Server.Volumes["0"].Size)
60+
assert.Equal(t, 10*scw.GB, ctx.Result.(*instance.AttachVolumeResponse).Server.Volumes["1"].Size)
61+
assert.Equal(t, instance.VolumeTypeLSSD, ctx.Result.(*instance.AttachVolumeResponse).Server.Volumes["1"].VolumeType)
62+
},
63+
AfterFunc: deleteVanillaServer,
64+
}))
65+
66+
t.Run("invalid volume UUID", core.Test(&core.TestConfig{
67+
Commands: GetCommands(),
68+
BeforeFunc: createVanillaServer,
69+
Cmd: "scw instance server attach-volume server-id={{ .Server.ID }} volume-id=11111111-1111-1111-1111-111111111111",
70+
Check: core.TestCheckCombine(
71+
core.TestCheckGolden(),
72+
core.TestCheckExitCode(1),
73+
),
74+
AfterFunc: deleteVanillaServer,
75+
}))
76+
})
77+
t.Run("Detach", func(t *testing.T) {
78+
t.Run("help", core.Test(&core.TestConfig{
79+
Commands: GetCommands(),
80+
Cmd: "scw instance server detach-volume -h",
81+
Check: core.TestCheckCombine(
82+
core.TestCheckExitCode(0),
83+
core.TestCheckGolden(),
84+
),
85+
}))
86+
87+
t.Run("simple block volume", core.Test(&core.TestConfig{
88+
Commands: GetCommands(),
89+
BeforeFunc: func(ctx *core.BeforeFuncCtx) error {
90+
ctx.Meta["Server"] = ctx.ExecuteCmd("scw instance server create stopped=true image=ubuntu-bionic additional-volumes.0=block:10G")
91+
return nil
92+
},
93+
Cmd: `scw instance server detach-volume volume-id={{ (index .Server.Volumes "1").ID }}`,
94+
Check: func(t *testing.T, ctx *core.CheckFuncCtx) {
95+
require.NoError(t, ctx.Err)
96+
assert.NotZero(t, ctx.Result.(*instance.DetachVolumeResponse).Server.Volumes["0"])
97+
assert.Nil(t, ctx.Result.(*instance.DetachVolumeResponse).Server.Volumes["1"])
98+
assert.Equal(t, 1, len(ctx.Result.(*instance.DetachVolumeResponse).Server.Volumes))
99+
},
100+
AfterFunc: func(ctx *core.AfterFuncCtx) error {
101+
ctx.ExecuteCmd(`scw instance volume delete volume-id={{ (index .Server.Volumes "1").ID }}`)
102+
return deleteVanillaServer(ctx)
103+
},
104+
}))
105+
106+
t.Run("invalid volume UUID", core.Test(&core.TestConfig{
107+
Commands: GetCommands(),
108+
BeforeFunc: createVanillaServer,
109+
Cmd: "scw instance server detach-volume volume-id=11111111-1111-1111-1111-111111111111",
110+
Check: core.TestCheckCombine(
111+
core.TestCheckGolden(),
112+
core.TestCheckExitCode(1),
113+
),
114+
AfterFunc: deleteVanillaServer,
115+
}))
116+
})
117+
}
118+
11119
func Test_ServerUpdateCustom(t *testing.T) {
12120
t.Run("Try to remove ip from server without ip", core.Test(&core.TestConfig{
13121
Commands: GetCommands(),
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Attach a volume to a server
2+
3+
USAGE:
4+
scw [global-flags] instance server attach-volume [flags] [arg=value ...]
5+
6+
EXAMPLES:
7+
Attach a volume to a server
8+
scw instance server attach-volume server-id=11111111-1111-1111-1111-111111111111 volume-id=22222222-1111-5555-2222-666666111111
9+
10+
ARGS:
11+
server-id ID of the server
12+
volume-id ID of the volume to attach
13+
[zone] Zone to target. If none is passed will use default zone from the config
14+
15+
FLAGS:
16+
-h, --help help for attach-volume
17+
18+
GLOBAL FLAGS:
19+
-D, --debug Enable debug mode
20+
-o, --output string Output format: json or human
21+
-p, --profile string The config profile to use

0 commit comments

Comments
 (0)