Skip to content

Commit 7658e92

Browse files
committed
feat(block): added asynchrone for the new call
Signed-off-by: olivier dubo <olivier.dubo@ovhcloud.com>
1 parent a481f64 commit 7658e92

3 files changed

Lines changed: 111 additions & 8 deletions

File tree

internal/cmd/cloud_storage_block.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ func initCloudVolumeCommand(cloudCmd *cobra.Command) {
4545
volumeEditCmd.Flags().StringVar(&cloud.VolumeEditSpec.Description, "description", "", "Volume description")
4646
volumeEditCmd.Flags().StringVar(&cloud.VolumeEditSpec.Name, "name", "", "Volume name")
4747
volumeEditCmd.Flags().IntVar(&cloud.VolumeEditSpec.Size, "size", 0, "Volume size (in GB, can only be increased)")
48-
volumeEditCmd.Flags().StringVar(&cloud.VolumeEditSpec.Type, "type", "", "Volume type (classic, classic-luks, classic-multiattach, high-speed, high-speed-gen2, high-speed-gen2-luks, high-speed-luks)")
48+
volumeEditCmd.Flags().StringVar(&cloud.VolumeEditSpec.Type, "type", "", "Volume type (CLASSIC, HIGH_SPEED, HIGH_SPEED_GEN2)")
49+
volumeEditCmd.Flags().BoolVar(&flags.WaitForTask, "wait", false, "Wait for the volume to be READY before exiting")
4950
addInteractiveEditorFlag(volumeEditCmd)
5051
storageBlockCmd.AddCommand(volumeEditCmd)
5152

@@ -92,6 +93,7 @@ func initCloudVolumeCommand(cloudCmd *cobra.Command) {
9293
}
9394
volumeSnapshotCreateCmd.Flags().StringVar(&cloud.VolumeSnapShotSpec.Description, "description", "", "Snapshot description")
9495
volumeSnapshotCreateCmd.Flags().StringVar(&cloud.VolumeSnapShotSpec.Name, "name", "", "Snapshot name")
96+
volumeSnapshotCreateCmd.Flags().BoolVar(&flags.WaitForTask, "wait", false, "Wait for the snapshot to be READY before exiting")
9597
volumeSnapshotCmd.AddCommand(volumeSnapshotCreateCmd)
9698

9799
volumeSnapshotListCmd := &cobra.Command{
@@ -132,13 +134,15 @@ func initCloudVolumeCommand(cloudCmd *cobra.Command) {
132134
Args: cobra.ExactArgs(1),
133135
})
134136

135-
volumeBackupCmd.AddCommand(&cobra.Command{
137+
volumeBackupCreateCmd := &cobra.Command{
136138
Use: "create <volume_id> <backup_name>",
137139
Short: "Create a backup of the given volume",
138140
ValidArgsFunction: completion.CloudResources("/v1/cloud/project/%s/volume"),
139141
Run: cloud.CreateVolumeBackup,
140142
Args: cobra.ExactArgs(2),
141-
})
143+
}
144+
volumeBackupCreateCmd.Flags().BoolVar(&flags.WaitForTask, "wait", false, "Wait for the backup to be READY before exiting")
145+
volumeBackupCmd.AddCommand(volumeBackupCreateCmd)
142146

143147
volumeBackupCmd.AddCommand(&cobra.Command{
144148
Use: "delete <backup_id>",
@@ -179,7 +183,7 @@ func getVolumeCreateCmd() *cobra.Command {
179183
volumeCreateCmd.Flags().StringVar(&cloud.VolumeSpec.Name, "name", "", "Volume name")
180184
volumeCreateCmd.Flags().IntVar(&cloud.VolumeSpec.Size, "size", 0, "Volume size (in GB)")
181185
volumeCreateCmd.Flags().StringVar(&cloud.VolumeSpec.SnapshotId, "snapshot-id", "", "Snapshot ID to create the volume from")
182-
volumeCreateCmd.Flags().StringVar(&cloud.VolumeSpec.Type, "type", "", "Volume type (classic, classic-luks, classic-multiattach, high-speed, high-speed-gen2, high-speed-gen2-luks, high-speed-luks)")
186+
volumeCreateCmd.Flags().StringVar(&cloud.VolumeSpec.Type, "type", "", "Volume type (CLASSIC, HIGH_SPEED, HIGH_SPEED_GEN2)")
183187

184188
addParameterFileFlags(volumeCreateCmd, false, assets.CloudOpenapiSchema, "/cloud/project/{serviceName}/region/{regionName}/volume", "post", cloud.VolumeCreateExample, nil)
185189
addInteractiveEditorFlag(volumeCreateCmd)

internal/services/cloud/cloud_storage_block.go

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"errors"
1010
"fmt"
1111
"net/url"
12+
"time"
1213

1314
"github.com/ovh/ovhcloud-cli/internal/display"
1415
"github.com/ovh/ovhcloud-cli/internal/flags"
@@ -169,7 +170,18 @@ func EditVolume(_ *cobra.Command, args []string) {
169170
return
170171
}
171172

172-
display.OutputInfo(&flags.OutputFormatConfig, updated, "✅ Volume %s updated successfully", args[0])
173+
if !flags.WaitForTask {
174+
display.OutputInfo(&flags.OutputFormatConfig, updated, "⚡️ Volume %s update started", args[0])
175+
return
176+
}
177+
178+
ready, err := waitForCloudResourceReady(endpoint, 10*time.Minute)
179+
if err != nil {
180+
display.OutputError(&flags.OutputFormatConfig, "failed to wait for volume update: %s", err)
181+
return
182+
}
183+
184+
display.OutputInfo(&flags.OutputFormatConfig, ready, "✅ Volume %s updated successfully", args[0])
173185
}
174186

175187
func CreateVolume(_ *cobra.Command, args []string) {
@@ -214,7 +226,20 @@ func CreateVolume(_ *cobra.Command, args []string) {
214226
return
215227
}
216228

217-
display.OutputInfo(&flags.OutputFormatConfig, response, "✅ Volume %s created successfully", response["id"])
229+
volumeID, _ := response["id"].(string)
230+
231+
if !flags.WaitForTask {
232+
display.OutputInfo(&flags.OutputFormatConfig, response, "⚡️ Volume %s creation started", volumeID)
233+
return
234+
}
235+
236+
ready, err := waitForCloudResourceReady(fmt.Sprintf("%s/%s", volumeV2Endpoint(projectID), url.PathEscape(volumeID)), 10*time.Minute)
237+
if err != nil {
238+
display.OutputError(&flags.OutputFormatConfig, "failed to wait for volume creation: %s", err)
239+
return
240+
}
241+
242+
display.OutputInfo(&flags.OutputFormatConfig, ready, "✅ Volume %s created successfully", volumeID)
218243
}
219244

220245
func DeleteVolume(_ *cobra.Command, args []string) {
@@ -304,7 +329,20 @@ func CreateVolumeSnapshot(_ *cobra.Command, args []string) {
304329
return
305330
}
306331

307-
display.OutputInfo(&flags.OutputFormatConfig, response, "✅ Snapshot for volume %s created successfully, id : %s", args[0], response["id"])
332+
snapshotID, _ := response["id"].(string)
333+
334+
if !flags.WaitForTask {
335+
display.OutputInfo(&flags.OutputFormatConfig, response, "⚡️ Snapshot %s creation started for volume %s", snapshotID, args[0])
336+
return
337+
}
338+
339+
ready, err := waitForCloudResourceReady(fmt.Sprintf("%s/%s", endpoint, url.PathEscape(snapshotID)), 10*time.Minute)
340+
if err != nil {
341+
display.OutputError(&flags.OutputFormatConfig, "failed to wait for snapshot creation: %s", err)
342+
return
343+
}
344+
345+
display.OutputInfo(&flags.OutputFormatConfig, ready, "✅ Snapshot %s created successfully for volume %s", snapshotID, args[0])
308346
}
309347

310348
func ListVolumeSnapshots(cmd *cobra.Command, _ []string) {
@@ -406,7 +444,20 @@ func CreateVolumeBackup(_ *cobra.Command, args []string) {
406444
return
407445
}
408446

409-
display.OutputInfo(&flags.OutputFormatConfig, response, "✅ Volume backup for volume %s created successfully (id: %s)", args[0], response["id"])
447+
backupID, _ := response["id"].(string)
448+
449+
if !flags.WaitForTask {
450+
display.OutputInfo(&flags.OutputFormatConfig, response, "⚡️ Backup %s creation started for volume %s", backupID, args[0])
451+
return
452+
}
453+
454+
ready, err := waitForCloudResourceReady(fmt.Sprintf("%s/%s", endpoint, url.PathEscape(backupID)), 30*time.Minute)
455+
if err != nil {
456+
display.OutputError(&flags.OutputFormatConfig, "failed to wait for backup creation: %s", err)
457+
return
458+
}
459+
460+
display.OutputInfo(&flags.OutputFormatConfig, ready, "✅ Volume backup %s created successfully for volume %s", backupID, args[0])
410461
}
411462

412463
// findVolumeBackupV1 locates a volume backup across all regions using the v1 API.

internal/services/cloud/utils.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,51 @@ func waitForCloudOperation(projectID, operationID, action string, retryDuration
158158
}
159159
}
160160
}
161+
162+
// waitForCloudResourceReady polls a v2 asynchronous resource at the given full
163+
// endpoint (e.g. ".../storage/block/volume/{id}") until its "resourceStatus"
164+
// reaches "READY". It fails if the resource reports an "ERROR" status or a failed
165+
// task, and times out after retryDuration. The last fetched resource is returned.
166+
func waitForCloudResourceReady(endpoint string, retryDuration time.Duration) (map[string]any, error) {
167+
ctx, cancel := context.WithTimeout(context.Background(), retryDuration)
168+
defer cancel()
169+
170+
ticker := time.NewTicker(5 * time.Second)
171+
defer ticker.Stop()
172+
173+
for {
174+
var resource map[string]any
175+
if err := httpLib.Client.Get(endpoint, &resource); err != nil {
176+
return nil, fmt.Errorf("error fetching resource: %w", err)
177+
}
178+
179+
status, _ := resource["resourceStatus"].(string)
180+
switch status {
181+
case "READY":
182+
return resource, nil
183+
case "ERROR":
184+
return nil, errors.New("resource ended in error state (resourceStatus=ERROR)")
185+
}
186+
187+
// Surface a failed task as soon as one is reported.
188+
if tasks, ok := resource["currentTasks"].([]any); ok {
189+
for _, t := range tasks {
190+
task, ok := t.(map[string]any)
191+
if !ok {
192+
continue
193+
}
194+
if taskStatus, _ := task["status"].(string); taskStatus == "ERROR" {
195+
return nil, fmt.Errorf("resource task %v ended in error", task["type"])
196+
}
197+
}
198+
}
199+
200+
select {
201+
case <-ctx.Done():
202+
return nil, errors.New("timeout waiting for resource to be ready")
203+
case <-ticker.C:
204+
log.Printf("Still waiting for resource to be ready (resourceStatus=%s)…", status)
205+
continue
206+
}
207+
}
208+
}

0 commit comments

Comments
 (0)