github.com/sacloud/libsacloud/v2@v2.32.3/helper/builder/disk/builder.go (about) 1 // Copyright 2016-2022 The Libsacloud Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package disk 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "reflect" 22 23 "github.com/sacloud/libsacloud/v2/helper/builder" 24 "github.com/sacloud/libsacloud/v2/pkg/size" 25 26 archiveUtil "github.com/sacloud/libsacloud/v2/helper/query" 27 "github.com/sacloud/libsacloud/v2/sacloud" 28 "github.com/sacloud/libsacloud/v2/sacloud/ostype" 29 "github.com/sacloud/libsacloud/v2/sacloud/types" 30 ) 31 32 // Builder ディスクの構築インターフェース 33 type Builder interface { 34 Validate(ctx context.Context, zone string) error 35 Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) 36 Update(ctx context.Context, zone string) (*UpdateResult, error) 37 DiskID() types.ID 38 UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel 39 NoWaitFlag() bool 40 } 41 42 // BuildResult ディスク構築結果 43 type BuildResult struct { 44 DiskID types.ID 45 GeneratedSSHKey *sacloud.SSHKeyGenerated 46 } 47 48 // UpdateResult ディスク更新結果 49 type UpdateResult struct { 50 Disk *sacloud.Disk 51 } 52 53 // FromUnixBuilder Unix系パブリックアーカイブからディスクを作成するリクエスト 54 type FromUnixBuilder struct { 55 OSType ostype.ArchiveOSType 56 57 Name string 58 SizeGB int 59 DistantFrom []types.ID 60 PlanID types.ID 61 Connection types.EDiskConnection 62 Description string 63 Tags types.Tags 64 IconID types.ID 65 66 EditParameter *UnixEditRequest 67 68 Client *APIClient 69 NoWait bool 70 71 ID types.ID 72 73 generatedSSHKey *sacloud.SSHKeyGenerated 74 generatedNotes []*sacloud.Note 75 } 76 77 // Validate 設定値の検証 78 func (d *FromUnixBuilder) Validate(ctx context.Context, zone string) error { 79 if !d.OSType.IsSupportDiskEdit() { 80 return fmt.Errorf("invalid OSType: %s", d.OSType.String()) 81 } 82 if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil { 83 return err 84 } 85 86 if d.EditParameter != nil { 87 return d.EditParameter.Validate(ctx, d.Client) 88 } 89 return nil 90 } 91 92 // Build ディスクの構築 93 func (d *FromUnixBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) { 94 res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d) 95 if err != nil { 96 return nil, err 97 } 98 d.ID = res.DiskID 99 100 if d.generatedSSHKey != nil { 101 res.GeneratedSSHKey = d.generatedSSHKey 102 } 103 104 if d.EditParameter != nil { 105 if d.EditParameter.IsSSHKeysEphemeral { 106 if err := d.Client.SSHKey.Delete(ctx, d.generatedSSHKey.ID); err != nil { 107 return nil, err 108 } 109 } 110 if d.EditParameter.IsNotesEphemeral { 111 for _, note := range d.generatedNotes { 112 if err := d.Client.Note.Delete(ctx, note.ID); err != nil { 113 return nil, err 114 } 115 } 116 } 117 } 118 return res, nil 119 } 120 121 // Update ディスクの更新 122 func (d *FromUnixBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) { 123 return update(ctx, d.Client, zone, d) 124 } 125 126 // DiskID ディスクID取得 127 func (d *FromUnixBuilder) DiskID() types.ID { 128 return d.ID 129 } 130 131 // UpdateLevel Update時にどのレベルの変更が必要か 132 func (d *FromUnixBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel { 133 return updateLevel(disk, d.EditParameter != nil, d) 134 } 135 136 func (d *FromUnixBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest { 137 return &sacloud.DiskUpdateRequest{ 138 Name: d.Name, 139 Description: d.Description, 140 Tags: d.Tags, 141 IconID: d.IconID, 142 Connection: d.Connection, 143 } 144 } 145 146 func (d *FromUnixBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) { 147 archive, err := archiveUtil.FindArchiveByOSType(ctx, client.Archive, zone, d.OSType) 148 if err != nil { 149 return nil, nil, err 150 } 151 152 createReq := &sacloud.DiskCreateRequest{ 153 DiskPlanID: d.PlanID, 154 SizeMB: d.SizeGB * size.GiB, 155 Connection: d.Connection, 156 SourceArchiveID: archive.ID, 157 ServerID: serverID, 158 Name: d.Name, 159 Description: d.Description, 160 Tags: d.Tags, 161 IconID: d.IconID, 162 } 163 164 var editReq *sacloud.DiskEditRequest 165 if d.EditParameter != nil { 166 req, sshKey, notes, err := d.EditParameter.prepareDiskEditParameter(ctx, client) 167 if err != nil { 168 return nil, nil, err 169 } 170 editReq = req 171 if sshKey != nil { 172 d.generatedSSHKey = sshKey 173 } 174 if len(notes) > 0 { 175 d.generatedNotes = notes 176 } 177 } 178 179 return createReq, editReq, nil 180 } 181 182 func (d *FromUnixBuilder) NoWaitFlag() bool { 183 return d.NoWait 184 } 185 186 // FromFixedArchiveBuilder ディスクの修正をサポートしないパブリックアーカイブからディスクを作成するリクエスト 187 type FromFixedArchiveBuilder struct { 188 OSType ostype.ArchiveOSType 189 190 Name string 191 SizeGB int 192 DistantFrom []types.ID 193 PlanID types.ID 194 Connection types.EDiskConnection 195 Description string 196 Tags types.Tags 197 IconID types.ID 198 199 Client *APIClient 200 NoWait bool 201 202 ID types.ID 203 204 generatedSSHKey *sacloud.SSHKeyGenerated 205 } 206 207 // Validate 設定値の検証 208 func (d *FromFixedArchiveBuilder) Validate(ctx context.Context, zone string) error { 209 if d.OSType.IsSupportDiskEdit() || d.OSType.IsWindows() { 210 return fmt.Errorf("invalid OSType: %s", d.OSType.String()) 211 } 212 if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil { 213 return err 214 } 215 216 return nil 217 } 218 219 // Build ディスクの構築 220 func (d *FromFixedArchiveBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) { 221 res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d) 222 if err != nil { 223 return nil, err 224 } 225 d.ID = res.DiskID 226 if d.generatedSSHKey != nil { 227 res.GeneratedSSHKey = d.generatedSSHKey 228 } 229 return res, nil 230 } 231 232 // Update ディスクの更新 233 func (d *FromFixedArchiveBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) { 234 return update(ctx, d.Client, zone, d) 235 } 236 237 // DiskID ディスクID取得 238 func (d *FromFixedArchiveBuilder) DiskID() types.ID { 239 return d.ID 240 } 241 242 // UpdateLevel Update時にどのレベルの変更が必要か 243 func (d *FromFixedArchiveBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel { 244 return updateLevel(disk, false, d) 245 } 246 247 func (d *FromFixedArchiveBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest { 248 return &sacloud.DiskUpdateRequest{ 249 Name: d.Name, 250 Description: d.Description, 251 Tags: d.Tags, 252 IconID: d.IconID, 253 Connection: d.Connection, 254 } 255 } 256 257 func (d *FromFixedArchiveBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) { 258 archive, err := archiveUtil.FindArchiveByOSType(ctx, client.Archive, zone, d.OSType) 259 if err != nil { 260 return nil, nil, err 261 } 262 263 createReq := &sacloud.DiskCreateRequest{ 264 DiskPlanID: d.PlanID, 265 SizeMB: d.SizeGB * size.GiB, 266 Connection: d.Connection, 267 SourceArchiveID: archive.ID, 268 ServerID: serverID, 269 Name: d.Name, 270 Description: d.Description, 271 Tags: d.Tags, 272 IconID: d.IconID, 273 } 274 return createReq, nil, nil 275 } 276 277 func (d *FromFixedArchiveBuilder) NoWaitFlag() bool { 278 return d.NoWait 279 } 280 281 // FromWindowsBuilder Windows系パブリックアーカイブからディスクを作成するリクエスト 282 type FromWindowsBuilder struct { 283 OSType ostype.ArchiveOSType 284 285 Name string 286 SizeGB int 287 DistantFrom []types.ID 288 PlanID types.ID 289 Connection types.EDiskConnection 290 Description string 291 Tags types.Tags 292 IconID types.ID 293 294 EditParameter *WindowsEditRequest 295 296 Client *APIClient 297 NoWait bool 298 299 ID types.ID 300 } 301 302 // Validate 設定値の検証 303 func (d *FromWindowsBuilder) Validate(ctx context.Context, zone string) error { 304 if !d.OSType.IsWindows() { 305 return fmt.Errorf("invalid OSType: %s", d.OSType.String()) 306 } 307 if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil { 308 return err 309 } 310 return nil 311 } 312 313 // Build ディスクの構築 314 func (d *FromWindowsBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) { 315 res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d) 316 if err != nil { 317 return nil, err 318 } 319 d.ID = res.DiskID 320 return res, nil 321 } 322 323 // Update ディスクの更新 324 func (d *FromWindowsBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) { 325 return update(ctx, d.Client, zone, d) 326 } 327 328 // DiskID ディスクID取得 329 func (d *FromWindowsBuilder) DiskID() types.ID { 330 return d.ID 331 } 332 333 // UpdateLevel Update時にどのレベルの変更が必要か 334 func (d *FromWindowsBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel { 335 return updateLevel(disk, d.EditParameter != nil, d) 336 } 337 338 func (d *FromWindowsBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest { 339 return &sacloud.DiskUpdateRequest{ 340 Name: d.Name, 341 Description: d.Description, 342 Tags: d.Tags, 343 IconID: d.IconID, 344 Connection: d.Connection, 345 } 346 } 347 348 func (d *FromWindowsBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) { 349 archive, err := archiveUtil.FindArchiveByOSType(ctx, client.Archive, zone, d.OSType) 350 if err != nil { 351 return nil, nil, err 352 } 353 354 createReq := &sacloud.DiskCreateRequest{ 355 DiskPlanID: d.PlanID, 356 SizeMB: d.SizeGB * size.GiB, 357 Connection: d.Connection, 358 SourceArchiveID: archive.ID, 359 ServerID: serverID, 360 Name: d.Name, 361 Description: d.Description, 362 Tags: d.Tags, 363 IconID: d.IconID, 364 } 365 366 var editReq *sacloud.DiskEditRequest 367 if d.EditParameter != nil { 368 editReq = d.EditParameter.prepareDiskEditParameter() 369 } 370 371 return createReq, editReq, nil 372 } 373 374 func (d *FromWindowsBuilder) NoWaitFlag() bool { 375 return d.NoWait 376 } 377 378 // FromDiskOrArchiveBuilder ディスクorアーカイブからディスクを作成するリクエスト 379 // 380 // ディスクの修正が可能かは実行時にさくらのクラウドAPI側にて判定される 381 type FromDiskOrArchiveBuilder struct { 382 SourceDiskID types.ID 383 SourceArchiveID types.ID 384 385 Name string 386 SizeGB int 387 DistantFrom []types.ID 388 PlanID types.ID 389 Connection types.EDiskConnection 390 Description string 391 Tags types.Tags 392 IconID types.ID 393 394 EditParameter *UnixEditRequest 395 396 Client *APIClient 397 398 ID types.ID 399 NoWait bool 400 401 generatedSSHKey *sacloud.SSHKeyGenerated 402 generatedNotes []*sacloud.Note 403 } 404 405 // Validate 設定値の検証 406 func (d *FromDiskOrArchiveBuilder) Validate(ctx context.Context, zone string) error { 407 if d.SourceArchiveID.IsEmpty() && d.SourceDiskID.IsEmpty() { 408 return errors.New("SourceArchiveID or SourceDiskID is required") 409 } 410 if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil { 411 return err 412 } 413 414 if !d.SourceArchiveID.IsEmpty() { 415 if _, err := d.Client.Archive.Read(ctx, zone, d.SourceArchiveID); err != nil { 416 return err 417 } 418 } 419 if !d.SourceDiskID.IsEmpty() { 420 if _, err := d.Client.Disk.Read(ctx, zone, d.SourceDiskID); err != nil { 421 return err 422 } 423 } 424 425 return nil 426 } 427 428 // Build ディスクの構築 429 func (d *FromDiskOrArchiveBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) { 430 res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d) 431 if err != nil { 432 return nil, err 433 } 434 d.ID = res.DiskID 435 if d.generatedSSHKey != nil { 436 res.GeneratedSSHKey = d.generatedSSHKey 437 } 438 439 if d.EditParameter != nil { 440 if d.EditParameter.IsSSHKeysEphemeral { 441 if err := d.Client.SSHKey.Delete(ctx, d.generatedSSHKey.ID); err != nil { 442 return nil, err 443 } 444 } 445 if d.EditParameter.IsNotesEphemeral { 446 for _, note := range d.generatedNotes { 447 if err := d.Client.Note.Delete(ctx, note.ID); err != nil { 448 return nil, err 449 } 450 } 451 } 452 } 453 return res, nil 454 } 455 456 // Update ディスクの更新 457 func (d *FromDiskOrArchiveBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) { 458 return update(ctx, d.Client, zone, d) 459 } 460 461 // DiskID ディスクID取得 462 func (d *FromDiskOrArchiveBuilder) DiskID() types.ID { 463 return d.ID 464 } 465 466 // UpdateLevel Update時にどのレベルの変更が必要か 467 func (d *FromDiskOrArchiveBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel { 468 return updateLevel(disk, d.EditParameter != nil, d) 469 } 470 471 func (d *FromDiskOrArchiveBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest { 472 return &sacloud.DiskUpdateRequest{ 473 Name: d.Name, 474 Description: d.Description, 475 Tags: d.Tags, 476 IconID: d.IconID, 477 Connection: d.Connection, 478 } 479 } 480 481 func (d *FromDiskOrArchiveBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) { 482 createReq := &sacloud.DiskCreateRequest{ 483 DiskPlanID: d.PlanID, 484 SizeMB: d.SizeGB * size.GiB, 485 Connection: d.Connection, 486 SourceArchiveID: d.SourceArchiveID, 487 SourceDiskID: d.SourceDiskID, 488 ServerID: serverID, 489 Name: d.Name, 490 Description: d.Description, 491 Tags: d.Tags, 492 IconID: d.IconID, 493 } 494 495 var editReq *sacloud.DiskEditRequest 496 if d.EditParameter != nil { 497 req, sshKey, notes, err := d.EditParameter.prepareDiskEditParameter(ctx, client) 498 if err != nil { 499 return nil, nil, err 500 } 501 editReq = req 502 if sshKey != nil { 503 d.generatedSSHKey = sshKey 504 } 505 if len(notes) > 0 { 506 d.generatedNotes = notes 507 } 508 } 509 510 return createReq, editReq, nil 511 } 512 513 func (d *FromDiskOrArchiveBuilder) NoWaitFlag() bool { 514 return d.NoWait 515 } 516 517 // BlankBuilder ブランクディスクを作成する場合のリクエスト 518 type BlankBuilder struct { 519 Name string 520 SizeGB int 521 DistantFrom []types.ID 522 PlanID types.ID 523 Connection types.EDiskConnection 524 Description string 525 Tags types.Tags 526 IconID types.ID 527 528 Client *APIClient 529 NoWait bool 530 ID types.ID 531 } 532 533 // Validate 設定値の検証 534 func (d *BlankBuilder) Validate(ctx context.Context, zone string) error { 535 if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil { 536 return err 537 } 538 return nil 539 } 540 541 // Build ディスクの構築 542 func (d *BlankBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) { 543 res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d) 544 if err != nil { 545 return nil, err 546 } 547 d.ID = res.DiskID 548 return res, err 549 } 550 551 // Update ディスクの更新 552 func (d *BlankBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) { 553 return update(ctx, d.Client, zone, d) 554 } 555 556 // DiskID ディスクID取得 557 func (d *BlankBuilder) DiskID() types.ID { 558 return d.ID 559 } 560 561 // UpdateLevel Update時にどのレベルの変更が必要か 562 func (d *BlankBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel { 563 return updateLevel(disk, false, d) 564 } 565 566 func (d *BlankBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest { 567 return &sacloud.DiskUpdateRequest{ 568 Name: d.Name, 569 Description: d.Description, 570 Tags: d.Tags, 571 IconID: d.IconID, 572 Connection: d.Connection, 573 } 574 } 575 576 func (d *BlankBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) { 577 createReq := &sacloud.DiskCreateRequest{ 578 DiskPlanID: d.PlanID, 579 SizeMB: d.SizeGB * size.GiB, 580 Connection: d.Connection, 581 ServerID: serverID, 582 Name: d.Name, 583 Description: d.Description, 584 Tags: d.Tags, 585 IconID: d.IconID, 586 } 587 return createReq, nil, nil 588 } 589 590 func (d *BlankBuilder) NoWaitFlag() bool { 591 return d.NoWait 592 } 593 594 // ConnectedDiskBuilder 既存ディスクを接続する場合のリクエスト 595 type ConnectedDiskBuilder struct { 596 ID types.ID 597 EditParameter *UnixEditRequest 598 599 Name string 600 Description string 601 Tags types.Tags 602 IconID types.ID 603 Connection types.EDiskConnection 604 605 NoWait bool 606 Client *APIClient 607 } 608 609 // Validate 設定値の検証 610 func (d *ConnectedDiskBuilder) Validate(ctx context.Context, zone string) error { 611 if d.ID.IsEmpty() { 612 return errors.New("DiskID is required") 613 } 614 615 if _, err := d.Client.Disk.Read(ctx, zone, d.ID); err != nil { 616 return err 617 } 618 619 return nil 620 } 621 622 // Build ディスクの構築 623 func (d *ConnectedDiskBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) { 624 res := &BuildResult{ 625 DiskID: d.ID, 626 } 627 if !serverID.IsEmpty() { 628 if err := d.Client.Disk.ConnectToServer(ctx, zone, d.ID, serverID); err != nil { 629 return nil, err 630 } 631 } 632 633 if d.EditParameter != nil { 634 req, sshKey, _, err := d.EditParameter.prepareDiskEditParameter(ctx, d.Client) 635 if err != nil { 636 return nil, err 637 } 638 res.GeneratedSSHKey = sshKey 639 if err := d.Client.Disk.Config(ctx, zone, d.ID, req); err != nil { 640 return nil, err 641 } 642 waiter := sacloud.WaiterForReady(func() (interface{}, error) { 643 return d.Client.Disk.Read(ctx, zone, d.ID) 644 }) 645 if _, err := waiter.WaitForState(ctx); err != nil { 646 return nil, err 647 } 648 } 649 return res, nil 650 } 651 652 // Update ディスクの更新 653 func (d *ConnectedDiskBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) { 654 disk, err := d.Client.Disk.Update(ctx, zone, d.ID, d.updateDiskParameter()) 655 if err != nil { 656 return nil, err 657 } 658 659 if d.EditParameter != nil { 660 req, _, _, err := d.EditParameter.prepareDiskEditParameter(ctx, d.Client) 661 if err != nil { 662 return nil, err 663 } 664 if err := d.Client.Disk.Config(ctx, zone, d.ID, req); err != nil { 665 return nil, err 666 } 667 waiter := sacloud.WaiterForReady(func() (interface{}, error) { 668 return d.Client.Disk.Read(ctx, zone, d.ID) 669 }) 670 if _, err := waiter.WaitForState(ctx); err != nil { 671 return nil, err 672 } 673 } 674 675 return &UpdateResult{Disk: disk}, nil 676 } 677 678 // DiskID ディスクID取得 679 func (d *ConnectedDiskBuilder) DiskID() types.ID { 680 return d.ID 681 } 682 683 // UpdateLevel Update時にどのレベルの変更が必要か 684 func (d *ConnectedDiskBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel { 685 return updateLevel(disk, d.EditParameter != nil, d) 686 } 687 688 func (d *ConnectedDiskBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest { 689 return &sacloud.DiskUpdateRequest{ 690 Name: d.Name, 691 Description: d.Description, 692 Tags: d.Tags, 693 IconID: d.IconID, 694 Connection: d.Connection, 695 } 696 } 697 698 func (d *ConnectedDiskBuilder) createDiskParameter( 699 ctx context.Context, 700 client *APIClient, 701 zone string, 702 serverID types.ID, 703 ) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) { 704 // noop 705 return nil, nil, nil 706 } 707 708 func (d *ConnectedDiskBuilder) NoWaitFlag() bool { 709 return d.NoWait 710 } 711 712 type diskBuilder interface { 713 createDiskParameter( 714 ctx context.Context, 715 client *APIClient, 716 zone string, 717 serverID types.ID, 718 ) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) 719 updateDiskParameter() *sacloud.DiskUpdateRequest 720 DiskID() types.ID 721 NoWaitFlag() bool 722 } 723 724 func build(ctx context.Context, client *APIClient, zone string, serverID types.ID, distantFrom []types.ID, builder diskBuilder) (*BuildResult, error) { 725 var err error 726 727 diskReq, editReq, err := builder.createDiskParameter(ctx, client, zone, serverID) 728 if err != nil { 729 return nil, err 730 } 731 if diskReq == nil { 732 return nil, fmt.Errorf("disk create request is nil") 733 } 734 diskReq.ServerID = serverID 735 736 var disk *sacloud.Disk 737 738 if editReq == nil { 739 disk, err = client.Disk.Create(ctx, zone, diskReq, distantFrom) 740 } else { 741 disk, err = client.Disk.CreateWithConfig(ctx, zone, diskReq, editReq, false, distantFrom) 742 } 743 if err != nil { 744 if disk != nil { 745 return &BuildResult{DiskID: disk.ID}, err 746 } 747 return nil, err 748 } 749 750 if builder.NoWaitFlag() { 751 return &BuildResult{DiskID: disk.ID}, nil 752 } 753 754 waiter := sacloud.WaiterForReady(func() (interface{}, error) { 755 return client.Disk.Read(ctx, zone, disk.ID) 756 }) 757 lastState, err := waiter.WaitForState(ctx) 758 if err != nil { 759 if lastState != nil { 760 return &BuildResult{DiskID: lastState.(*sacloud.Disk).ID}, err 761 } 762 return nil, err 763 } 764 disk = lastState.(*sacloud.Disk) 765 766 return &BuildResult{DiskID: disk.ID}, nil 767 } 768 769 func update(ctx context.Context, client *APIClient, zone string, builder diskBuilder) (*UpdateResult, error) { 770 var err error 771 772 diskID := builder.DiskID() 773 if diskID.IsEmpty() { 774 return nil, fmt.Errorf("disk id required") 775 } 776 777 diskReq, editReq, err := builder.createDiskParameter(ctx, client, zone, types.ID(0)) 778 if err != nil { 779 return nil, err 780 } 781 if diskReq == nil { 782 return nil, fmt.Errorf("disk update request is nil") 783 } 784 785 disk, err := client.Disk.Update(ctx, zone, diskID, &sacloud.DiskUpdateRequest{ 786 Name: diskReq.Name, 787 Description: diskReq.Description, 788 Tags: diskReq.Tags, 789 IconID: diskReq.IconID, 790 Connection: diskReq.Connection, 791 }) 792 if err != nil { 793 return nil, err 794 } 795 796 if editReq != nil { 797 if err := client.Disk.Config(ctx, zone, disk.ID, editReq); err != nil { 798 return nil, err 799 } 800 } 801 802 if builder.NoWaitFlag() { 803 return &UpdateResult{Disk: disk}, nil 804 } 805 806 waiter := sacloud.WaiterForReady(func() (interface{}, error) { 807 return client.Disk.Read(ctx, zone, disk.ID) 808 }) 809 lastState, err := waiter.WaitForState(ctx) 810 if err != nil { 811 return nil, err 812 } 813 disk = lastState.(*sacloud.Disk) 814 815 return &UpdateResult{Disk: disk}, nil 816 } 817 818 func validateDiskPlan(ctx context.Context, client *APIClient, zone string, diskPlanID types.ID, sizeGB int) error { 819 plan, err := client.DiskPlan.Read(ctx, zone, diskPlanID) 820 if err != nil { 821 return err 822 } 823 found := false 824 for _, size := range plan.Size { 825 if size.Availability.IsAvailable() && size.GetSizeGB() == sizeGB { 826 found = true 827 break 828 } 829 } 830 if !found { 831 return fmt.Errorf("disk plan[%s:%dGB] is not found", plan.Name, sizeGB) 832 } 833 return nil 834 } 835 836 func updateLevel(disk *sacloud.Disk, hasEditReq bool, b diskBuilder) builder.UpdateLevel { 837 if disk.ID != b.DiskID() || hasEditReq { 838 return builder.UpdateLevelNeedShutdown 839 } 840 841 current := &sacloud.DiskUpdateRequest{ 842 Name: disk.Name, 843 Description: disk.Description, 844 Tags: disk.Tags, 845 IconID: disk.IconID, 846 Connection: disk.Connection, 847 } 848 desired := b.updateDiskParameter() 849 if desired == nil { 850 return builder.UpdateLevelNone 851 } 852 if reflect.DeepEqual(current, desired) { 853 if current.Connection != desired.Connection { 854 return builder.UpdateLevelNeedShutdown 855 } 856 return builder.UpdateLevelSimple 857 } 858 return builder.UpdateLevelNone 859 }