Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/namespaces/instance/v1/custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ func GetCommands() *core.Commands {
serverStandbyCommand(),
serverRebootCommand(),
serverDeleteCommand(),
serverAttachVolumeCommand(),
serverDetachVolumeCommand(),
))

//
Expand Down
67 changes: 67 additions & 0 deletions internal/namespaces/instance/v1/custom_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,73 @@ func serverUpdateBuilder(c *core.Command) *core.Command {
// Commands
//

func serverAttachVolumeCommand() *core.Command {
return &core.Command{
Short: `Attach a volume to a server`,
Namespace: "instance",
Resource: "server",
Verb: "attach-volume",
ArgsType: reflect.TypeOf(instance.AttachVolumeRequest{}),
ArgSpecs: core.ArgSpecs{
{
Name: "server-id",
Short: `ID of the server`,
Required: true,
},
{
Name: "volume-id",
Short: `ID of the volume to attach`,
Required: true,
},
core.ZoneArgSpec(),
},
Run: func(ctx context.Context, argsI interface{}) (i interface{}, err error) {
request := argsI.(*instance.AttachVolumeRequest)

client := core.ExtractClient(ctx)
api := instance.NewAPI(client)
return api.AttachVolume(request)
},
Examples: []*core.Example{
{
Short: "Attach a volume to a server",
Request: `{"server_id": "11111111-1111-1111-1111-111111111111","volume_id": "22222222-1111-5555-2222-666666111111"}`,
},
},
}
}

func serverDetachVolumeCommand() *core.Command {
return &core.Command{
Short: `Detach a volume from its server`,
Namespace: "instance",
Resource: "server",
Verb: "detach-volume",
ArgsType: reflect.TypeOf(instance.DetachVolumeRequest{}),
ArgSpecs: core.ArgSpecs{
{
Name: "volume-id",
Short: `ID of the volume to detach`,
Required: true,
},
core.ZoneArgSpec(),
},
Run: func(ctx context.Context, argsI interface{}) (i interface{}, err error) {
request := argsI.(*instance.DetachVolumeRequest)

client := core.ExtractClient(ctx)
api := instance.NewAPI(client)
return api.DetachVolume(request)
},
Examples: []*core.Example{
{
Short: "Detach a volume from its server",
Request: `{"volume_id": "22222222-1111-5555-2222-666666111111"}`,
},
},
}
}

type instanceActionRequest struct {
Zone scw.Zone
ServerID string
Expand Down
108 changes: 108 additions & 0 deletions internal/namespaces/instance/v1/custom_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,116 @@ import (
"github.com/alecthomas/assert"
"github.com/scaleway/scaleway-cli/internal/core"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/stretchr/testify/require"
)

func createVanillaServer(ctx *core.BeforeFuncCtx) error {
ctx.Meta["Server"] = ctx.ExecuteCmd("scw instance server create stopped=true image=ubuntu-bionic")
return nil
}

func deleteVanillaServer(ctx *core.AfterFuncCtx) error {
ctx.ExecuteCmd("scw instance server delete server-id={{ .Server.ID }} delete-ip=true delete-volumes=true")
return nil
}

func Test_ServerVolumeUpdate(t *testing.T) {
t.Run("Attach", func(t *testing.T) {
t.Run("help", core.Test(&core.TestConfig{
Commands: GetCommands(),
Cmd: "scw instance server attach-volume -h",
Check: core.TestCheckCombine(
core.TestCheckExitCode(0),
core.TestCheckGolden(),
),
}))

t.Run("simple block volume", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: func(ctx *core.BeforeFuncCtx) error {
ctx.Meta["Response"] = ctx.ExecuteCmd("scw instance volume create name=cli-test size=10G volume-type=b_ssd")
return createVanillaServer(ctx)
},
Cmd: "scw instance server attach-volume server-id={{ .Server.ID }} volume-id={{ .Response.Volume.ID }}",
Check: func(t *testing.T, ctx *core.CheckFuncCtx) {
require.NoError(t, ctx.Err)
assert.Equal(t, 20*scw.GB, ctx.Result.(*instance.AttachVolumeResponse).Server.Volumes["0"].Size)
assert.Equal(t, 10*scw.GB, ctx.Result.(*instance.AttachVolumeResponse).Server.Volumes["1"].Size)
assert.Equal(t, instance.VolumeTypeBSSD, ctx.Result.(*instance.AttachVolumeResponse).Server.Volumes["1"].VolumeType)
},
AfterFunc: deleteVanillaServer,
}))

t.Run("simple local volume", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: func(ctx *core.BeforeFuncCtx) error {
ctx.Meta["Response"] = ctx.ExecuteCmd("scw instance volume create name=cli-test size=10G volume-type=l_ssd")
return createVanillaServer(ctx)
},
Cmd: "scw instance server attach-volume server-id={{ .Server.ID }} volume-id={{ .Response.Volume.ID }}",
Check: func(t *testing.T, ctx *core.CheckFuncCtx) {
require.NoError(t, ctx.Err)
assert.Equal(t, 20*scw.GB, ctx.Result.(*instance.AttachVolumeResponse).Server.Volumes["0"].Size)
assert.Equal(t, 10*scw.GB, ctx.Result.(*instance.AttachVolumeResponse).Server.Volumes["1"].Size)
assert.Equal(t, instance.VolumeTypeLSSD, ctx.Result.(*instance.AttachVolumeResponse).Server.Volumes["1"].VolumeType)
},
AfterFunc: deleteVanillaServer,
}))

t.Run("invalid volume UUID", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: createVanillaServer,
Cmd: "scw instance server attach-volume server-id={{ .Server.ID }} volume-id=11111111-1111-1111-1111-111111111111",
Check: core.TestCheckCombine(
core.TestCheckGolden(),
core.TestCheckExitCode(1),
),
AfterFunc: deleteVanillaServer,
}))
})
t.Run("Detach", func(t *testing.T) {
t.Run("help", core.Test(&core.TestConfig{
Commands: GetCommands(),
Cmd: "scw instance server detach-volume -h",
Check: core.TestCheckCombine(
core.TestCheckExitCode(0),
core.TestCheckGolden(),
),
}))

t.Run("simple block volume", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: func(ctx *core.BeforeFuncCtx) error {
ctx.Meta["Server"] = ctx.ExecuteCmd("scw instance server create stopped=true image=ubuntu-bionic additional-volumes.0=block:10G")
return nil
},
Cmd: `scw instance server detach-volume volume-id={{ (index .Server.Volumes "1").ID }}`,
Check: func(t *testing.T, ctx *core.CheckFuncCtx) {
require.NoError(t, ctx.Err)
assert.NotZero(t, ctx.Result.(*instance.DetachVolumeResponse).Server.Volumes["0"])
assert.Nil(t, ctx.Result.(*instance.DetachVolumeResponse).Server.Volumes["1"])
assert.Equal(t, 1, len(ctx.Result.(*instance.DetachVolumeResponse).Server.Volumes))
},
AfterFunc: func(ctx *core.AfterFuncCtx) error {
ctx.ExecuteCmd(`scw instance volume delete volume-id={{ (index .Server.Volumes "1").ID }}`)
return deleteVanillaServer(ctx)
},
}))

t.Run("invalid volume UUID", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: createVanillaServer,
Cmd: "scw instance server detach-volume volume-id=11111111-1111-1111-1111-111111111111",
Check: core.TestCheckCombine(
core.TestCheckGolden(),
core.TestCheckExitCode(1),
),
AfterFunc: deleteVanillaServer,
}))
})
}

func Test_ServerUpdateCustom(t *testing.T) {
t.Run("Try to remove ip from server without ip", core.Test(&core.TestConfig{
Commands: GetCommands(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Attach a volume to a server

USAGE:
scw [global-flags] instance server attach-volume [flags] [arg=value ...]

EXAMPLES:
Attach a volume to a server
scw instance server attach-volume server-id=11111111-1111-1111-1111-111111111111 volume-id=22222222-1111-5555-2222-666666111111

ARGS:
server-id ID of the server
volume-id ID of the volume to attach
[zone] Zone to target. If none is passed will use default zone from the config

FLAGS:
-h, --help help for attach-volume

GLOBAL FLAGS:
-D, --debug Enable debug mode
-o, --output string Output format: json or human
-p, --profile string The config profile to use
Loading