diff --git a/ibm/provider/provider.go b/ibm/provider/provider.go index dc4a116869..5ff41fca5c 100644 --- a/ibm/provider/provider.go +++ b/ibm/provider/provider.go @@ -1196,6 +1196,7 @@ func Provider() *schema.Provider { "ibm_is_reservation": vpc.ResourceIBMISReservation(), "ibm_is_reservation_activate": vpc.ResourceIBMISReservationActivate(), "ibm_is_subnet_reserved_ip": vpc.ResourceIBMISReservedIP(), + "ibm_is_subnet_reserved_ip_patch": vpc.ResourceIBMISReservedIPPatch(), "ibm_is_subnet_network_acl_attachment": vpc.ResourceIBMISSubnetNetworkACLAttachment(), "ibm_is_subnet_public_gateway_attachment": vpc.ResourceIBMISSubnetPublicGatewayAttachment(), "ibm_is_subnet_routing_table_attachment": vpc.ResourceIBMISSubnetRoutingTableAttachment(), diff --git a/ibm/service/vpc/resource_ibm_is_subnet_reserved_ip_patch.go b/ibm/service/vpc/resource_ibm_is_subnet_reserved_ip_patch.go new file mode 100644 index 0000000000..1a6be955ed --- /dev/null +++ b/ibm/service/vpc/resource_ibm_is_subnet_reserved_ip_patch.go @@ -0,0 +1,279 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package vpc + +import ( + "fmt" + "reflect" + "time" + + "github.com/IBM-Cloud/terraform-provider-ibm/ibm/flex" + "github.com/IBM-Cloud/terraform-provider-ibm/ibm/validate" + "github.com/IBM/go-sdk-core/v5/core" + "github.com/IBM/vpc-go-sdk/vpcv1" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func ResourceIBMISReservedIPPatch() *schema.Resource { + return &schema.Resource{ + Create: resourceIBMISReservedIPPatchCreate, + Read: resourceIBMISReservedIPPatchRead, + Update: resourceIBMISReservedIPPatchUpdate, + Delete: resourceIBMISReservedIPPatchDelete, + Exists: resourceIBMISReservedIPPatchExists, + Importer: &schema.ResourceImporter{}, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + /* + Request Parameters + ================== + These are mandatory req parameters + */ + isSubNetID: { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The subnet identifier.", + }, + isReservedIPAutoDelete: { + Type: schema.TypeBool, + Default: nil, + AtLeastOneOf: []string{isReservedIPAutoDelete, isReservedIPName}, + Computed: true, + Optional: true, + Description: "If set to true, this reserved IP will be automatically deleted", + }, + isReservedIPName: { + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: []string{isReservedIPAutoDelete, isReservedIPName}, + Computed: true, + ValidateFunc: validate.InvokeValidator("ibm_is_subnet_reserved_ip", isReservedIPName), + Description: "The user-defined or system-provided name for this reserved IP.", + }, + isReservedIPTarget: { + Type: schema.TypeString, + Computed: true, + Optional: true, + Description: "The unique identifier for target.", + }, + isReservedIPTargetCrn: { + Type: schema.TypeString, + Computed: true, + Optional: true, + Description: "The crn for target.", + }, + isReservedIPLifecycleState: { + Type: schema.TypeString, + Computed: true, + Description: "The lifecycle state of the reserved IP", + }, + isReservedIPAddress: { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + Description: "The address for this reserved IP.", + }, + isReservedIP: { + Type: schema.TypeString, + Required: true, + Description: "The unique identifier of the reserved IP.", + }, + isReservedIPCreatedAt: { + Type: schema.TypeString, + Computed: true, + Description: "The date and time that the reserved IP was created.", + }, + isReservedIPhref: { + Type: schema.TypeString, + Computed: true, + Description: "The URL for this reserved IP.", + }, + isReservedIPOwner: { + Type: schema.TypeString, + Computed: true, + Description: "The owner of a reserved IP, defining whether it is managed by the user or the provider.", + }, + isReservedIPType: { + Type: schema.TypeString, + Computed: true, + Description: "The resource type.", + }, + }, + } +} + +// resourceIBMISReservedIPCreate Creates a reserved IP given a subnet ID +func resourceIBMISReservedIPPatchCreate(d *schema.ResourceData, meta interface{}) error { + sess, err := vpcClient(meta) + if err != nil { + return err + } + + subnetID := d.Get(isSubNetID).(string) + reservedIPID := d.Get(isReservedIP).(string) + name := d.Get(isReservedIPName).(string) + reservedIPPatchModel := &vpcv1.ReservedIPPatch{} + if name != "" { + reservedIPPatchModel.Name = &name + } + if autoDeleteBoolOk, ok := d.GetOkExists(isReservedIPAutoDelete); ok { + autoDeleteBool := autoDeleteBoolOk.(bool) + reservedIPPatchModel.AutoDelete = &autoDeleteBool + } + reservedIPPatch, err := reservedIPPatchModel.AsPatch() + if err != nil { + return fmt.Errorf("[ERROR] Error updating the reserved IP %s", err) + } + + options := sess.NewUpdateSubnetReservedIPOptions(subnetID, reservedIPID, reservedIPPatch) + + rip, response, err := sess.UpdateSubnetReservedIP(options) + if err != nil || response == nil || rip == nil { + return fmt.Errorf("[ERROR] Error updating the reserved ip patch: %s\n%s", err, response) + } + + // Set id for the reserved IP as combination of subnet ID and reserved IP ID + d.SetId(fmt.Sprintf("%s/%s", subnetID, *rip.ID)) + return resourceIBMISReservedIPPatchRead(d, meta) +} + +func resourceIBMISReservedIPPatchRead(d *schema.ResourceData, meta interface{}) error { + rip, err := get(d, meta) + if err != nil { + return err + } + + allIDs, err := flex.IdParts(d.Id()) + if err != nil { + return fmt.Errorf("[ERROR] The ID can not be split into subnet ID and reserved IP ID in patch. %s", err) + } + subnetID := allIDs[0] + + if rip != nil { + d.Set(isReservedIPAddress, *rip.Address) + d.Set(isReservedIP, *rip.ID) + d.Set(isSubNetID, subnetID) + if rip.LifecycleState != nil { + d.Set(isReservedIPLifecycleState, *rip.LifecycleState) + } + d.Set(isReservedIPAutoDelete, *rip.AutoDelete) + d.Set(isReservedIPCreatedAt, (*rip.CreatedAt).String()) + d.Set(isReservedIPhref, *rip.Href) + d.Set(isReservedIPName, *rip.Name) + d.Set(isReservedIPOwner, *rip.Owner) + d.Set(isReservedIPType, *rip.ResourceType) + if rip.Target != nil { + targetIntf := rip.Target + switch reflect.TypeOf(targetIntf).String() { + case "*vpcv1.ReservedIPTargetEndpointGatewayReference": + { + target := targetIntf.(*vpcv1.ReservedIPTargetEndpointGatewayReference) + d.Set(isReservedIPTarget, target.ID) + d.Set(isReservedIPTargetCrn, target.CRN) + } + case "*vpcv1.ReservedIPTargetGenericResourceReference": + { + target := targetIntf.(*vpcv1.ReservedIPTargetGenericResourceReference) + d.Set(isReservedIPTargetCrn, target.CRN) + } + case "*vpcv1.ReservedIPTargetNetworkInterfaceReferenceTargetContext": + { + target := targetIntf.(*vpcv1.ReservedIPTargetNetworkInterfaceReferenceTargetContext) + d.Set(isReservedIPTarget, target.ID) + } + case "*vpcv1.ReservedIPTargetLoadBalancerReference": + { + target := targetIntf.(*vpcv1.ReservedIPTargetLoadBalancerReference) + d.Set(isReservedIPTarget, target.ID) + d.Set(isReservedIPTargetCrn, target.CRN) + } + case "*vpcv1.ReservedIPTargetVPNGatewayReference": + { + target := targetIntf.(*vpcv1.ReservedIPTargetVPNGatewayReference) + d.Set(isReservedIPTarget, target.ID) + d.Set(isReservedIPTargetCrn, target.CRN) + } + case "*vpcv1.ReservedIPTarget": + { + target := targetIntf.(*vpcv1.ReservedIPTarget) + d.Set(isReservedIPTarget, target.ID) + d.Set(isReservedIPTargetCrn, target.CRN) + } + } + } + } + return nil +} + +func resourceIBMISReservedIPPatchUpdate(d *schema.ResourceData, meta interface{}) error { + + // For updating the name + nameChanged := d.HasChange(isReservedIPName) + autoDeleteChanged := d.HasChange(isReservedIPAutoDelete) + + if nameChanged || autoDeleteChanged { + sess, err := vpcClient(meta) + if err != nil { + return err + } + + allIDs, err := flex.IdParts(d.Id()) + if err != nil { + return err + } + subnetID := allIDs[0] + reservedIPID := allIDs[1] + + options := &vpcv1.UpdateSubnetReservedIPOptions{ + SubnetID: &subnetID, + ID: &reservedIPID, + } + + patch := new(vpcv1.ReservedIPPatch) + + if nameChanged { + name := d.Get(isReservedIPName).(string) + patch.Name = core.StringPtr(name) + } + + if autoDeleteChanged { + autoDelete := d.Get(isReservedIPAutoDelete).(bool) + patch.AutoDelete = core.BoolPtr(autoDelete) + } + + reservedIPPatch, err := patch.AsPatch() + if err != nil { + return fmt.Errorf("[ERROR] Error updating the reserved IP %s", err) + } + + options.ReservedIPPatch = reservedIPPatch + + _, response, err := sess.UpdateSubnetReservedIP(options) + if err != nil { + return fmt.Errorf("[ERROR] Error updating the reserved ip patch %s\n%s", err, response) + } + } + return resourceIBMISReservedIPPatchRead(d, meta) +} + +func resourceIBMISReservedIPPatchDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func resourceIBMISReservedIPPatchExists(d *schema.ResourceData, meta interface{}) (bool, error) { + rip, err := get(d, meta) + if err != nil { + return false, err + } + if err == nil && rip == nil { + return false, nil + } + return true, nil +} diff --git a/ibm/service/vpc/resource_ibm_is_subnet_reserved_ip_patch_test.go b/ibm/service/vpc/resource_ibm_is_subnet_reserved_ip_patch_test.go new file mode 100644 index 0000000000..3819662017 --- /dev/null +++ b/ibm/service/vpc/resource_ibm_is_subnet_reserved_ip_patch_test.go @@ -0,0 +1,138 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package vpc_test + +import ( + "fmt" + "testing" + + acc "github.com/IBM-Cloud/terraform-provider-ibm/ibm/acctest" + "github.com/IBM-Cloud/terraform-provider-ibm/ibm/conns" + "github.com/IBM-Cloud/terraform-provider-ibm/ibm/flex" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccIBMISSubnetReservedIPPatchResource_basic(t *testing.T) { + var reservedIPID string + vpcName := fmt.Sprintf("tfresip-vpc-%d", acctest.RandIntRange(10, 100)) + vniName := fmt.Sprintf("tfresip-vni-%d", acctest.RandIntRange(10, 100)) + subnetName := fmt.Sprintf("tfresip-subnet-%d", acctest.RandIntRange(10, 100)) + reservedIPName := fmt.Sprintf("tfresip-reservedip-%d", acctest.RandIntRange(10, 100)) + reservedIPName3 := fmt.Sprintf("tfresip-reservedip-%d", acctest.RandIntRange(10, 100)) + terraformTag1 := "ibm_is_subnet_reserved_ip_patch.resIP1" + terraformTag2 := "ibm_is_subnet_reserved_ip_patch.resIP2" + terraformTag3 := "ibm_is_subnet_reserved_ip_patch.resIP3" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + // Tests create + Config: testAccCheckISSubnetReservedIPPatchConfigBasic(vpcName, subnetName, vniName, reservedIPName, reservedIPName3), + Check: resource.ComposeTestCheckFunc( + testAccCheckISSubnetReservedIPPatchExists(terraformTag1, &reservedIPID), + testAccCheckISSubnetReservedIPPatchExists(terraformTag2, &reservedIPID), + testAccCheckISSubnetReservedIPPatchExists(terraformTag3, &reservedIPID), + resource.TestCheckResourceAttrSet(terraformTag1, "name"), + resource.TestCheckResourceAttrSet(terraformTag2, "name"), + resource.TestCheckResourceAttrSet(terraformTag3, "name"), + resource.TestCheckResourceAttr(terraformTag1, "name", reservedIPName), + resource.TestCheckResourceAttr(terraformTag1, "auto_delete", "true"), + resource.TestCheckResourceAttr(terraformTag2, "auto_delete", "false"), + resource.TestCheckResourceAttr(terraformTag3, "name", reservedIPName3), + ), + }, + }, + }) +} + +func testAccCheckISSubnetReservedIPPatchExists(resIPName string, reservedIPID *string) resource.TestCheckFunc { + return func(s *terraform.State) error { + + rs, ok := s.RootModule().Resources[resIPName] + if !ok { + return fmt.Errorf("Not Found (reserved IP patch): %s", resIPName) + } + if rs.Primary.ID == "" { + return fmt.Errorf("[ERROR] No reserved IP ID is set") + } + + sess, err := acc.TestAccProvider.Meta().(conns.ClientSession).VpcV1API() + if err != nil { + return err + } + parts, err := flex.IdParts(rs.Primary.ID) + if err != nil { + return err + } + opt := sess.NewGetSubnetReservedIPOptions(parts[0], parts[1]) + result, response, err := sess.GetSubnetReservedIP(opt) + if err != nil { + return fmt.Errorf("Reserved IP does not exist: %s", response) + } + *reservedIPID = *result.ID + return nil + } +} + +func testAccCheckISSubnetReservedIPPatchConfigBasic(vpcName, subnetName, vniname, resIPName1, resIPName2 string) string { + return fmt.Sprintf(` + resource "ibm_is_vpc" "vpc1" { + name = "%s" + } + + resource "ibm_is_subnet" "subnet1" { + name = "%s" + vpc = ibm_is_vpc.vpc1.id + zone = "us-south-1" + total_ipv4_address_count = 256 + } + + resource "ibm_is_virtual_endpoint_gateway" "endpoint_gateway" { + name = "my-endpoint-gateway-1" + target { + name = "ibm-ntp-server" + resource_type = "provider_infrastructure_service" + } + vpc = ibm_is_vpc.vpc1.id + } + resource "ibm_is_virtual_network_interface" "testacc_vni"{ + name = "%s" + subnet = ibm_is_subnet.subnet1.id + } + resource "ibm_is_subnet_reserved_ip" "resIP1" { + subnet = ibm_is_subnet.subnet1.id + target = ibm_is_virtual_endpoint_gateway.endpoint_gateway.id + } + resource "ibm_is_subnet_reserved_ip" "resIP2" { + subnet = ibm_is_subnet.subnet1.id + } + resource "ibm_is_virtual_network_interface_ip" "testacc_vni_reservedip" { + virtual_network_interface = ibm_is_virtual_network_interface.testacc_vni.id + reserved_ip = ibm_is_subnet_reserved_ip.resIP2.reserved_ip + } + resource "ibm_is_subnet_reserved_ip" "resIP3" { + subnet = ibm_is_subnet.subnet1.id + } + resource "ibm_is_subnet_reserved_ip_patch" "resIP1" { + subnet = ibm_is_subnet.subnet1.id + name = "%s" + auto_delete = true + reserved_ip = ibm_is_subnet_reserved_ip.resIP1.reserved_ip + } + resource "ibm_is_subnet_reserved_ip_patch" "resIP2" { + subnet = ibm_is_subnet.subnet1.id + auto_delete = false + reserved_ip = ibm_is_subnet_reserved_ip.resIP2.reserved_ip + } + resource "ibm_is_subnet_reserved_ip_patch" "resIP3" { + subnet = ibm_is_subnet.subnet1.id + name = "%s" + reserved_ip = ibm_is_subnet_reserved_ip.resIP3.reserved_ip + } + `, vpcName, subnetName, vniname, resIPName1, resIPName2) +} diff --git a/website/docs/r/is_subnet_reserved_ip_patch.html.markdown b/website/docs/r/is_subnet_reserved_ip_patch.html.markdown new file mode 100644 index 0000000000..488b06074e --- /dev/null +++ b/website/docs/r/is_subnet_reserved_ip_patch.html.markdown @@ -0,0 +1,91 @@ +--- +subcategory: "VPC infrastructure" +layout: "ibm" +page_title: "IBM : ibm_is_subnet_reserved_ip_patch" +description: |- + Manages IBM Subnet reserved IP patch. +--- + +# ibm_is_subnet_reserved_ip_patch +Update name and/or auto_delete of an existing reserved ip. For more information, about associated reserved IP subnet, see [reserved IP subnet](https://cloud.ibm.com/docs/vpc?topic=vpc-troubleshoot-reserved-ip). + +~> NOTE: Use this resource with caution, conflicts with `ibm_is_subnet_reserved_ip` resource if it has `name` attribute, using both will show changes on either of the resources alternatively on each apply. + +**Note:** +VPC infrastructure services are a regional specific based endpoint, by default targets to `us-south`. Please make sure to target right region in the provider block as shown in the `provider.tf` file, if VPC service is created in region other than `us-south`. + +**provider.tf** + +```terraform +provider "ibm" { + region = "eu-gb" +} +``` + +## Example usage +Sample to create a reserved IP: + +```terraform +// Create a VPC +resource "ibm_is_vpc" "example" { + name = "example-vpc" +} + +// Create a subnet +resource "ibm_is_subnet" "example" { + name = "example-subnet" + vpc = ibm_is_vpc.example.id + zone = "us-south-1" + total_ipv4_address_count = 256 +} + +resource "ibm_is_subnet_reserved_ip" "example" { + subnet = ibm_is_subnet.example.id +} + +resource "ibm_is_subnet_reserved_ip_patch" "example" { + subnet = ibm_is_subnet.example.id + reserved_ip = ibm_is_subnet_reserved_ip.example.reserved_ip + + name = "test-reserved-ip" + auto_delete = "true" +} +``` + +## Argument reference +Review the argument references that you can specify for your resource. + +- `auto_delete`- (Optional, Bool) If reserved IP is auto deleted. +- `name` - (Required, String) The name of the reserved IP. + + ~> **NOTE:** raise error if name is given with a prefix `ibm- `. +- `subnet` - (Required, Forces new resource, String) The subnet ID for the reserved IP. +- `reserved_ip` - (Required, Forces new resource, string) The ID for the reserved IP. + +## Attribute reference +In addition to all argument reference list, you can access the following attribute reference after your resource is created. + +- `created_at` - (Timestamp) The date and time that the reserved IP was created.", +- `href` - (String) The URL for this reserved IP. +- `id` - (String) The combination of the subnet ID and reserved IP ID, separated by **/**. +- `lifecycle_state` - (String) The lifecycle state of the reserved IP. [ deleting, failed, pending, stable, suspended, updating, waiting ] +- `owner` - (String) The owner of a reserved IP, defining whether it is managed by the user or the provider. +- `reserved_ip` - (String) The reserved IP. +- `resource_type` - (String) The resource type. +- `target` - (String) The ID for the target for the reserved IP. +- `target_crn` - (String) The crn of the target for the reserved IP. + +## Import +The `ibm_is_subnet_reserved_ip_patch` resource can be imported by using subnet ID and reserved IP ID separated by **/**. + +**Syntax** + +``` +$ terraform import ibm_is_subnet_reserved_ip_patch.example / +``` + +**Example** + +``` +$ terraform import ibm_is_subnet_reserved_ip_patch.example 0716-13315ad8-d355-4041-bb60-62342000423/0716-617de4d8-5e2f-4d4a-b0d6-1000023 +```