44package vpc
55
66import (
7+ "bytes"
78 "context"
9+ "encoding/json"
810 "fmt"
911 "log"
12+ "time"
1013
14+ "github.com/IBM-Cloud/terraform-provider-ibm/ibm/conns"
1115 "github.com/IBM-Cloud/terraform-provider-ibm/ibm/flex"
1216 "github.com/IBM-Cloud/terraform-provider-ibm/ibm/validate"
1317 "github.com/IBM/go-sdk-core/v5/core"
1418 "github.com/IBM/vpc-go-sdk/vpcv1"
1519 "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
20+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
1621 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1722)
1823
@@ -44,9 +49,10 @@ func ResourceIBMIsVirtualNetworkInterface() *schema.Resource {
4449 Description : "If `true`:- The VPC infrastructure performs any needed NAT operations.- `floating_ips` must not have more than one floating IP.If `false`:- Packets are passed unchanged to/from the network interface, allowing the workload to perform any needed NAT operations.- `allow_ip_spoofing` must be `false`.- If the virtual network interface is attached: - The target `resource_type` must be `bare_metal_server_network_attachment`. - The target `interface_type` must not be `hipersocket`." ,
4550 },
4651 "ips" : & schema.Schema {
47- Type : schema .TypeList ,
52+ Type : schema .TypeSet ,
4853 Optional : true ,
4954 Computed : true ,
55+ Set : hashIpsList ,
5056 Description : "The reserved IPs bound to this virtual network interface.May be empty when `lifecycle_state` is `pending`." ,
5157 Elem : & schema.Resource {
5258 Schema : map [string ]* schema.Schema {
@@ -84,6 +90,7 @@ func ResourceIBMIsVirtualNetworkInterface() *schema.Resource {
8490 "reserved_ip" : & schema.Schema {
8591 Type : schema .TypeString ,
8692 Optional : true ,
93+ Computed : true ,
8794 Description : "The unique identifier for this reserved IP." ,
8895 },
8996 "name" : & schema.Schema {
@@ -170,7 +177,6 @@ func ResourceIBMIsVirtualNetworkInterface() *schema.Resource {
170177 Type : schema .TypeSet ,
171178 Optional : true ,
172179 Computed : true ,
173- ForceNew : true ,
174180 Elem : & schema.Schema {Type : schema .TypeString },
175181 Set : schema .HashString ,
176182 Description : "The security groups for this virtual network interface." ,
@@ -347,7 +353,7 @@ func resourceIBMIsVirtualNetworkInterfaceCreate(context context.Context, d *sche
347353 }
348354 if _ , ok := d .GetOk ("ips" ); ok {
349355 var ips []vpcv1.VirtualNetworkInterfaceIPPrototypeIntf
350- for _ , v := range d .Get ("ips" ).([] interface {} ) {
356+ for _ , v := range d .Get ("ips" ).(* schema. Set ). List ( ) {
351357 value := v .(map [string ]interface {})
352358 ipsItem , err := resourceIBMIsVirtualNetworkInterfaceMapToVirtualNetworkInterfaceIPsReservedIPPrototype (value )
353359 if err != nil {
@@ -427,35 +433,37 @@ func resourceIBMIsVirtualNetworkInterfaceRead(context context.Context, d *schema
427433
428434 if ! core .IsNil (virtualNetworkInterface .AllowIPSpoofing ) {
429435 if err = d .Set ("allow_ip_spoofing" , virtualNetworkInterface .AllowIPSpoofing ); err != nil {
430- return diag .FromErr (fmt .Errorf ("Error setting allow_ip_spoofing: %s" , err ))
436+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting allow_ip_spoofing: %s" , err ))
431437 }
432438 }
433439 if ! core .IsNil (virtualNetworkInterface .AutoDelete ) {
434440 if err = d .Set ("auto_delete" , virtualNetworkInterface .AutoDelete ); err != nil {
435- return diag .FromErr (fmt .Errorf ("Error setting auto_delete: %s" , err ))
441+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting auto_delete: %s" , err ))
436442 }
437443 }
438444 if ! core .IsNil (virtualNetworkInterface .EnableInfrastructureNat ) {
439445 if err = d .Set ("enable_infrastructure_nat" , virtualNetworkInterface .EnableInfrastructureNat ); err != nil {
440- return diag .FromErr (fmt .Errorf ("Error setting enable_infrastructure_nat: %s" , err ))
446+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting enable_infrastructure_nat: %s" , err ))
441447 }
442448 }
443449 if ! core .IsNil (virtualNetworkInterface .Ips ) {
444450 ips := []map [string ]interface {}{}
445451 for _ , ipsItem := range virtualNetworkInterface .Ips {
446- ipsItemMap , err := resourceIBMIsVirtualNetworkInterfaceReservedIPReferenceToMap (& ipsItem )
447- if err != nil {
448- return diag .FromErr (err )
452+ if * virtualNetworkInterface .PrimaryIP .ID != * ipsItem .ID {
453+ ipsItemMap , err := resourceIBMIsVirtualNetworkInterfaceReservedIPReferenceToMap (& ipsItem )
454+ if err != nil {
455+ return diag .FromErr (err )
456+ }
457+ ips = append (ips , ipsItemMap )
449458 }
450- ips = append (ips , ipsItemMap )
451459 }
452460 if err = d .Set ("ips" , ips ); err != nil {
453- return diag .FromErr (fmt .Errorf ("Error setting ips: %s" , err ))
461+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting ips: %s" , err ))
454462 }
455463 }
456464 if ! core .IsNil (virtualNetworkInterface .Name ) {
457465 if err = d .Set ("name" , virtualNetworkInterface .Name ); err != nil {
458- return diag .FromErr (fmt .Errorf ("Error setting name: %s" , err ))
466+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting name: %s" , err ))
459467 }
460468 }
461469 if ! core .IsNil (virtualNetworkInterface .PrimaryIP ) {
@@ -464,7 +472,7 @@ func resourceIBMIsVirtualNetworkInterfaceRead(context context.Context, d *schema
464472 return diag .FromErr (err )
465473 }
466474 if err = d .Set ("primary_ip" , []map [string ]interface {}{primaryIPMap }); err != nil {
467- return diag .FromErr (fmt .Errorf ("Error setting primary_ip: %s" , err ))
475+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting primary_ip: %s" , err ))
468476 }
469477 }
470478 if ! core .IsNil (virtualNetworkInterface .ResourceGroup ) {
@@ -485,32 +493,32 @@ func resourceIBMIsVirtualNetworkInterfaceRead(context context.Context, d *schema
485493 d .Set ("subnet" , virtualNetworkInterface .Subnet .ID )
486494 }
487495 if err = d .Set ("created_at" , flex .DateTimeToString (virtualNetworkInterface .CreatedAt )); err != nil {
488- return diag .FromErr (fmt .Errorf ("Error setting created_at: %s" , err ))
496+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting created_at: %s" , err ))
489497 }
490498 if err = d .Set ("crn" , virtualNetworkInterface .CRN ); err != nil {
491- return diag .FromErr (fmt .Errorf ("Error setting crn: %s" , err ))
499+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting crn: %s" , err ))
492500 }
493501 if err = d .Set ("href" , virtualNetworkInterface .Href ); err != nil {
494- return diag .FromErr (fmt .Errorf ("Error setting href: %s" , err ))
502+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting href: %s" , err ))
495503 }
496504 if err = d .Set ("lifecycle_state" , virtualNetworkInterface .LifecycleState ); err != nil {
497- return diag .FromErr (fmt .Errorf ("Error setting lifecycle_state: %s" , err ))
505+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting lifecycle_state: %s" , err ))
498506 }
499507 if ! core .IsNil (virtualNetworkInterface .MacAddress ) {
500508 if err = d .Set ("mac_address" , virtualNetworkInterface .MacAddress ); err != nil {
501- return diag .FromErr (fmt .Errorf ("Error setting mac_address: %s" , err ))
509+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting mac_address: %s" , err ))
502510 }
503511 }
504512 if err = d .Set ("resource_type" , virtualNetworkInterface .ResourceType ); err != nil {
505- return diag .FromErr (fmt .Errorf ("Error setting resource_type: %s" , err ))
513+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting resource_type: %s" , err ))
506514 }
507515 if ! core .IsNil (virtualNetworkInterface .Target ) {
508516 targetMap , err := resourceIBMIsVirtualNetworkInterfaceVirtualNetworkInterfaceTargetToMap (virtualNetworkInterface .Target )
509517 if err != nil {
510518 return diag .FromErr (err )
511519 }
512520 if err = d .Set ("target" , []map [string ]interface {}{targetMap }); err != nil {
513- return diag .FromErr (fmt .Errorf ("Error setting target: %s" , err ))
521+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting target: %s" , err ))
514522 }
515523 } else {
516524 d .Set ("target" , nil )
@@ -520,7 +528,7 @@ func resourceIBMIsVirtualNetworkInterfaceRead(context context.Context, d *schema
520528 return diag .FromErr (err )
521529 }
522530 if err = d .Set ("vpc" , []map [string ]interface {}{vpcMap }); err != nil {
523- return diag .FromErr (fmt .Errorf ("Error setting vpc: %s" , err ))
531+ return diag .FromErr (fmt .Errorf ("[ERROR] Error setting vpc: %s" , err ))
524532 }
525533
526534 if virtualNetworkInterface .Zone != nil {
@@ -535,10 +543,10 @@ func resourceIBMIsVirtualNetworkInterfaceUpdate(context context.Context, d *sche
535543 if err != nil {
536544 return diag .FromErr (err )
537545 }
538-
546+ id := d . Id ()
539547 updateVirtualNetworkInterfaceOptions := & vpcv1.UpdateVirtualNetworkInterfaceOptions {}
540548
541- updateVirtualNetworkInterfaceOptions .SetID (d . Id () )
549+ updateVirtualNetworkInterfaceOptions .SetID (id )
542550
543551 hasChange := false
544552
@@ -563,6 +571,130 @@ func resourceIBMIsVirtualNetworkInterfaceUpdate(context context.Context, d *sche
563571 patchVals .Name = & newName
564572 hasChange = true
565573 }
574+ if d .HasChange ("ips" ) {
575+ oldips , newIPs := d .GetChange ("ips" )
576+ log .Printf ("[INFO] vnip2 ipsoldmap old map %s" , output (oldips ))
577+ log .Printf ("[INFO] vnip2 ipsnewmap new map %s" , output (newIPs ))
578+
579+ ov := oldips .(* schema.Set )
580+ nv := newIPs .(* schema.Set )
581+ remove := (ov .Difference (nv ).List ())
582+ add := (nv .Difference (ov ).List ())
583+ log .Printf ("[INFO] vnip2 add map %s" , output (add ))
584+ log .Printf ("[INFO] vnip2 remove map %s" , output (remove ))
585+
586+ if add != nil && len (add ) > 0 {
587+ for _ , ipItem := range add {
588+ value := ipItem .(map [string ]interface {})
589+ if value ["reserved_ip" ] != nil && value ["reserved_ip" ].(string ) != "" {
590+ reservedipid := value ["reserved_ip" ].(string )
591+ addVirtualNetworkInterfaceIPOptions := & vpcv1.AddVirtualNetworkInterfaceIPOptions {}
592+ addVirtualNetworkInterfaceIPOptions .SetVirtualNetworkInterfaceID (id )
593+ addVirtualNetworkInterfaceIPOptions .SetID (reservedipid )
594+ _ , response , err := sess .AddVirtualNetworkInterfaceIPWithContext (context , addVirtualNetworkInterfaceIPOptions )
595+ if err != nil {
596+ log .Printf ("[DEBUG] AddVirtualNetworkInterfaceIPWithContext failed in VirtualNetworkInterface patch %s\n %s" , err , response )
597+ return diag .FromErr (fmt .Errorf ("AddVirtualNetworkInterfaceIPWithContext failed in VirtualNetworkInterface patch %s\n %s" , err , response ))
598+ }
599+ }
600+ }
601+ }
602+ if remove != nil && len (remove ) > 0 {
603+ subnetId := d .Get ("subnet" ).(string )
604+ for _ , ipItem := range remove {
605+ value := ipItem .(map [string ]interface {})
606+ if value ["reserved_ip" ] != nil && value ["reserved_ip" ].(string ) != "" {
607+ reservedipid := value ["reserved_ip" ].(string )
608+ removeVirtualNetworkInterfaceIPOptions := & vpcv1.RemoveVirtualNetworkInterfaceIPOptions {}
609+ removeVirtualNetworkInterfaceIPOptions .SetVirtualNetworkInterfaceID (id )
610+ removeVirtualNetworkInterfaceIPOptions .SetID (reservedipid )
611+ response , err := sess .RemoveVirtualNetworkInterfaceIPWithContext (context , removeVirtualNetworkInterfaceIPOptions )
612+ if err != nil {
613+ log .Printf ("[DEBUG] RemoveVirtualNetworkInterfaceIPWithContext failed in VirtualNetworkInterface patch %s\n %s" , err , response )
614+ return diag .FromErr (fmt .Errorf ("RemoveVirtualNetworkInterfaceIPWithContext failed in VirtualNetworkInterface patch %s\n %s" , err , response ))
615+ }
616+ }
617+ if value ["address" ] != nil && value ["address" ].(string ) != "" {
618+ reservedipid := value ["reserved_ip" ].(string )
619+ removeSubnetReservedIPOptions := & vpcv1.DeleteSubnetReservedIPOptions {}
620+ removeSubnetReservedIPOptions .SetSubnetID (subnetId )
621+ removeSubnetReservedIPOptions .SetID (reservedipid )
622+ response , err := sess .DeleteSubnetReservedIPWithContext (context , removeSubnetReservedIPOptions )
623+ if err != nil {
624+ log .Printf ("[DEBUG] DeleteSubnetReservedIPWithContext failed in VirtualNetworkInterface patch %s\n %s" , err , response )
625+ return diag .FromErr (fmt .Errorf ("DeleteSubnetReservedIPWithContext failed in VirtualNetworkInterface patch %s\n %s" , err , response ))
626+ }
627+ }
628+ }
629+ }
630+ }
631+ if ! d .IsNewResource () && d .HasChange ("primary_ip" ) {
632+ subnetId := d .Get ("subnet" ).(string )
633+ ripId := d .Get ("primary_ip.0.reserved_ip" ).(string )
634+ updateripoptions := & vpcv1.UpdateSubnetReservedIPOptions {
635+ SubnetID : & subnetId ,
636+ ID : & ripId ,
637+ }
638+ reservedIpPath := & vpcv1.ReservedIPPatch {}
639+ if d .HasChange ("primary_ip.0.name" ) {
640+ name := d .Get ("primary_ip.0.name" ).(string )
641+ reservedIpPath .Name = & name
642+ }
643+ if d .HasChange ("primary_ip.0.auto_delete" ) {
644+ auto := d .Get ("primary_ip.0.auto_delete" ).(bool )
645+ reservedIpPath .AutoDelete = & auto
646+ }
647+ reservedIpPathAsPatch , err := reservedIpPath .AsPatch ()
648+ if err != nil {
649+ return diag .FromErr (fmt .Errorf ("[ERROR] Error calling reserved ip as patch on vni patch \n %s" , err ))
650+ }
651+ updateripoptions .ReservedIPPatch = reservedIpPathAsPatch
652+ _ , response , err := sess .UpdateSubnetReservedIP (updateripoptions )
653+ if err != nil {
654+ return diag .FromErr (fmt .Errorf ("[ERROR] Error updating vni reserved ip(%s): %s\n %s" , ripId , err , response ))
655+ }
656+ }
657+ if d .HasChange ("security_groups" ) && ! d .IsNewResource () {
658+ ovs , nvs := d .GetChange ("security_groups" )
659+ vniId := d .Id ()
660+ ov := ovs .(* schema.Set )
661+ nv := nvs .(* schema.Set )
662+ remove := flex .ExpandStringList (ov .Difference (nv ).List ())
663+ add := flex .ExpandStringList (nv .Difference (ov ).List ())
664+ if len (add ) > 0 {
665+ for i := range add {
666+ createsgnicoptions := & vpcv1.CreateSecurityGroupTargetBindingOptions {
667+ SecurityGroupID : & add [i ],
668+ ID : & vniId ,
669+ }
670+ _ , response , err := sess .CreateSecurityGroupTargetBinding (createsgnicoptions )
671+ if err != nil {
672+ return diag .FromErr (fmt .Errorf ("[ERROR] Error while creating security group %q for virtual network interface %s\n %s: %q" , add [i ], d .Id (), err , response ))
673+ }
674+ _ , err = isWaitForVirtualNetworkInterfaceAvailable (sess , vniId , d .Timeout (schema .TimeoutUpdate ))
675+ if err != nil {
676+ return diag .FromErr (err )
677+ }
678+ }
679+
680+ }
681+ if len (remove ) > 0 {
682+ for i := range remove {
683+ deletesgnicoptions := & vpcv1.DeleteSecurityGroupTargetBindingOptions {
684+ SecurityGroupID : & remove [i ],
685+ ID : & vniId ,
686+ }
687+ response , err := sess .DeleteSecurityGroupTargetBinding (deletesgnicoptions )
688+ if err != nil {
689+ return diag .FromErr (fmt .Errorf ("[ERROR] Error while removing security group %q for virtual network interface %s\n %s: %q" , remove [i ], d .Id (), err , response ))
690+ }
691+ _ , err = isWaitForVirtualNetworkInterfaceAvailable (sess , vniId , d .Timeout (schema .TimeoutUpdate ))
692+ if err != nil {
693+ return diag .FromErr (err )
694+ }
695+ }
696+ }
697+ }
566698
567699 if hasChange {
568700 updateVirtualNetworkInterfaceOptions .VirtualNetworkInterfacePatch , _ = patchVals .AsPatch ()
@@ -747,6 +879,37 @@ func resourceIBMIsVirtualNetworkInterfaceVirtualNetworkInterfaceTargetShareMount
747879 return modelMap , nil
748880}
749881
882+ func isWaitForVirtualNetworkInterfaceAvailable (client * vpcv1.VpcV1 , id string , timeout time.Duration ) (interface {}, error ) {
883+ log .Printf ("Waiting for VirtualNetworkInterface (%s) to be available." , id )
884+
885+ stateConf := & resource.StateChangeConf {
886+ Pending : []string {"" , "pending" },
887+ Target : []string {"done" , "failed" , "stable" },
888+ Refresh : isVirtualNetworkInterfaceRefreshFunc (client , id ),
889+ Timeout : timeout ,
890+ Delay : 10 * time .Second ,
891+ MinTimeout : 10 * time .Second ,
892+ }
893+
894+ return stateConf .WaitForState ()
895+ }
896+
897+ func isVirtualNetworkInterfaceRefreshFunc (client * vpcv1.VpcV1 , id string ) resource.StateRefreshFunc {
898+ return func () (interface {}, string , error ) {
899+ vnigetoptions := & vpcv1.GetVirtualNetworkInterfaceOptions {
900+ ID : & id ,
901+ }
902+ vni , response , err := client .GetVirtualNetworkInterface (vnigetoptions )
903+ if err != nil {
904+ return nil , "failed" , fmt .Errorf ("[ERROR] Error getting vni: %s\n %s" , err , response )
905+ }
906+ if * vni .LifecycleState == "failed" || * vni .LifecycleState == "suspended" {
907+ return vni , * vni .LifecycleState , fmt .Errorf ("[ERROR] Error VirtualNetworkInterface in : %s state" , * vni .LifecycleState )
908+ }
909+ return vni , * vni .LifecycleState , nil
910+ }
911+ }
912+
750913func resourceIBMIsVirtualNetworkInterfaceVirtualNetworkInterfaceTargetInstanceNetworkAttachmentReferenceVirtualNetworkInterfaceContextToMap (model * vpcv1.VirtualNetworkInterfaceTargetInstanceNetworkAttachmentReferenceVirtualNetworkInterfaceContext ) (map [string ]interface {}, error ) {
751914 modelMap := make (map [string ]interface {})
752915 // if model.Deleted != nil {
@@ -813,3 +976,18 @@ func resourceIBMIsVirtualNetworkInterfaceVPCReferenceDeletedToMap(model *vpcv1.V
813976 modelMap ["more_info" ] = model .MoreInfo
814977 return modelMap , nil
815978}
979+
980+ func hashIpsList (v interface {}) int {
981+ var buf bytes.Buffer
982+ a := v .(map [string ]interface {})
983+ buf .WriteString (fmt .Sprintf ("%s-" , a ["address" ].(string )))
984+ return conns .String (buf .String ())
985+ }
986+ func output (vnimap interface {}) string {
987+ output , err := json .MarshalIndent (vnimap , "" , " " )
988+ if err == nil {
989+ return fmt .Sprintf ("block %+v\n " , string (output ))
990+ } else {
991+ return fmt .Sprintf ("block : %#v" , vnimap )
992+ }
993+ }
0 commit comments