Skip to content

Commit 38c93d3

Browse files
authored
feat(instance): add image update command (#2539)
1 parent 30aa3a2 commit 38c93d3

12 files changed

+6346
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
2+
🟥🟥🟥 STDERR️️ 🟥🟥🟥️
3+
Update properties of an instance image.
4+
5+
USAGE:
6+
scw instance image update [arg=value ...]
7+
8+
EXAMPLES:
9+
Update image name
10+
scw instance image update image-id=11111111-1111-1111-1111-111111111111 name=foo
11+
12+
Update image public
13+
scw instance image update image-id=11111111-1111-1111-1111-111111111111 public=true
14+
15+
Add extra volume
16+
scw instance image update image-id=11111111-1111-1111-1111-111111111111 extra-volumes.1.id=11111111-1111-1111-1111-111111111111
17+
18+
ARGS:
19+
image-id
20+
[name]
21+
[arch] (x86_64 | arm)
22+
[extra-volumes.{index}.id] Additional extra-volume ID
23+
[from-server]
24+
[public]
25+
[tags.{index}]
26+
[project] Project ID to use. If none is passed the default project ID will be used
27+
[organization] Organization ID to use. If none is passed the default organization ID will be used
28+
[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 | pl-waw-1)
29+
30+
FLAGS:
31+
-h, --help help for update
32+
33+
GLOBAL FLAGS:
34+
-c, --config string The path to the config file
35+
-D, --debug Enable debug mode
36+
-o, --output string Output format: json or human, see 'scw help output' for more info (default "human")
37+
-p, --profile string The config profile to use

cmd/scw/testdata/test-all-usage-instance-image-usage.golden

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ AVAILABLE COMMANDS:
1313
delete Delete an instance image
1414
get Get an instance image
1515
list List instance images
16+
update Update an instance image
1617
wait Wait for image to reach a stable state
1718

1819
FLAGS:

docs/commands/instance.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Instance API
77
- [Delete an instance image](#delete-an-instance-image)
88
- [Get an instance image](#get-an-instance-image)
99
- [List instance images](#list-instance-images)
10+
- [Update an instance image](#update-an-instance-image)
1011
- [Wait for image to reach a stable state](#wait-for-image-to-reach-a-stable-state)
1112
- [IP management commands](#ip-management-commands)
1213
- [Attach an IP to a given server](#attach-an-ip-to-a-given-server)
@@ -235,6 +236,54 @@ scw instance image list
235236

236237

237238

239+
### Update an instance image
240+
241+
Update properties of an instance image.
242+
243+
**Usage:**
244+
245+
```
246+
scw instance image update [arg=value ...]
247+
```
248+
249+
250+
**Args:**
251+
252+
| Name | | Description |
253+
|------|---|-------------|
254+
| image-id | Required | |
255+
| name | | |
256+
| arch | One of: `x86_64`, `arm` | |
257+
| extra-volumes.{index}.id | | Additional extra-volume ID |
258+
| from-server | | |
259+
| public | | |
260+
| tags.{index} | | |
261+
| project | | Project ID to use. If none is passed the default project ID will be used |
262+
| organization | | Organization ID to use. If none is passed the default organization ID will be used |
263+
| zone | Default: `fr-par-1`<br />One of: `fr-par-1`, `fr-par-2`, `fr-par-3`, `nl-ams-1`, `nl-ams-2`, `pl-waw-1` | Zone to target. If none is passed will use default zone from the config |
264+
265+
266+
**Examples:**
267+
268+
269+
Update image name
270+
```
271+
scw instance image update image-id=11111111-1111-1111-1111-111111111111 name=foo
272+
```
273+
274+
Update image public
275+
```
276+
scw instance image update image-id=11111111-1111-1111-1111-111111111111 public=true
277+
```
278+
279+
Add extra volume
280+
```
281+
scw instance image update image-id=11111111-1111-1111-1111-111111111111 extra-volumes.1.id=11111111-1111-1111-1111-111111111111
282+
```
283+
284+
285+
286+
238287
### Wait for image to reach a stable state
239288

240289
Wait for image to reach a stable state. This is similar to using --wait flag on other action commands, but without requiring a new action on the image.

internal/namespaces/instance/v1/custom.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ func GetCommands() *core.Commands {
9797
cmds.MustFind("instance", "image", "delete").Override(imageDeleteBuilder)
9898
cmds.Merge(core.NewCommands(
9999
imageWaitCommand(),
100+
imageUpdateCommand(),
100101
))
101102

102103
//

internal/namespaces/instance/v1/custom_image.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/scaleway/scaleway-cli/v2/internal/core"
1212
"github.com/scaleway/scaleway-cli/v2/internal/human"
1313
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
14+
"github.com/scaleway/scaleway-sdk-go/logger"
1415
"github.com/scaleway/scaleway-sdk-go/scw"
1516
)
1617

@@ -314,6 +315,125 @@ func imageDeleteBuilder(c *core.Command) *core.Command {
314315
return c
315316
}
316317

318+
//
319+
// Commands
320+
//
321+
322+
func imageUpdateCommand() *core.Command {
323+
return &core.Command{
324+
Short: `Update an instance image`,
325+
Long: `Update properties of an instance image.`,
326+
Namespace: "instance",
327+
Resource: "image",
328+
Verb: "update",
329+
ArgsType: reflect.TypeOf(instance.UpdateImageRequest{}),
330+
ArgSpecs: core.ArgSpecs{
331+
{
332+
Name: "image-id",
333+
Required: true,
334+
Positional: false,
335+
},
336+
{
337+
Name: "name",
338+
Required: false,
339+
Positional: false,
340+
},
341+
{
342+
Name: "arch",
343+
Required: false,
344+
Positional: false,
345+
EnumValues: []string{"x86_64", "arm"},
346+
},
347+
{
348+
Name: "extra-volumes.{index}.id",
349+
Short: `Additional extra-volume ID`,
350+
Required: false,
351+
Deprecated: false,
352+
Positional: false,
353+
},
354+
{
355+
Name: "from-server",
356+
Required: false,
357+
Positional: false,
358+
},
359+
{
360+
Name: "public",
361+
Required: false,
362+
Positional: false,
363+
},
364+
{
365+
Name: "tags.{index}",
366+
Required: false,
367+
Positional: false,
368+
},
369+
core.ProjectArgSpec(),
370+
core.OrganizationArgSpec(),
371+
core.ZoneArgSpec(scw.ZoneFrPar1, scw.ZoneFrPar2, scw.ZoneFrPar3, scw.ZoneNlAms1, scw.ZoneNlAms2, scw.ZonePlWaw1),
372+
},
373+
Run: func(ctx context.Context, argsI interface{}) (i interface{}, err error) {
374+
request := argsI.(*instance.UpdateImageRequest)
375+
376+
client := core.ExtractClient(ctx)
377+
api := instance.NewAPI(client)
378+
379+
getImageResponse, err := api.GetImage(&instance.GetImageRequest{
380+
Zone: request.Zone,
381+
ImageID: request.ImageID,
382+
})
383+
if err != nil {
384+
logger.Warningf("cannot get image %s: %s", request.Name, err)
385+
}
386+
387+
if request.Name == nil {
388+
request.Name = &getImageResponse.Image.Name
389+
}
390+
if request.Arch == "" {
391+
request.Arch = getImageResponse.Image.Arch
392+
}
393+
if request.CreationDate == nil {
394+
request.CreationDate = getImageResponse.Image.CreationDate
395+
}
396+
if request.ModificationDate == nil {
397+
request.ModificationDate = getImageResponse.Image.ModificationDate
398+
}
399+
if request.ExtraVolumes == nil {
400+
request.ExtraVolumes = make(map[string]*instance.VolumeTemplate)
401+
for k, v := range getImageResponse.Image.ExtraVolumes {
402+
volume := instance.VolumeTemplate{
403+
ID: v.ID,
404+
Name: v.Name,
405+
Size: v.Size,
406+
VolumeType: v.VolumeType,
407+
}
408+
request.ExtraVolumes[k] = &volume
409+
}
410+
}
411+
if request.RootVolume == nil {
412+
request.RootVolume = getImageResponse.Image.RootVolume
413+
}
414+
if !request.Public && !getImageResponse.Image.Public {
415+
request.Public = getImageResponse.Image.Public
416+
}
417+
418+
return api.UpdateImage(request)
419+
},
420+
Examples: []*core.Example{
421+
{
422+
Short: "Update image name",
423+
Raw: "scw instance image update image-id=11111111-1111-1111-1111-111111111111 name=foo",
424+
},
425+
{
426+
Short: "Update image public",
427+
Raw: "scw instance image update image-id=11111111-1111-1111-1111-111111111111 public=true",
428+
},
429+
{
430+
Short: "Add extra volume",
431+
Raw: "scw instance image update image-id=11111111-1111-1111-1111-111111111111 extra-volumes.1.id=11111111-1111-1111-1111-111111111111",
432+
},
433+
},
434+
}
435+
}
436+
317437
func imageWaitCommand() *core.Command {
318438
return &core.Command{
319439
Short: `Wait for image to reach a stable state`,

internal/namespaces/instance/v1/custom_image_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,61 @@ func Test_ImageList(t *testing.T) {
9494
AfterFunc: deleteImage("Image"),
9595
}))
9696
}
97+
98+
func Test_ImageUpdate(t *testing.T) {
99+
t.Run("Change name", core.Test(&core.TestConfig{
100+
BeforeFunc: createImage("ImageName"),
101+
Commands: GetCommands(),
102+
Cmd: "scw instance image update image-id={{ .ImageName.Image.ID }} name=foo",
103+
Check: core.TestCheckCombine(
104+
func(t *testing.T, ctx *core.CheckFuncCtx) {
105+
assert.Equal(t, "foo", ctx.Result.(*instance.UpdateImageResponse).Image.Name)
106+
},
107+
core.TestCheckGolden(),
108+
core.TestCheckExitCode(0),
109+
),
110+
AfterFunc: core.AfterFuncCombine(
111+
deleteServer("Server"),
112+
deleteImage("ImageName"),
113+
),
114+
}))
115+
116+
t.Run("Change public from default false to true", core.Test(&core.TestConfig{
117+
BeforeFunc: createImage("ImagePub"),
118+
Commands: GetCommands(),
119+
Cmd: "scw instance image update image-id={{ .ImagePub.Image.ID }} public=true",
120+
Check: core.TestCheckCombine(
121+
func(t *testing.T, ctx *core.CheckFuncCtx) {
122+
assert.Equal(t, true, ctx.Result.(*instance.UpdateImageResponse).Image.Public)
123+
},
124+
core.TestCheckGolden(),
125+
core.TestCheckExitCode(0),
126+
),
127+
AfterFunc: core.AfterFuncCombine(
128+
deleteServer("Server"),
129+
deleteImage("ImagePub"),
130+
),
131+
}))
132+
133+
t.Run("Add extra volume", core.Test(&core.TestConfig{
134+
BeforeFunc: core.BeforeFuncCombine(
135+
createVolume("Volume", 20, instance.VolumeVolumeTypeBSSD),
136+
core.ExecStoreBeforeCmd("SnapshotVol", `scw instance snapshot create -w name=snapVol volume-id={{ .Volume.ID }}`),
137+
createImage("ImageExtraVol"),
138+
),
139+
Commands: GetCommands(),
140+
Cmd: "scw instance image update image-id={{ .ImageExtraVol.Image.ID }} extra-volumes.1.id={{ .SnapshotVol.ID }}",
141+
Check: core.TestCheckCombine(
142+
func(t *testing.T, ctx *core.CheckFuncCtx) {
143+
assert.Equal(t, "snapVol", ctx.Result.(*instance.UpdateImageResponse).Image.ExtraVolumes["1"].Name)
144+
},
145+
core.TestCheckGolden(),
146+
core.TestCheckExitCode(0),
147+
),
148+
AfterFunc: core.AfterFuncCombine(
149+
deleteServer("Server"),
150+
deleteImage("ImageExtraVol"),
151+
deleteVolume("Volume"),
152+
),
153+
}))
154+
}

0 commit comments

Comments
 (0)