@@ -118,22 +118,12 @@ func (sb *ServerBuilder) isWindows() bool {
118118 return commercialTypeIsWindowsServer (sb .createReq .CommercialType )
119119}
120120
121- func (sb * ServerBuilder ) rootVolumeTemplate () * instance.VolumeServerTemplate {
122- rootVolume , exists := sb .createReq .Volumes ["0" ]
123- if ! exists {
124- return nil
125- }
126-
127- return rootVolume
128- }
129-
130121func (sb * ServerBuilder ) rootVolumeIsSBS () bool {
131- rootVolume := sb .rootVolumeTemplate ()
132- if rootVolume == nil {
122+ if sb .rootVolume == nil {
133123 return false
134124 }
135125
136- return rootVolume .VolumeType == instance .VolumeVolumeTypeSbsVolume
126+ return sb . rootVolume .VolumeType == instance .VolumeVolumeTypeSbsVolume
137127}
138128
139129func (sb * ServerBuilder ) marketplaceImageType () marketplace.LocalImageType {
@@ -237,14 +227,22 @@ func (sb *ServerBuilder) AddIP(ip string) (*ServerBuilder, error) {
237227 return sb , nil
238228}
239229
230+ func (sb * ServerBuilder ) addIPID (ipID string ) * ServerBuilder {
231+ if sb .createReq .PublicIPs == nil {
232+ sb .createReq .PublicIPs = new ([]string )
233+ }
234+
235+ * sb .createReq .PublicIPs = append (* sb .createReq .PublicIPs , ipID )
236+
237+ return sb
238+ }
239+
240240// AddVolumes build volume templates from arguments.
241241//
242242// More format details in buildVolumeTemplate function.
243243//
244244// Also add default volumes to server, ex: scratch storage for GPU servers
245245func (sb * ServerBuilder ) AddVolumes (rootVolume string , additionalVolumes []string ) (* ServerBuilder , error ) {
246- var err error
247-
248246 if len (additionalVolumes ) > 0 || rootVolume != "" {
249247 if rootVolume != "" {
250248 rootVolumeBuilder , err := NewVolumeBuilder (sb .createReq .Zone , rootVolume )
@@ -260,29 +258,6 @@ func (sb *ServerBuilder) AddVolumes(rootVolume string, additionalVolumes []strin
260258 }
261259 sb .volumes = append (sb .volumes , additionalVolumeBuilder )
262260 }
263-
264- volumes := make (map [string ]* instance.VolumeServerTemplate , len (sb .volumes )+ 1 )
265- if sb .rootVolume != nil {
266- volumes ["0" ], err = sb .rootVolume .BuildVolumeServerTemplate (sb .apiInstance , sb .apiBlock )
267- if err != nil {
268- return sb , fmt .Errorf ("failed to build root volume: %w" , err )
269- }
270- }
271- for i , volume := range sb .volumes {
272- volumeTemplate , err := volume .BuildVolumeServerTemplate (sb .apiInstance , sb .apiBlock )
273- if err != nil {
274- return sb , fmt .Errorf ("failed to build volume template: %w" , err )
275- }
276- index := strconv .Itoa (i + 1 )
277- volumeTemplate .Name = scw .StringPtr (sb .createReq .Name + "-" + index )
278- volumes [index ] = volumeTemplate
279- }
280- // Sanitize the volume map to respect API schemas
281- sb .createReq .Volumes = volumes
282- }
283-
284- if sb .serverType != nil {
285- sb .createReq .Volumes = addDefaultVolumes (sb .serverType , sb .createReq .Volumes )
286261 }
287262
288263 return sb , nil
@@ -360,11 +335,148 @@ func (sb *ServerBuilder) Validate() error {
360335 logger .Warningf ("skipping image server-type compatibility validation" )
361336 }
362337
363- return sb . ValidateVolumes ()
338+ return nil
364339}
365340
366- func (sb * ServerBuilder ) Build () (* instance.CreateServerRequest , []* instance.CreateIPRequest ) {
367- return sb .createReq , sb .createIPReqs
341+ func (sb * ServerBuilder ) BuildVolumes () error {
342+ var err error
343+
344+ volumes := make (map [string ]* instance.VolumeServerTemplate , len (sb .volumes )+ 1 )
345+ if sb .rootVolume != nil {
346+ volumes ["0" ], err = sb .rootVolume .BuildVolumeServerTemplate (sb .apiInstance , sb .apiBlock )
347+ if err != nil {
348+ return fmt .Errorf ("failed to build root volume: %w" , err )
349+ }
350+ }
351+
352+ for i , volume := range sb .volumes {
353+ volumeTemplate , err := volume .BuildVolumeServerTemplate (sb .apiInstance , sb .apiBlock )
354+ if err != nil {
355+ return fmt .Errorf ("failed to build volume template: %w" , err )
356+ }
357+ index := strconv .Itoa (i + 1 )
358+ volumeTemplate .Name = scw .StringPtr (sb .createReq .Name + "-" + index )
359+ volumes [index ] = volumeTemplate
360+ }
361+ // Sanitize the volume map to respect API schemas
362+ sb .createReq .Volumes = volumes
363+
364+ if sb .serverType != nil {
365+ sb .createReq .Volumes = addDefaultVolumes (sb .serverType , sb .createReq .Volumes )
366+ }
367+
368+ return nil
369+ }
370+
371+ func (sb * ServerBuilder ) Build () (* instance.CreateServerRequest , error ) {
372+ err := sb .BuildVolumes ()
373+ if err != nil {
374+ return nil , err
375+ }
376+
377+ return sb .createReq , sb .ValidateVolumes ()
378+ }
379+
380+ type PreServerCreationSetupFunc func (ctx context.Context ) error
381+
382+ type PreServerCreationSetup struct {
383+ setupFunctions []PreServerCreationSetupFunc
384+ cleanFunctions []PreServerCreationSetupFunc
385+ }
386+
387+ func (sb * ServerBuilder ) BuildPreCreationSetup () * PreServerCreationSetup {
388+ setup := & PreServerCreationSetup {}
389+
390+ for _ , ipCreationRequest := range sb .createIPReqs {
391+ setup .setupFunctions = append (setup .setupFunctions , func (ctx context.Context ) error {
392+ resp , err := sb .apiInstance .CreateIP (ipCreationRequest , scw .WithContext (ctx ))
393+ if err != nil {
394+ return err
395+ }
396+
397+ sb .addIPID (resp .IP .ID )
398+
399+ setup .cleanFunctions = append (setup .cleanFunctions , func (ctx context.Context ) error {
400+ return sb .apiInstance .DeleteIP (& instance.DeleteIPRequest {
401+ IP : resp .IP .ID ,
402+ Zone : resp .IP .Zone ,
403+ }, scw .WithContext (ctx ))
404+ })
405+
406+ return nil
407+ })
408+ }
409+
410+ sb .BuildPreCreationVolumesSetup (setup )
411+
412+ return setup
413+ }
414+
415+ // BuildPreCreationVolumesSetup configure PreServerCreationSetup to create required SBS volumes.
416+ // Instance API does not support SBS volumes creation alongside the server, they must be created before then imported.
417+ func (sb * ServerBuilder ) BuildPreCreationVolumesSetup (setup * PreServerCreationSetup ) {
418+ for _ , volume := range sb .volumes {
419+ if volume .VolumeType != instance .VolumeVolumeTypeSbsVolume || volume .VolumeID != nil || volume .Size == nil {
420+ continue
421+ }
422+
423+ projectID := "" // If let empty, ProjectID will be set by scaleway client to default Project ID.
424+ if sb .createReq .Project != nil {
425+ projectID = * sb .createReq .Project
426+ }
427+
428+ setup .setupFunctions = append (setup .setupFunctions , func (ctx context.Context ) error {
429+ vol , err := sb .apiBlock .CreateVolume (& block.CreateVolumeRequest {
430+ Zone : volume .Zone ,
431+ Name : core .GetRandomName ("vol" ),
432+ PerfIops : volume .IOPS ,
433+ ProjectID : projectID ,
434+ FromEmpty : & block.CreateVolumeRequestFromEmpty {
435+ Size : * volume .Size ,
436+ },
437+ }, scw .WithContext (ctx ))
438+ if err != nil {
439+ return err
440+ }
441+
442+ volume .VolumeID = & vol .ID
443+
444+ setup .cleanFunctions = append (setup .cleanFunctions , func (ctx context.Context ) error {
445+ return sb .apiBlock .DeleteVolume (& block.DeleteVolumeRequest {
446+ Zone : vol .Zone ,
447+ VolumeID : vol .ID ,
448+ }, scw .WithContext (ctx ))
449+ })
450+
451+ return nil
452+ })
453+ }
454+ }
455+
456+ func (s * PreServerCreationSetup ) Execute (ctx context.Context ) error {
457+ for _ , setupFunc := range s .setupFunctions {
458+ if err := setupFunc (ctx ); err != nil {
459+ return err
460+ }
461+ }
462+
463+ return nil
464+ }
465+
466+ func (s * PreServerCreationSetup ) Clean (ctx context.Context ) error {
467+ errs := []error (nil )
468+
469+ for _ , cleanFunc := range s .cleanFunctions {
470+ if err := cleanFunc (ctx ); err != nil {
471+ errs = append (errs , err )
472+ }
473+ }
474+
475+ if len (errs ) > 0 {
476+ return errors .Join (errs ... )
477+ }
478+
479+ return nil
368480}
369481
370482type PostServerCreationSetupFunc func (ctx context.Context , server * instance.Server ) error
0 commit comments