github.com/sacloud/libsacloud/v2@v2.32.3/helper/builder/server/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 server 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/helper/builder/disk" 25 "github.com/sacloud/libsacloud/v2/helper/plans" 26 "github.com/sacloud/libsacloud/v2/helper/power" 27 "github.com/sacloud/libsacloud/v2/helper/query" 28 "github.com/sacloud/libsacloud/v2/pkg/size" 29 "github.com/sacloud/libsacloud/v2/sacloud" 30 "github.com/sacloud/libsacloud/v2/sacloud/types" 31 ) 32 33 // Builder サーバ作成時のパラメータ 34 type Builder struct { 35 Name string 36 CPU int 37 MemoryGB int 38 GPU int 39 Commitment types.ECommitment 40 Generation types.EPlanGeneration 41 InterfaceDriver types.EInterfaceDriver 42 Description string 43 IconID types.ID 44 Tags types.Tags 45 BootAfterCreate bool 46 CDROMID types.ID 47 PrivateHostID types.ID 48 NIC NICSettingHolder 49 AdditionalNICs []AdditionalNICSettingHolder 50 DiskBuilders []disk.Builder 51 52 UserData string 53 54 Client *APIClient 55 56 NoWait bool 57 58 ServerID types.ID 59 ForceShutdown bool 60 } 61 62 func BuilderFromResource(ctx context.Context, caller sacloud.APICaller, zone string, id types.ID) (*Builder, error) { 63 serverOp := sacloud.NewServerOp(caller) 64 current, err := serverOp.Read(ctx, zone, id) 65 if err != nil { 66 return nil, err 67 } 68 69 var nic NICSettingHolder 70 if len(current.Interfaces) > 0 { 71 iface := current.Interfaces[0] 72 switch { 73 case iface.SwitchID.IsEmpty(): 74 nic = &DisconnectedNICSetting{} 75 case iface.SwitchScope == types.Scopes.Shared: 76 nic = &SharedNICSetting{PacketFilterID: iface.PacketFilterID} 77 default: 78 nic = &ConnectedNICSetting{ 79 SwitchID: iface.SwitchID, 80 DisplayIPAddress: iface.UserIPAddress, 81 PacketFilterID: iface.PacketFilterID, 82 } 83 } 84 } 85 86 var additionalNICs []AdditionalNICSettingHolder 87 if len(current.Interfaces) > 1 { 88 for i, iface := range current.Interfaces { 89 if i == 0 { 90 continue 91 } 92 switch { 93 case iface.SwitchID.IsEmpty(): 94 additionalNICs = append(additionalNICs, &DisconnectedNICSetting{}) 95 default: 96 additionalNICs = append(additionalNICs, &ConnectedNICSetting{ 97 SwitchID: iface.SwitchID, 98 DisplayIPAddress: iface.UserIPAddress, 99 PacketFilterID: iface.PacketFilterID, 100 }) 101 } 102 } 103 } 104 105 diskOp := sacloud.NewDiskOp(caller) 106 var diskBuilders []disk.Builder 107 for _, d := range current.Disks { 108 currentDisk, err := diskOp.Read(ctx, zone, d.ID) 109 if err != nil { 110 return nil, err 111 } 112 diskBuilders = append(diskBuilders, &disk.ConnectedDiskBuilder{ 113 ID: currentDisk.ID, 114 EditParameter: nil, 115 Name: currentDisk.Name, 116 Description: currentDisk.Description, 117 Tags: currentDisk.Tags, 118 IconID: currentDisk.IconID, 119 Connection: currentDisk.Connection, 120 NoWait: false, 121 Client: disk.NewBuildersAPIClient(caller), 122 }) 123 } 124 125 return &Builder{ 126 Name: current.Name, 127 CPU: current.CPU, 128 MemoryGB: current.MemoryMB * size.GiB, 129 GPU: current.GPU, 130 Commitment: current.ServerPlanCommitment, 131 Generation: current.ServerPlanGeneration, 132 InterfaceDriver: current.InterfaceDriver, 133 Description: current.Description, 134 IconID: current.IconID, 135 Tags: current.Tags, 136 BootAfterCreate: false, 137 CDROMID: current.CDROMID, 138 PrivateHostID: current.PrivateHostID, 139 NIC: nic, 140 AdditionalNICs: additionalNICs, 141 DiskBuilders: diskBuilders, 142 Client: NewBuildersAPIClient(caller), 143 ServerID: current.ID, 144 }, nil 145 } 146 147 // BuildResult サーバ構築結果 148 type BuildResult struct { 149 ServerID types.ID 150 DiskIDs []types.ID 151 GeneratedSSHPrivateKey string 152 } 153 154 var ( 155 defaultCPU = 1 156 defaultMemoryGB = 1 157 defaultGPU = 0 158 defaultCommitment = types.Commitments.Standard 159 defaultGeneration = types.PlanGenerations.Default 160 defaultInterfaceDriver = types.InterfaceDrivers.VirtIO 161 ) 162 163 // Validate 入力値の検証 164 // 165 // 各種IDの存在確認のためにAPIリクエストが行われます。 166 func (b *Builder) Validate(ctx context.Context, zone string) error { 167 b.setDefaults() 168 169 // Fields 170 if b.Client == nil { 171 return errors.New("client is empty") 172 } 173 174 if b.NIC == nil && len(b.AdditionalNICs) > 0 { 175 return errors.New("NIC is required when AdditionalNICs is specified") 176 } 177 178 if len(b.AdditionalNICs) > 9 { 179 return errors.New("AdditionalNICs must be less than 9") 180 } 181 182 if b.InterfaceDriver != types.InterfaceDrivers.E1000 && b.InterfaceDriver != types.InterfaceDrivers.VirtIO { 183 return fmt.Errorf("invalid InterfaceDriver: %s", b.InterfaceDriver) 184 } 185 186 // NICs 187 if b.NIC != nil { 188 if err := b.NIC.Validate(ctx, b.Client, zone); err != nil { 189 return fmt.Errorf("invalid NIC: %s", err) 190 } 191 } 192 for i, nic := range b.AdditionalNICs { 193 if err := nic.Validate(ctx, b.Client, zone); err != nil { 194 return fmt.Errorf("invalid AdditionalNICs[%d]: %s", i, err) 195 } 196 } 197 198 // server plan 199 _, err := query.FindServerPlan(ctx, b.Client.ServerPlan, zone, &query.FindServerPlanRequest{ 200 CPU: b.CPU, 201 MemoryGB: b.MemoryGB, 202 GPU: b.GPU, 203 Commitment: b.Commitment, 204 Generation: b.Generation, 205 }) 206 if err != nil { 207 return err 208 } 209 210 for _, diskBuilder := range b.DiskBuilders { 211 if err := diskBuilder.Validate(ctx, zone); err != nil { 212 return err 213 } 214 if b.NoWait && !diskBuilder.NoWaitFlag() { 215 return errors.New("NoWait=true is not supported if the disks contain NoWait=false") 216 } 217 } 218 219 if b.NoWait && b.BootAfterCreate { 220 return errors.New("NoWait=true is not supported with BootAfterCreate=true") 221 } 222 223 return nil 224 } 225 226 // Build サーバ構築を行う 227 func (b *Builder) Build(ctx context.Context, zone string) (*BuildResult, error) { 228 // validate 229 if err := b.Validate(ctx, zone); err != nil { 230 return nil, err 231 } 232 233 // create server 234 server, err := b.createServer(ctx, zone) 235 if err != nil { 236 return nil, err 237 } 238 result := &BuildResult{ 239 ServerID: server.ID, 240 } 241 242 // create&connect disk(s) 243 for _, diskReq := range b.DiskBuilders { 244 builtDisk, err := diskReq.Build(ctx, zone, server.ID) 245 if err != nil { 246 return result, err 247 } 248 result.DiskIDs = append(result.DiskIDs, builtDisk.DiskID) 249 if builtDisk.GeneratedSSHKey != nil { 250 result.GeneratedSSHPrivateKey = builtDisk.GeneratedSSHKey.PrivateKey 251 } 252 } 253 254 // connect packet filter 255 if err := b.updateInterfaces(ctx, zone, server); err != nil { 256 return result, err 257 } 258 259 // insert CD-ROM 260 if !b.CDROMID.IsEmpty() { 261 req := &sacloud.InsertCDROMRequest{ID: b.CDROMID} 262 if err := b.Client.Server.InsertCDROM(ctx, zone, server.ID, req); err != nil { 263 return result, err 264 } 265 } 266 267 // bool 268 if !b.NoWait && b.BootAfterCreate { 269 if err := power.BootServer(ctx, b.Client.Server, zone, server.ID, b.userData()...); err != nil { 270 return result, err 271 } 272 } 273 274 b.ServerID = result.ServerID 275 return result, nil 276 } 277 278 // IsNeedShutdown Update時にシャットダウンが必要か 279 func (b *Builder) IsNeedShutdown(ctx context.Context, zone string) (bool, error) { 280 if b.ServerID.IsEmpty() { 281 return false, fmt.Errorf("server id required") 282 } 283 284 server, err := b.Client.Server.Read(ctx, zone, b.ServerID) 285 if err != nil { 286 return false, err 287 } 288 289 if b.UserData != "" { 290 return true, nil 291 } 292 293 current := b.currentState(server) 294 desired := b.desiredState() 295 296 // シャットダウンが不要な項目には固定値を入れる 297 var nics []*nicState 298 nics = append(nics, current.nic) 299 nics = append(nics, current.additionalNICs...) 300 nics = append(nics, desired.nic) 301 nics = append(nics, desired.additionalNICs...) 302 b.fillDummyValueToState(nics...) 303 304 if !reflect.DeepEqual(current, desired) { 305 return true, nil 306 } 307 308 // ここに到達するときはserver.Disksとb.DiskBuildersは同数となっている 309 for i, disk := range server.Disks { 310 level := b.DiskBuilders[i].UpdateLevel(ctx, zone, &sacloud.Disk{ 311 ID: disk.ID, 312 Name: disk.Name, 313 Availability: disk.Availability, 314 Connection: disk.Connection, 315 ConnectionOrder: disk.ConnectionOrder, 316 ReinstallCount: disk.ReinstallCount, 317 SizeMB: disk.SizeMB, 318 DiskPlanID: disk.DiskPlanID, 319 Storage: disk.Storage, 320 }) 321 322 if level == builder.UpdateLevelNeedShutdown { 323 return true, nil 324 } 325 } 326 return false, nil 327 } 328 329 func (b *Builder) fillDummyValueToState(state ...*nicState) { 330 for _, s := range state { 331 if s != nil { 332 s.packetFilterID = types.ID(0) 333 s.displayIP = "" 334 } 335 } 336 } 337 338 // Update サーバの更新 339 func (b *Builder) Update(ctx context.Context, zone string) (*BuildResult, error) { 340 // validate 341 if err := b.Validate(ctx, zone); err != nil { 342 return nil, err 343 } 344 if b.ServerID.IsEmpty() { 345 return nil, fmt.Errorf("server id required") 346 } 347 348 result := &BuildResult{ServerID: b.ServerID} 349 350 server, err := b.Client.Server.Read(ctx, zone, b.ServerID) 351 if err != nil { 352 return result, err 353 } 354 355 isNeedShutdown, err := b.IsNeedShutdown(ctx, zone) 356 if err != nil { 357 return result, err 358 } 359 360 // shutdown 361 running := server.InstanceStatus.IsUp() 362 if isNeedShutdown && running { 363 if b.NoWait { 364 return nil, errors.New("NoWait option is not available due to the need to shut down") 365 } 366 if err := power.ShutdownServer(ctx, b.Client.Server, zone, server.ID, b.ForceShutdown); err != nil { 367 return result, err 368 } 369 } 370 371 // reconcile disks 372 if err := b.reconcileDisks(ctx, zone, server, result); err != nil { 373 return result, err 374 } 375 376 // reconcile interface 377 if err := b.reconcileInterfaces(ctx, zone, server); err != nil { 378 return result, err 379 } 380 381 // plan 382 if b.isPlanChanged(server) { 383 b.Tags = plans.AppendPreviousIDTagIfAbsent(b.Tags, server.ID) 384 updated, err := b.Client.Server.ChangePlan(ctx, zone, server.ID, &sacloud.ServerChangePlanRequest{ 385 CPU: b.CPU, 386 MemoryMB: b.MemoryGB * size.GiB, 387 GPU: b.GPU, 388 ServerPlanGeneration: b.Generation, 389 ServerPlanCommitment: b.Commitment, 390 }) 391 if err != nil { 392 return result, err 393 } 394 server = updated 395 } 396 397 // update 398 updated, err := b.Client.Server.Update(ctx, zone, server.ID, &sacloud.ServerUpdateRequest{ 399 Name: b.Name, 400 Description: b.Description, 401 Tags: b.Tags, 402 IconID: b.IconID, 403 PrivateHostID: b.PrivateHostID, 404 InterfaceDriver: b.InterfaceDriver, 405 }) 406 if err != nil { 407 return result, err 408 } 409 server = updated 410 result.ServerID = server.ID 411 412 // insert CD-ROM 413 if !b.CDROMID.IsEmpty() && b.CDROMID != server.CDROMID { 414 if !server.CDROMID.IsEmpty() { 415 if err := b.Client.Server.EjectCDROM(ctx, zone, server.ID, &sacloud.EjectCDROMRequest{ID: server.CDROMID}); err != nil { 416 return result, err 417 } 418 } 419 if err := b.Client.Server.InsertCDROM(ctx, zone, server.ID, &sacloud.InsertCDROMRequest{ID: b.CDROMID}); err != nil { 420 return result, err 421 } 422 } 423 424 // boot 425 if isNeedShutdown && running && server.InstanceStatus.IsDown() { 426 if err := power.BootServer(ctx, b.Client.Server, zone, server.ID, b.userData()...); err != nil { 427 return result, err 428 } 429 } 430 431 result.ServerID = server.ID 432 return result, nil 433 } 434 435 func (b *Builder) setDefaults() { 436 if b.CPU == 0 { 437 b.CPU = defaultCPU 438 } 439 if b.MemoryGB == 0 { 440 b.MemoryGB = defaultMemoryGB 441 } 442 if b.GPU == 0 { 443 b.GPU = defaultGPU 444 } 445 if b.Commitment == types.ECommitment("") { 446 b.Commitment = defaultCommitment 447 } 448 if b.Generation == types.EPlanGeneration(0) { 449 b.Generation = defaultGeneration 450 } 451 if b.InterfaceDriver == types.EInterfaceDriver("") { 452 b.InterfaceDriver = defaultInterfaceDriver 453 } 454 } 455 456 type serverState struct { 457 privateHostID types.ID 458 interfaceDriver types.EInterfaceDriver 459 memoryGB int 460 cpu int 461 gpu int 462 commitment types.ECommitment 463 nic *nicState // hash 464 additionalNICs []*nicState // hash 465 diskCount int 466 } 467 468 func (b *Builder) desiredState() *serverState { 469 var nic *nicState 470 if b.NIC != nil { 471 nic = b.NIC.state() 472 } 473 var additionalNICs []*nicState 474 for _, n := range b.AdditionalNICs { 475 additionalNICs = append(additionalNICs, n.state()) 476 } 477 478 return &serverState{ 479 privateHostID: b.PrivateHostID, 480 interfaceDriver: b.InterfaceDriver, 481 memoryGB: b.MemoryGB, 482 cpu: b.CPU, 483 gpu: b.GPU, 484 commitment: b.Commitment, 485 nic: nic, 486 additionalNICs: additionalNICs, 487 diskCount: len(b.DiskBuilders), 488 } 489 } 490 491 func (b *Builder) currentNICState(nic *sacloud.InterfaceView) *nicState { 492 var state *nicState 493 494 switch { 495 case nic.SwitchScope == types.Scopes.Shared: 496 state = &nicState{ 497 upstreamType: types.UpstreamNetworkTypes.Shared, 498 switchID: types.ID(0), 499 packetFilterID: nic.PacketFilterID, 500 displayIP: "", 501 } 502 case nic.SwitchID.IsEmpty(): 503 state = &nicState{ 504 upstreamType: types.UpstreamNetworkTypes.None, 505 switchID: types.ID(0), 506 packetFilterID: types.ID(0), 507 displayIP: "", 508 } 509 default: 510 state = &nicState{ 511 upstreamType: types.UpstreamNetworkTypes.Switch, 512 switchID: nic.SwitchID, 513 packetFilterID: nic.PacketFilterID, 514 displayIP: nic.UserIPAddress, 515 } 516 } 517 return state 518 } 519 520 func (b *Builder) currentState(server *sacloud.Server) *serverState { 521 var nic *nicState 522 var additionalNICs []*nicState 523 for i, n := range server.Interfaces { 524 state := b.currentNICState(n) 525 if i == 0 { 526 nic = state 527 } else { 528 additionalNICs = append(additionalNICs, state) 529 } 530 } 531 532 return &serverState{ 533 privateHostID: server.PrivateHostID, 534 interfaceDriver: server.InterfaceDriver, 535 memoryGB: server.GetMemoryGB(), 536 cpu: server.CPU, 537 gpu: server.GPU, 538 commitment: server.ServerPlanCommitment, 539 nic: nic, 540 additionalNICs: additionalNICs, 541 diskCount: len(server.Disks), 542 } 543 } 544 545 // createServer サーバ作成 546 func (b *Builder) createServer(ctx context.Context, zone string) (*sacloud.Server, error) { 547 param := &sacloud.ServerCreateRequest{ 548 CPU: b.CPU, 549 MemoryMB: b.MemoryGB * size.GiB, 550 GPU: b.GPU, 551 ServerPlanCommitment: b.Commitment, 552 ServerPlanGeneration: b.Generation, 553 InterfaceDriver: b.InterfaceDriver, 554 Name: b.Name, 555 Description: b.Description, 556 Tags: b.Tags, 557 IconID: b.IconID, 558 WaitDiskMigration: false, 559 PrivateHostID: b.PrivateHostID, 560 ConnectedSwitches: []*sacloud.ConnectedSwitch{}, 561 } 562 if b.NIC != nil { 563 cs := b.NIC.GetConnectedSwitchParam() 564 if cs == nil { 565 param.ConnectedSwitches = append(param.ConnectedSwitches, nil) 566 } else { 567 param.ConnectedSwitches = append(param.ConnectedSwitches, cs) 568 } 569 } 570 if len(b.AdditionalNICs) > 0 { 571 for _, nic := range b.AdditionalNICs { 572 switchID := nic.GetSwitchID() 573 if switchID.IsEmpty() { 574 param.ConnectedSwitches = append(param.ConnectedSwitches, nil) 575 } else { 576 param.ConnectedSwitches = append(param.ConnectedSwitches, &sacloud.ConnectedSwitch{ID: switchID}) 577 } 578 } 579 } 580 return b.Client.Server.Create(ctx, zone, param) 581 } 582 583 type updateInterfaceRequest struct { 584 index int 585 packetFilterID types.ID 586 displayIP string 587 } 588 589 func (b *Builder) collectInterfaceParameters() []*updateInterfaceRequest { 590 var reqs []*updateInterfaceRequest 591 if b.NIC != nil { 592 reqs = append(reqs, &updateInterfaceRequest{ 593 index: 0, 594 packetFilterID: b.NIC.GetPacketFilterID(), 595 displayIP: b.NIC.GetDisplayIPAddress(), 596 }) 597 } 598 for i, nic := range b.AdditionalNICs { 599 reqs = append(reqs, &updateInterfaceRequest{ 600 index: i + 1, 601 packetFilterID: nic.GetPacketFilterID(), 602 displayIP: nic.GetDisplayIPAddress(), 603 }) 604 } 605 return reqs 606 } 607 608 func (b *Builder) updateInterfaces(ctx context.Context, zone string, server *sacloud.Server) error { 609 requests := b.collectInterfaceParameters() 610 for _, req := range requests { 611 if req.index < len(server.Interfaces) { 612 iface := server.Interfaces[req.index] 613 614 if !req.packetFilterID.IsEmpty() { 615 if err := b.Client.Interface.ConnectToPacketFilter(ctx, zone, iface.ID, req.packetFilterID); err != nil { 616 return err 617 } 618 } 619 620 if req.displayIP != "" { 621 if _, err := b.Client.Interface.Update(ctx, zone, iface.ID, &sacloud.InterfaceUpdateRequest{ 622 UserIPAddress: req.displayIP, 623 }); err != nil { 624 return err 625 } 626 } 627 } 628 } 629 return nil 630 } 631 632 func (b *Builder) reconcileDisks(ctx context.Context, zone string, server *sacloud.Server, result *BuildResult) error { 633 // reconcile disks 634 isDiskUpdated := len(server.Disks) != len(b.DiskBuilders) // isDiskUpdateがtrueの場合、後でディスクの取外&接続を行う 635 for i, diskReq := range b.DiskBuilders { 636 if diskReq.DiskID().IsEmpty() { 637 res, err := diskReq.Build(ctx, zone, server.ID) 638 if err != nil { 639 return err 640 } 641 if res.GeneratedSSHKey != nil { 642 result.GeneratedSSHPrivateKey = res.GeneratedSSHKey.PrivateKey 643 } 644 isDiskUpdated = true 645 } 646 if len(server.Disks) > i { 647 disk := server.Disks[i] 648 level := diskReq.UpdateLevel(ctx, zone, &sacloud.Disk{ 649 ID: disk.ID, 650 Name: disk.Name, 651 Availability: disk.Availability, 652 Connection: disk.Connection, 653 ConnectionOrder: disk.ConnectionOrder, 654 ReinstallCount: disk.ReinstallCount, 655 SizeMB: disk.SizeMB, 656 DiskPlanID: disk.DiskPlanID, 657 Storage: disk.Storage, 658 }) 659 if level != builder.UpdateLevelNone { 660 _, err := diskReq.Update(ctx, zone) 661 if err != nil { 662 return err 663 } 664 } 665 if disk.ID != diskReq.DiskID() { 666 isDiskUpdated = true 667 } 668 } 669 } 670 if isDiskUpdated { 671 refreshed, err := b.Client.Server.Read(ctx, zone, server.ID) 672 if err != nil { 673 return err 674 } 675 server = refreshed 676 677 // disconnect all 678 for i := range server.Disks { 679 // disconnect 680 if err := b.Client.Disk.DisconnectFromServer(ctx, zone, server.Disks[i].ID); err != nil { 681 return err 682 } 683 } 684 // reconnect all 685 for _, diskReq := range b.DiskBuilders { 686 result.DiskIDs = []types.ID{} 687 if err := b.Client.Disk.ConnectToServer(ctx, zone, diskReq.DiskID(), server.ID); err != nil { 688 return err 689 } 690 result.DiskIDs = append(result.DiskIDs, diskReq.DiskID()) 691 } 692 } 693 return nil 694 } 695 696 func (b *Builder) reconcileInterfaces(ctx context.Context, zone string, server *sacloud.Server) error { 697 desiredState := b.desiredState() 698 for i, nic := range server.Interfaces { 699 current := b.currentNICState(nic) 700 var desired *nicState 701 if i == 0 { 702 desired = desiredState.nic 703 } else { 704 if len(desiredState.additionalNICs) > i-1 { 705 desired = desiredState.additionalNICs[i-1] 706 } 707 } 708 if desired == nil { 709 // disconnect and delete 710 if !nic.SwitchID.IsEmpty() { 711 if err := b.Client.Interface.DisconnectFromSwitch(ctx, zone, nic.ID); err != nil { 712 return err 713 } 714 } 715 if err := b.Client.Interface.Delete(ctx, zone, nic.ID); err != nil { 716 return err 717 } 718 continue 719 } 720 if current.upstreamType != desired.upstreamType || 721 current.switchID != desired.switchID { 722 if !nic.SwitchID.IsEmpty() { 723 if err := b.Client.Interface.DisconnectFromSwitch(ctx, zone, nic.ID); err != nil { 724 return err 725 } 726 } 727 } 728 } 729 730 desiredNICs := []*nicState{desiredState.nic} 731 desiredNICs = append(desiredNICs, desiredState.additionalNICs...) 732 733 for i, desired := range desiredNICs { 734 if desired == nil { 735 continue 736 } 737 var nic *sacloud.InterfaceView 738 if len(server.Interfaces) > i { 739 nic = server.Interfaces[i] 740 } 741 if nic == nil { 742 created, err := b.Client.Interface.Create(ctx, zone, &sacloud.InterfaceCreateRequest{ 743 ServerID: server.ID, 744 }) 745 if err != nil { 746 return err 747 } 748 nic = &sacloud.InterfaceView{ 749 ID: created.ID, 750 MACAddress: created.MACAddress, 751 IPAddress: created.IPAddress, 752 UserIPAddress: created.UserIPAddress, 753 HostName: created.HostName, 754 SwitchID: created.SwitchID, 755 SwitchScope: created.SwitchScope, 756 PacketFilterID: created.PacketFilterID, 757 } 758 } 759 switch desired.upstreamType { 760 case types.UpstreamNetworkTypes.None: 761 // noop 762 case types.UpstreamNetworkTypes.Shared: 763 if nic.SwitchScope != types.Scopes.Shared { 764 if err := b.Client.Interface.ConnectToSharedSegment(ctx, zone, nic.ID); err != nil { 765 return err 766 } 767 } 768 default: 769 if nic.SwitchID != desired.switchID { 770 if err := b.Client.Interface.ConnectToSwitch(ctx, zone, nic.ID, desired.switchID); err != nil { 771 return err 772 } 773 } 774 } 775 if desired.packetFilterID != nic.PacketFilterID { 776 if !nic.PacketFilterID.IsEmpty() { 777 if err := b.Client.Interface.DisconnectFromPacketFilter(ctx, zone, nic.ID); err != nil { 778 return err 779 } 780 } 781 if !desired.packetFilterID.IsEmpty() { 782 if err := b.Client.Interface.ConnectToPacketFilter(ctx, zone, nic.ID, desired.packetFilterID); err != nil { 783 return err 784 } 785 } 786 } 787 if desired.displayIP != nic.UserIPAddress { 788 if _, err := b.Client.Interface.Update(ctx, zone, nic.ID, &sacloud.InterfaceUpdateRequest{ 789 UserIPAddress: desired.displayIP, 790 }); err != nil { 791 return err 792 } 793 } 794 } 795 return nil 796 } 797 798 func (b *Builder) isPlanChanged(server *sacloud.Server) bool { 799 return b.CPU != server.CPU || 800 b.MemoryGB != server.GetMemoryGB() || 801 b.GPU != server.GPU || 802 b.Commitment != server.ServerPlanCommitment || 803 (b.Generation != types.PlanGenerations.Default && b.Generation != server.ServerPlanGeneration) 804 //b.Generation != server.ServerPlanGeneration 805 } 806 807 func (b *Builder) userData() []string { 808 if b.UserData == "" { 809 return nil 810 } 811 return []string{b.UserData} 812 }