github.com/sacloud/libsacloud/v2@v2.32.3/helper/builder/mobilegateway/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 mobilegateway 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "reflect" 22 "time" 23 24 "github.com/sacloud/libsacloud/v2/helper/builder" 25 "github.com/sacloud/libsacloud/v2/helper/power" 26 "github.com/sacloud/libsacloud/v2/helper/setup" 27 "github.com/sacloud/libsacloud/v2/sacloud" 28 "github.com/sacloud/libsacloud/v2/sacloud/accessor" 29 "github.com/sacloud/libsacloud/v2/sacloud/types" 30 ) 31 32 // Builder モバイルゲートウェイの構築を行う 33 type Builder struct { 34 Name string 35 Description string 36 Tags types.Tags 37 IconID types.ID 38 PrivateInterface *PrivateInterfaceSetting 39 StaticRoutes []*sacloud.MobileGatewayStaticRoute 40 SIMRoutes []*SIMRouteSetting 41 InternetConnectionEnabled bool 42 InterDeviceCommunicationEnabled bool 43 DNS *sacloud.MobileGatewayDNSSetting 44 SIMs []*SIMSetting 45 TrafficConfig *sacloud.MobileGatewayTrafficControl 46 47 SettingsHash string 48 NoWait bool 49 50 SetupOptions *builder.RetryableSetupParameter 51 Client *APIClient 52 } 53 54 // PrivateInterfaceSetting モバイルゲートウェイのプライベート側インターフェース設定 55 type PrivateInterfaceSetting struct { 56 SwitchID types.ID 57 IPAddress string 58 NetworkMaskLen int 59 } 60 61 // SIMSetting モバイルゲートウェイに接続するSIM設定 62 type SIMSetting struct { 63 SIMID types.ID 64 IPAddress string 65 } 66 67 // SIMRouteSetting SIMルート設定 68 type SIMRouteSetting struct { 69 SIMID types.ID 70 Prefix string 71 } 72 73 // BuilderFromResource 既存のMobileGatewayからBuilderを組み立てて返す 74 func BuilderFromResource(ctx context.Context, caller sacloud.APICaller, zone string, id types.ID) (*Builder, error) { 75 mgwOp := sacloud.NewMobileGatewayOp(caller) 76 current, err := mgwOp.Read(ctx, zone, id) 77 if err != nil { 78 return nil, err 79 } 80 81 var privateInterface *PrivateInterfaceSetting 82 for i, nic := range current.InterfaceSettings { 83 if nic.Index == 1 { 84 privateInterface = &PrivateInterfaceSetting{ 85 SwitchID: current.Interfaces[i].SwitchID, 86 IPAddress: nic.IPAddress[0], 87 NetworkMaskLen: nic.NetworkMaskLen, 88 } 89 } 90 } 91 92 simRoutes, err := mgwOp.GetSIMRoutes(ctx, zone, id) 93 if err != nil { 94 return nil, err 95 } 96 var simRouteSettings []*SIMRouteSetting 97 for _, r := range simRoutes { 98 simRouteSettings = append(simRouteSettings, &SIMRouteSetting{ 99 SIMID: types.StringID(r.ResourceID), 100 Prefix: r.Prefix, 101 }) 102 } 103 104 dns, err := mgwOp.GetDNS(ctx, zone, id) 105 if err != nil { 106 return nil, err 107 } 108 109 sims, err := mgwOp.ListSIM(ctx, zone, id) 110 if err != nil { 111 return nil, err 112 } 113 var simSettings []*SIMSetting 114 for _, s := range sims { 115 simSettings = append(simSettings, &SIMSetting{ 116 SIMID: types.StringID(s.ResourceID), 117 IPAddress: s.IP, 118 }) 119 } 120 121 trafficConfig, err := mgwOp.GetTrafficConfig(ctx, zone, id) 122 if err != nil { 123 return nil, err 124 } 125 126 return &Builder{ 127 Name: current.Name, 128 Description: current.Description, 129 Tags: current.Tags, 130 IconID: current.IconID, 131 PrivateInterface: privateInterface, 132 StaticRoutes: current.StaticRoutes, 133 SIMRoutes: simRouteSettings, 134 InternetConnectionEnabled: current.InternetConnectionEnabled.Bool(), 135 InterDeviceCommunicationEnabled: current.InterDeviceCommunicationEnabled.Bool(), 136 DNS: dns, 137 SIMs: simSettings, 138 TrafficConfig: trafficConfig, 139 SettingsHash: current.SettingsHash, 140 NoWait: false, 141 Client: NewAPIClient(caller), 142 }, nil 143 } 144 145 func (b *Builder) init() { 146 if b.SetupOptions == nil { 147 b.SetupOptions = builder.DefaultSetupOptions() 148 } 149 } 150 151 // Validate 設定値の検証 152 func (b *Builder) Validate(ctx context.Context, zone string) error { 153 if b.PrivateInterface != nil { 154 if b.PrivateInterface.SwitchID.IsEmpty() { 155 return fmt.Errorf("switch id is required when specified private interface") 156 } 157 if b.PrivateInterface.IPAddress == "" { 158 return fmt.Errorf("ip address is required when specified private interface") 159 } 160 if b.PrivateInterface.NetworkMaskLen == 0 { 161 return fmt.Errorf("ip address is required when specified private interface") 162 } 163 } 164 if len(b.SIMRoutes) > 0 && len(b.SIMs) == 0 { 165 return fmt.Errorf("sim settings are required when specified sim routes") 166 } 167 168 if b.NoWait { 169 if b.PrivateInterface != nil || len(b.StaticRoutes) > 0 || len(b.SIMRoutes) > 0 || b.DNS != nil || len(b.SIMs) > 0 || b.TrafficConfig != nil { 170 return errors.New("NoWait=true is not supported with PrivateInterface/StaticRoutes/SIMRoutes/DNS/SIMs/TrafficConfig") 171 } 172 } 173 return nil 174 } 175 176 // Build モバイルゲートウェイの作成や設定をまとめて行う 177 func (b *Builder) Build(ctx context.Context, zone string) (*sacloud.MobileGateway, error) { 178 b.init() 179 180 if err := b.Validate(ctx, zone); err != nil { 181 return nil, err 182 } 183 184 builder := &setup.RetryableSetup{ 185 Create: func(ctx context.Context, zone string) (accessor.ID, error) { 186 return b.Client.MobileGateway.Create(ctx, zone, &sacloud.MobileGatewayCreateRequest{ 187 Name: b.Name, 188 Description: b.Description, 189 Tags: b.Tags, 190 IconID: b.IconID, 191 InternetConnectionEnabled: types.StringFlag(b.InternetConnectionEnabled), 192 InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled), 193 }) 194 }, 195 ProvisionBeforeUp: func(ctx context.Context, zone string, id types.ID, target interface{}) error { 196 if b.NoWait { 197 return nil 198 } 199 mgw := target.(*sacloud.MobileGateway) 200 201 // スイッチの接続 202 if b.PrivateInterface != nil { 203 if err := b.Client.MobileGateway.ConnectToSwitch(ctx, zone, id, b.PrivateInterface.SwitchID); err != nil { 204 return err 205 } 206 } 207 208 // [HACK] スイッチ接続直後だとエラーになることがあるため数秒待つ 209 time.Sleep(b.SetupOptions.NICUpdateWaitDuration) 210 211 // Interface設定 212 updated, err := b.Client.MobileGateway.UpdateSettings(ctx, zone, id, &sacloud.MobileGatewayUpdateSettingsRequest{ 213 InterfaceSettings: b.getInterfaceSettings(), 214 InternetConnectionEnabled: types.StringFlag(b.InternetConnectionEnabled), 215 InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled), 216 SettingsHash: mgw.SettingsHash, 217 }) 218 if err != nil { 219 return err 220 } 221 // [HACK] インターフェースの設定をConfigで反映させておかないとエラーになることへの対応 222 // see: https://github.com/sacloud/libsacloud/issues/589 223 if err := b.Client.MobileGateway.Config(ctx, zone, id); err != nil { 224 return err 225 } 226 mgw = updated 227 228 // traffic config 229 if b.TrafficConfig != nil { 230 if err := b.Client.MobileGateway.SetTrafficConfig(ctx, zone, id, b.TrafficConfig); err != nil { 231 return err 232 } 233 } 234 235 // dns 236 if b.DNS != nil { 237 if err := b.Client.MobileGateway.SetDNS(ctx, zone, id, b.DNS); err != nil { 238 return err 239 } 240 } 241 242 // static route 243 if len(b.StaticRoutes) > 0 { 244 _, err := b.Client.MobileGateway.UpdateSettings(ctx, zone, id, &sacloud.MobileGatewayUpdateSettingsRequest{ 245 InterfaceSettings: b.getInterfaceSettings(), 246 StaticRoutes: b.StaticRoutes, 247 InternetConnectionEnabled: types.StringFlag(b.InternetConnectionEnabled), 248 InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled), 249 SettingsHash: mgw.SettingsHash, 250 }) 251 if err != nil { 252 return err 253 } 254 } 255 256 // SIMs 257 if len(b.SIMs) > 0 { 258 for _, sim := range b.SIMs { 259 if err := b.Client.MobileGateway.AddSIM(ctx, zone, id, &sacloud.MobileGatewayAddSIMRequest{SIMID: sim.SIMID.String()}); err != nil { 260 return err 261 } 262 if err := b.Client.SIM.AssignIP(ctx, sim.SIMID, &sacloud.SIMAssignIPRequest{IP: sim.IPAddress}); err != nil { 263 return err 264 } 265 } 266 } 267 268 // SIM routes 269 if len(b.SIMRoutes) > 0 { 270 if err := b.Client.MobileGateway.SetSIMRoutes(ctx, zone, id, b.getSIMRouteSettings()); err != nil { 271 return err 272 } 273 } 274 275 if err := b.Client.MobileGateway.Config(ctx, zone, id); err != nil { 276 return err 277 } 278 279 if b.SetupOptions.BootAfterBuild { 280 return power.BootMobileGateway(ctx, b.Client.MobileGateway, zone, id) 281 } 282 return nil 283 }, 284 Delete: func(ctx context.Context, zone string, id types.ID) error { 285 return b.Client.MobileGateway.Delete(ctx, zone, id) 286 }, 287 Read: func(ctx context.Context, zone string, id types.ID) (interface{}, error) { 288 return b.Client.MobileGateway.Read(ctx, zone, id) 289 }, 290 IsWaitForCopy: !b.NoWait, 291 IsWaitForUp: !b.NoWait && b.SetupOptions.BootAfterBuild, 292 RetryCount: b.SetupOptions.RetryCount, 293 ProvisioningRetryCount: 1, 294 ProvisioningRetryInterval: b.SetupOptions.ProvisioningRetryInterval, 295 DeleteRetryCount: b.SetupOptions.DeleteRetryCount, 296 DeleteRetryInterval: b.SetupOptions.DeleteRetryInterval, 297 PollingInterval: b.SetupOptions.PollingInterval, 298 } 299 300 result, err := builder.Setup(ctx, zone) 301 var mgw *sacloud.MobileGateway 302 if result != nil { 303 mgw = result.(*sacloud.MobileGateway) 304 } 305 if err != nil { 306 return mgw, err 307 } 308 309 // refresh 310 refreshed, err := b.Client.MobileGateway.Read(ctx, zone, mgw.ID) 311 if err != nil { 312 return mgw, err 313 } 314 return refreshed, nil 315 } 316 317 // Update モバイルゲートウェイの更新 318 // 319 // 更新中、SIMルートが一時的にクリアされます。また、接続先スイッチが変更されていた場合は再起動されます。 320 func (b *Builder) Update(ctx context.Context, zone string, id types.ID) (*sacloud.MobileGateway, error) { 321 b.init() 322 323 if err := b.Validate(ctx, zone); err != nil { 324 return nil, err 325 } 326 327 // check MobileGateway is exists 328 mgw, err := b.Client.MobileGateway.Read(ctx, zone, id) 329 if err != nil { 330 return nil, err 331 } 332 mgw.SettingsHash = b.SettingsHash // 更新ルートが複数あるためここに設定しておく 333 334 isNeedShutdown, err := b.collectUpdateInfo(mgw) 335 if err != nil { 336 return nil, err 337 } 338 339 isNeedRestart := false 340 if mgw.InstanceStatus.IsUp() && isNeedShutdown { 341 if b.NoWait { 342 return nil, errors.New("NoWait option is not available due to the need to shut down") 343 } 344 isNeedRestart = true 345 if err := power.ShutdownMobileGateway(ctx, b.Client.MobileGateway, zone, id, false); err != nil { 346 return nil, err 347 } 348 } 349 350 // NICの切断/変更 351 if b.isPrivateInterfaceChanged(mgw) { 352 if len(mgw.Interfaces) > 1 && !mgw.Interfaces[1].SwitchID.IsEmpty() { 353 if b.PrivateInterface != nil && mgw.Interfaces[1].SwitchID != b.PrivateInterface.SwitchID { 354 // 切断 355 if err := b.Client.MobileGateway.DisconnectFromSwitch(ctx, zone, id); err != nil { 356 return nil, err 357 } 358 // [HACK] スイッチ接続直後だとエラーになることがあるため数秒待つ 359 time.Sleep(b.SetupOptions.NICUpdateWaitDuration) 360 361 updated, err := b.Client.MobileGateway.UpdateSettings(ctx, zone, id, &sacloud.MobileGatewayUpdateSettingsRequest{ 362 InternetConnectionEnabled: types.StringFlag(b.InternetConnectionEnabled), 363 InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled), 364 SettingsHash: mgw.SettingsHash, 365 }) 366 if err != nil { 367 return nil, err 368 } 369 // [HACK] インターフェースの設定をConfigで反映させておかないとエラーになることへの対応 370 // see: https://github.com/sacloud/libsacloud/issues/589 371 if err := b.Client.MobileGateway.Config(ctx, zone, id); err != nil { 372 return nil, err 373 } 374 mgw = updated 375 } 376 } 377 378 // 接続 379 if b.PrivateInterface != nil { 380 if len(mgw.Interfaces) == 1 { 381 // スイッチの接続 382 if err := b.Client.MobileGateway.ConnectToSwitch(ctx, zone, id, b.PrivateInterface.SwitchID); err != nil { 383 return nil, err 384 } 385 386 // [HACK] スイッチ接続直後だとエラーになることがあるため数秒待つ 387 time.Sleep(b.SetupOptions.NICUpdateWaitDuration) 388 } 389 390 // Interface設定 391 updated, err := b.Client.MobileGateway.UpdateSettings(ctx, zone, id, &sacloud.MobileGatewayUpdateSettingsRequest{ 392 InterfaceSettings: b.getInterfaceSettings(), 393 InternetConnectionEnabled: types.StringFlag(b.InternetConnectionEnabled), 394 InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled), 395 SettingsHash: mgw.SettingsHash, 396 }) 397 if err != nil { 398 return nil, err 399 } 400 // [HACK] インターフェースの設定をConfigで反映させておかないとエラーになることへの対応 401 // see: https://github.com/sacloud/libsacloud/issues/589 402 if err := b.Client.MobileGateway.Config(ctx, zone, id); err != nil { 403 return nil, err 404 } 405 mgw = updated 406 } 407 } 408 409 mgw, err = b.Client.MobileGateway.Update(ctx, zone, id, &sacloud.MobileGatewayUpdateRequest{ 410 Name: b.Name, 411 Description: b.Description, 412 Tags: b.Tags, 413 IconID: b.IconID, 414 InterfaceSettings: b.getInterfaceSettings(), 415 InternetConnectionEnabled: types.StringFlag(b.InternetConnectionEnabled), 416 InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled), 417 SettingsHash: mgw.SettingsHash, 418 }) 419 if err != nil { 420 return nil, err 421 } 422 423 // traffic config 424 trafficConfig, err := b.Client.MobileGateway.GetTrafficConfig(ctx, zone, id) 425 if err != nil { 426 if !sacloud.IsNotFoundError(err) { 427 return nil, err 428 } 429 } 430 if !reflect.DeepEqual(trafficConfig, b.TrafficConfig) { 431 if trafficConfig != nil && b.TrafficConfig == nil { 432 if err := b.Client.MobileGateway.DeleteTrafficConfig(ctx, zone, id); err != nil { 433 return nil, err 434 } 435 } else { 436 if err := b.Client.MobileGateway.SetTrafficConfig(ctx, zone, id, b.TrafficConfig); err != nil { 437 return nil, err 438 } 439 } 440 } 441 442 // dns 443 dns, err := b.Client.MobileGateway.GetDNS(ctx, zone, id) 444 if err != nil { 445 if !sacloud.IsNotFoundError(err) { 446 return nil, err 447 } 448 } 449 if !reflect.DeepEqual(dns, b.DNS) { 450 if dns == nil { 451 zone, err := b.Client.Zone.Read(ctx, mgw.ZoneID) 452 if err != nil { 453 return nil, err 454 } 455 b.DNS = &sacloud.MobileGatewayDNSSetting{ 456 DNS1: zone.Region.NameServers[0], 457 DNS2: zone.Region.NameServers[1], 458 } 459 } 460 if err := b.Client.MobileGateway.SetDNS(ctx, zone, id, b.DNS); err != nil { 461 return nil, err 462 } 463 } 464 465 // static route( 466 if len(b.StaticRoutes) > 0 { 467 _, err := b.Client.MobileGateway.UpdateSettings(ctx, zone, id, &sacloud.MobileGatewayUpdateSettingsRequest{ 468 InterfaceSettings: b.getInterfaceSettings(), 469 StaticRoutes: b.StaticRoutes, 470 InternetConnectionEnabled: types.StringFlag(b.InternetConnectionEnabled), 471 InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled), 472 SettingsHash: mgw.SettingsHash, 473 }) 474 if err != nil { 475 return nil, err 476 } 477 } 478 479 // SIMs and SIMRoutes 480 currentSIMs, err := b.currentConnectedSIMs(ctx, zone, id) 481 if err != nil { 482 return nil, err 483 } 484 currentSIMRoutes, err := b.currentSIMRoutes(ctx, zone, id) 485 if err != nil { 486 return nil, err 487 } 488 489 if !reflect.DeepEqual(currentSIMs, b.SIMs) || !reflect.DeepEqual(currentSIMRoutes, b.SIMRoutes) { 490 if len(currentSIMRoutes) > 0 { 491 // SIMルートクリア 492 if err := b.Client.MobileGateway.SetSIMRoutes(ctx, zone, id, []*sacloud.MobileGatewaySIMRouteParam{}); err != nil { 493 return nil, err 494 } 495 } 496 // SIM変更 497 added, updated, deleted := b.changedSIMs(currentSIMs, b.SIMs) 498 for _, sim := range deleted { 499 if err := b.Client.SIM.ClearIP(ctx, sim.SIMID); err != nil { 500 return nil, err 501 } 502 if err := b.Client.MobileGateway.DeleteSIM(ctx, zone, id, sim.SIMID); err != nil { 503 return nil, err 504 } 505 } 506 for _, sim := range updated { 507 if err := b.Client.SIM.ClearIP(ctx, sim.SIMID); err != nil { 508 return nil, err 509 } 510 if err := b.Client.SIM.AssignIP(ctx, sim.SIMID, &sacloud.SIMAssignIPRequest{IP: sim.IPAddress}); err != nil { 511 return nil, err 512 } 513 } 514 for _, sim := range added { 515 if err := b.Client.MobileGateway.AddSIM(ctx, zone, id, &sacloud.MobileGatewayAddSIMRequest{SIMID: sim.SIMID.String()}); err != nil { 516 return nil, err 517 } 518 if err := b.Client.SIM.AssignIP(ctx, sim.SIMID, &sacloud.SIMAssignIPRequest{IP: sim.IPAddress}); err != nil { 519 return nil, err 520 } 521 } 522 if len(b.SIMRoutes) > 0 { 523 if err := b.Client.MobileGateway.SetSIMRoutes(ctx, zone, id, b.getSIMRouteSettings()); err != nil { 524 return nil, err 525 } 526 } 527 } 528 529 if err := b.Client.MobileGateway.Config(ctx, zone, id); err != nil { 530 return nil, err 531 } 532 533 if isNeedRestart { 534 if err := power.BootMobileGateway(ctx, b.Client.MobileGateway, zone, id); err != nil { 535 return nil, err 536 } 537 } 538 539 // refresh 540 mgw, err = b.Client.MobileGateway.Read(ctx, zone, id) 541 if err != nil { 542 return nil, err 543 } 544 return mgw, err 545 } 546 547 func (b *Builder) getInterfaceSettings() []*sacloud.MobileGatewayInterfaceSetting { 548 if b.PrivateInterface == nil { 549 return nil 550 } 551 return []*sacloud.MobileGatewayInterfaceSetting{ 552 { 553 Index: 1, 554 NetworkMaskLen: b.PrivateInterface.NetworkMaskLen, 555 IPAddress: []string{b.PrivateInterface.IPAddress}, 556 }, 557 } 558 } 559 560 func (b *Builder) getSIMRouteSettings() []*sacloud.MobileGatewaySIMRouteParam { 561 var results []*sacloud.MobileGatewaySIMRouteParam 562 for _, route := range b.SIMRoutes { 563 results = append(results, &sacloud.MobileGatewaySIMRouteParam{ 564 ResourceID: route.SIMID.String(), 565 Prefix: route.Prefix, 566 }) 567 } 568 return results 569 } 570 571 func (b *Builder) collectUpdateInfo(mgw *sacloud.MobileGateway) (isNeedShutdown bool, err error) { 572 // スイッチの変更/削除は再起動が必要 573 isNeedShutdown = b.isPrivateInterfaceChanged(mgw) 574 return 575 } 576 577 func (b *Builder) isPrivateInterfaceChanged(mgw *sacloud.MobileGateway) bool { 578 current := b.currentPrivateInterfaceState(mgw) 579 return !reflect.DeepEqual(current, b.PrivateInterface) 580 } 581 582 func (b *Builder) currentPrivateInterfaceState(mgw *sacloud.MobileGateway) *PrivateInterfaceSetting { 583 if len(mgw.Interfaces) > 1 { 584 switchID := mgw.Interfaces[1].SwitchID 585 var setting *sacloud.MobileGatewayInterfaceSetting 586 for _, s := range mgw.InterfaceSettings { 587 if s.Index == 1 { 588 setting = s 589 } 590 } 591 if setting != nil { 592 var ip string 593 if len(setting.IPAddress) > 0 { 594 ip = setting.IPAddress[0] 595 } 596 return &PrivateInterfaceSetting{ 597 SwitchID: switchID, 598 IPAddress: ip, 599 NetworkMaskLen: setting.NetworkMaskLen, 600 } 601 } 602 } 603 return nil 604 } 605 606 func (b *Builder) currentConnectedSIMs(ctx context.Context, zone string, id types.ID) ([]*SIMSetting, error) { 607 var results []*SIMSetting 608 609 sims, err := b.Client.MobileGateway.ListSIM(ctx, zone, id) 610 if err != nil && !sacloud.IsNotFoundError(err) { 611 return results, err 612 } 613 for _, sim := range sims { 614 results = append(results, &SIMSetting{ 615 SIMID: types.StringID(sim.ResourceID), 616 IPAddress: sim.IP, 617 }) 618 } 619 return results, nil 620 } 621 622 func (b *Builder) currentSIMRoutes(ctx context.Context, zone string, id types.ID) ([]*sacloud.MobileGatewaySIMRoute, error) { 623 return b.Client.MobileGateway.GetSIMRoutes(ctx, zone, id) 624 } 625 626 func (b *Builder) changedSIMs(current []*SIMSetting, desired []*SIMSetting) (added, updated, deleted []*SIMSetting) { 627 for _, c := range current { 628 isExists := false 629 for _, d := range desired { 630 if c.SIMID == d.SIMID { 631 isExists = true 632 if c.IPAddress != d.IPAddress { 633 updated = append(updated, d) 634 } 635 } 636 } 637 if !isExists { 638 deleted = append(deleted, c) 639 } 640 } 641 for _, d := range desired { 642 isExists := false 643 for _, c := range current { 644 if c.SIMID == d.SIMID { 645 isExists = true 646 continue 647 } 648 } 649 if !isExists { 650 added = append(added, d) 651 } 652 } 653 return 654 }