From 3c7568a8c18143090c1e0f85492fca052e5a5a32 Mon Sep 17 00:00:00 2001 From: Neil Garratt Date: Mon, 25 Apr 2022 18:19:08 -0400 Subject: [PATCH 01/13] Add infoblox_zone_delegated resource --- docs/index.md | 84 +------ docs/resources/infoblox_zone_delegated.md | 39 +++ .../v0.14/Resources/ZoneDelegated/infoblox.tf | 17 ++ .../v0.14/Resources/ZoneDelegated/versions.tf | 8 + infoblox/provider.go | 1 + infoblox/resource_infoblox_zone_delegated.go | 229 ++++++++++++++++++ .../resource_infoblox_zone_delegated_test.go | 92 +++++++ 7 files changed, 387 insertions(+), 83 deletions(-) create mode 100644 docs/resources/infoblox_zone_delegated.md create mode 100644 examples/v0.14/Resources/ZoneDelegated/infoblox.tf create mode 100644 examples/v0.14/Resources/ZoneDelegated/versions.tf create mode 100644 infoblox/resource_infoblox_zone_delegated.go create mode 100644 infoblox/resource_infoblox_zone_delegated_test.go diff --git a/docs/index.md b/docs/index.md index 9eae71ca6..ea64b9456 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,88 +1,5 @@ # Infoblox IPAM Driver for Terraform -## Prerequisites - -Whether you intend to use the published plug-in or the customized version that you have built yourself, you must complete the following prerequisites: - -- Install and set up a physical or virtual Infoblox NIOS appliance and has necessary licenses installed. Configure the access permissions for Terraform to interact with NIOS Grid objects. -- To use the Infoblox IPAM Plug-In for Terraform, you must either define the following extensible attributes or install the Cloud Network Automation license in the NIOS Grid, which adds the extensible attributes by default: -```json -{ - "Tenant ID": "String Type", - "CMP Type": "String Type", - "Cloud API Owned": "List Type (Values True, False)" -} -``` -You may add other extensible attributes that you want to use. -- Create an extensible attribute by name Terraform Internal ID of type string in Infoblox NIOS as given in below curl command. -```bash -curl -k -u : -H "Content-Type: application/json" -X POST https:///wapi/v2.12/extensibleattributedef -d '{"name": "Terraform Internal ID", "flags": "CR", "type": "STRING", "comment": "Internal ID for Terraform Resource"}' -``` - -> **Note:** -> ->Either the Terraform Internal ID extensible attribute definition must be present in NIOS or IPAM Plug-In for Terraform -must be configured with superuser access for it to automatically create the extensible attribute. If not, the connection - to Terraform will fail. -> ->If you choose to create the Terraform Internal ID extensible attribute manually or by using the cURL command, -the creation of the extensible attribute is not managed by IPAM Plug-In for Terraform. -> ->You must not modify the Terraform Internal ID for a resource under any circumstances. If it is modified, the resource - will no longer be managed by Terraform. - - -## Configuring Infoblox Terraform IPAM Plug-In - -Terraform relies on an Infoblox provider to interact with NIOS Grid objects. You can either use the published Infoblox provider (Infoblox IPAM Plug-In for Terraform) available on the Terraform Registry page or develop a plug-in with features that are not available in the published plug-in. - -As a prerequisite, configure provider authentication to set up the required access permissions for Terraform to interact with NIOS Grid objects. Additionally, declare the version of IPAM Plug-In for Terraform in the .tf file to allow Terraform to automatically install the published plug-in available in the Terraform Registry. - -To configure IPAM Plug-In for Terraform for use, complete the following steps: - -In the .tf file, specify the plug-in version in the required_providers block as follows in .tf file: -```hcl -terraform { - required_providers { - infoblox = { - source = "infobloxopen/infoblox" - version = ">= 2.7.0" - } - } -} -``` - -Configure the credentials required to access the NIOS Grid as environment variables or provider block in .tf file: - - -```bash - # Using environment variable - $ export INFOBLOX_SERVER= - $ export INFOBLOX_USERNAME= - $ export INFOBLOX_PASSWORD= -``` - -```hcl -// Using Provider block -provider "infoblox" { - server = var.server - username = var.username - password = var.password -} -``` - -Add other environment variables that you intend to use. -You can set the following environment variables instead of defining them as attributes inside the provider block in the .tf file. Each of these environment variables has a corresponding attribute in the provider block. -``` -PORT -SSLMODE -CONNECT_TIMEOUT -POOL_CONNECTIONS -WAPI_VERSION -``` - -Run the terraform init command in the directory where the .tf file is located to initialize the plug-in. - ## Resources There are resources for the following objects, supported by the plugin: @@ -101,6 +18,7 @@ There are resources for the following objects, supported by the plugin: * Zone Auth (`infoblox_zone_auth`) * Zone Forward (`infoblox_zone_forward`) * Host record (`infoblox_ip_allocation` / `infoblox_ip_association`) +* Zone delegated Network and network container resources have two versions: IPv4 and IPv6. In addition, there are two operations which are implemented as resources: diff --git a/docs/resources/infoblox_zone_delegated.md b/docs/resources/infoblox_zone_delegated.md new file mode 100644 index 000000000..718c15e6d --- /dev/null +++ b/docs/resources/infoblox_zone_delegated.md @@ -0,0 +1,39 @@ +# Resource Zone Delegated + +A Zone Delegated resource creates NS records for a subdomain, pointing to one or more external authoritative name servers. The `infoblox_zone_delegated` resource allow managing such delegations. The parent zone must already exist + +The following list describes the parameters you can define in the `infoblox_zone_delegated` resource block: + +## Argument Reference +* `fqdn`: (Required) The subdomain name to be delegated +* `delegate_to`: (Required) Nested block(s)s for the delegated name servers + * `address`: (Required) The IP address of the name server + * `name`: (Required) The FQDN of the name server +* `ext_attrs`: (Optional) A set of NIOS extensible attributes that are attached to the record, using jsonencode. Currently only "Tenant ID" is supported + +## Example Usage + +```hcl +resource "infoblox_zone_delegated" "subdomain" { + + fqdn = "subdomain.test.com" + + delegate_to { + address = "205.251.197.208" + name = "ns-1488.awsdns-58.org" + } + + delegate_to { + address = "205.251.199.242" + name = "ns-2034.awsdns-62.co.uk" + } + +} +``` + +## Import +Zone Delegated resources can be imported by using either the object reference or the subdomain fqdn, for example: +```shell script +# terraform import infoblox_zone_delegated.subdomain zone_delegated/ZG5zLnpvbmUkLl9kZWZhdWx0LmNvbS5jb2xsZWdlY2hvaWNldHJhbnNpdGlvbi5nc2xi:subdomain.test.com/default +# terraform import infoblox_zone_delegated.subdomain subdomain.test.com +``` diff --git a/examples/v0.14/Resources/ZoneDelegated/infoblox.tf b/examples/v0.14/Resources/ZoneDelegated/infoblox.tf new file mode 100644 index 000000000..2137549e2 --- /dev/null +++ b/examples/v0.14/Resources/ZoneDelegated/infoblox.tf @@ -0,0 +1,17 @@ +# Zone Delegated + +resource "infoblox_zone_delegated" "subdomain" { + + fqdn = "subdomain.example.com" + + delegate_to { + address = "205.251.197.208" + name = "ns-1488.awsdns-58.org" + } + + delegate_to { + address = "205.251.199.242" + name = "ns-2034.awsdns-62.co.uk" + } + +} diff --git a/examples/v0.14/Resources/ZoneDelegated/versions.tf b/examples/v0.14/Resources/ZoneDelegated/versions.tf new file mode 100644 index 000000000..0ac2a2b98 --- /dev/null +++ b/examples/v0.14/Resources/ZoneDelegated/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + infoblox = { + source = "infobloxopen/infoblox" + version = ">= 2.1" + } + } +} diff --git a/infoblox/provider.go b/infoblox/provider.go index f917abc3e..31e1e713c 100644 --- a/infoblox/provider.go +++ b/infoblox/provider.go @@ -204,6 +204,7 @@ func Provider() *schema.Provider { "infoblox_aaaa_record": resourceAAAARecord(), "infoblox_cname_record": resourceCNAMERecord(), "infoblox_ptr_record": resourcePTRRecord(), + "infoblox_zone_delegated": resourceZoneDelegated(), "infoblox_txt_record": resourceTXTRecord(), "infoblox_mx_record": resourceMXRecord(), "infoblox_srv_record": resourceSRVRecord(), diff --git a/infoblox/resource_infoblox_zone_delegated.go b/infoblox/resource_infoblox_zone_delegated.go new file mode 100644 index 000000000..31d8c17d1 --- /dev/null +++ b/infoblox/resource_infoblox_zone_delegated.go @@ -0,0 +1,229 @@ +package infoblox + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + ibclient "github.com/infobloxopen/infoblox-go-client/v2" +) + +func resourceZoneDelegated() *schema.Resource { + return &schema.Resource{ + Create: resourceZoneDelegatedCreate, + Read: resourceZoneDelegatedRead, + Update: resourceZoneDelegatedUpdate, + Delete: resourceZoneDelegatedDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "fqdn": { + Type: schema.TypeString, + Required: true, + Description: "The FQDN of the delegated zone.", + }, + "delegate_to": resourceNameServer(), + "ext_attrs": { + Type: schema.TypeString, + Default: "", + Optional: true, + Description: "Extensible attributes, as a map in JSON format", + }, + }, + } +} + +func resourceNameServer() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address": { + Type: schema.TypeString, + Required: true, + Description: "IP of Name Server", + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "FQDN of Name Server", + }, + }, + }, + } +} + +func resourceZoneDelegatedCreate(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] %s: Beginning to create Zone Delegated", resourceZoneDelegatedIDString(d)) + + extAttrJSON := d.Get("ext_attrs").(string) + extAttrs := make(map[string]interface{}) + if extAttrJSON != "" { + if err := json.Unmarshal([]byte(extAttrJSON), &extAttrs); err != nil { + return fmt.Errorf("cannot process 'ext_attrs' field: %s", err.Error()) + } + } + + delegatedFQDN := d.Get("fqdn").(string) + + var nameServers []ibclient.NameServer + delegations := d.Get("delegate_to").(*schema.Set).List() + for _, delegation := range delegations { + var ns ibclient.NameServer + var delegationMap = delegation.(map[string]interface{}) + ns.Address = delegationMap["address"].(string) + ns.Name = delegationMap["name"].(string) + nameServers = append(nameServers, ns) + } + + var tenantID string + if tempVal, ok := extAttrs["Tenant ID"]; ok { + tenantID = tempVal.(string) + } + + connector := m.(*ibclient.Connector) + + objMgr := ibclient.NewObjectManager(connector, "Terraform", tenantID) + + zoneDelegated, err := objMgr.CreateZoneDelegated( + delegatedFQDN, + nameServers) + if err != nil { + return fmt.Errorf("Error creating Zone Delegated: %s", err) + } + + d.SetId(zoneDelegated.Ref) + + log.Printf("[DEBUG] %s: Creation of Zone Delegated complete", resourceZoneDelegatedIDString(d)) + return nil +} + +func resourceZoneDelegatedRead(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] %s: Begining to Get Zone Delegated", resourceZoneDelegatedIDString(d)) + + extAttrJSON := d.Get("ext_attrs").(string) + extAttrs := make(map[string]interface{}) + if extAttrJSON != "" { + if err := json.Unmarshal([]byte(extAttrJSON), &extAttrs); err != nil { + return fmt.Errorf("cannot process 'ext_attrs' field: %s", err.Error()) + } + } + + var tenantID string + if tempVal, ok := extAttrs["Tenant ID"]; ok { + tenantID = tempVal.(string) + } + + connector := m.(*ibclient.Connector) + + objMgr := ibclient.NewObjectManager(connector, "Terraform", tenantID) + + // first attempt to read by ref, otherwise assume import and support fqdn + zoneDelegatedObj, err := objMgr.GetZoneDelegatedByRef(d.Id()) + if err != nil { + zoneDelegatedObj, err = objMgr.GetZoneDelegated(d.Id()) + if err != nil { + return fmt.Errorf("Getting Zone Delegated failed: %s", err) + } + } + + var delegations []map[string]interface{} + for _, delegation := range zoneDelegatedObj.DelegateTo { + ns := make(map[string]interface{}) + ns["address"] = delegation.Address + ns["name"] = delegation.Name + delegations = append(delegations, ns) + } + + d.Set("fqdn", zoneDelegatedObj.Fqdn) + d.Set("delegate_to", delegations) + + d.SetId(zoneDelegatedObj.Ref) + + log.Printf("[DEBUG] %s: Completed reading Zone Delegated ", resourceZoneDelegatedIDString(d)) + return nil +} + +func resourceZoneDelegatedUpdate(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] %s: Beginning to update Zone Delegated", resourceZoneDelegatedIDString(d)) + + extAttrJSON := d.Get("ext_attrs").(string) + extAttrs := make(map[string]interface{}) + if extAttrJSON != "" { + if err := json.Unmarshal([]byte(extAttrJSON), &extAttrs); err != nil { + return fmt.Errorf("cannot process 'ext_attrs' field: %s", err.Error()) + } + } + + var nameServers []ibclient.NameServer + delegations := d.Get("delegate_to").(*schema.Set).List() + for _, delegation := range delegations { + var ns ibclient.NameServer + var delegationMap = delegation.(map[string]interface{}) + ns.Address = delegationMap["address"].(string) + ns.Name = delegationMap["name"].(string) + nameServers = append(nameServers, ns) + } + + var tenantID string + if tempVal, ok := extAttrs["Tenant ID"]; ok { + tenantID = tempVal.(string) + } + + connector := m.(*ibclient.Connector) + + objMgr := ibclient.NewObjectManager(connector, "Terraform", tenantID) + + zoneDelegatedUpdated, err := objMgr.UpdateZoneDelegated(d.Id(), nameServers) + if err != nil { + return fmt.Errorf("Updating of Zone Delegated failed : %s", err.Error()) + } + + d.SetId(zoneDelegatedUpdated.Ref) + return nil +} + +func resourceZoneDelegatedDelete(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] %s: Beginning Deletion of Zone Delegated", resourceZoneDelegatedIDString(d)) + + extAttrJSON := d.Get("ext_attrs").(string) + extAttrs := make(map[string]interface{}) + if extAttrJSON != "" { + if err := json.Unmarshal([]byte(extAttrJSON), &extAttrs); err != nil { + return fmt.Errorf("cannot process 'ext_attrs' field: %s", err.Error()) + } + } + + var tenantID string + if tempVal, ok := extAttrs["Tenant ID"]; ok { + tenantID = tempVal.(string) + } + + connector := m.(*ibclient.Connector) + + objMgr := ibclient.NewObjectManager(connector, "Terraform", tenantID) + + _, err := objMgr.DeleteZoneDelegated(d.Id()) + if err != nil { + return fmt.Errorf("Deletion of Zone Delegated failed : %s", err) + } + d.SetId("") + + log.Printf("[DEBUG] %s: Deletion of Zone Delegated complete", resourceZoneDelegatedIDString(d)) + return nil +} + +type resourceZoneDelegatedIDStringInterface interface { + Id() string +} + +func resourceZoneDelegatedIDString(d resourceZoneDelegatedIDStringInterface) string { + id := d.Id() + if id == "" { + id = "" + } + return fmt.Sprintf("infoblox_zone_delegated (ID = %s)", id) +} diff --git a/infoblox/resource_infoblox_zone_delegated_test.go b/infoblox/resource_infoblox_zone_delegated_test.go new file mode 100644 index 000000000..faddc307f --- /dev/null +++ b/infoblox/resource_infoblox_zone_delegated_test.go @@ -0,0 +1,92 @@ +package infoblox + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + ibclient "github.com/infobloxopen/infoblox-go-client/v2" +) + +func testAccCheckZoneDelegatedDestroy(s *terraform.State) error { + meta := testAccProvider.Meta() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "infoblox_zone_delegated" { + return fmt.Errorf("Resource type %s is invalid after destroy", rs.Type) + } + connector := meta.(ibclient.IBConnector) + objMgr := ibclient.NewObjectManager(connector, "terraform_test", "test") + rec, _ := objMgr.GetZoneDelegatedByRef(rs.Primary.ID) + if rec != nil { + return fmt.Errorf("Zone Delegation record found after destroy") + } + } + return nil +} + +func testAccZoneDelegatedCompare(t *testing.T, resPath string, expectedRec *ibclient.RecordNS) resource.TestCheckFunc { + return func(s *terraform.State) error { + res, found := s.RootModule().Resources[resPath] + if !found { + return fmt.Errorf("Not found: %s", resPath) + } + if res.Primary.ID == "" { + return fmt.Errorf("ID is not set") + } + meta := testAccProvider.Meta() + connector := meta.(ibclient.IBConnector) + objMgr := ibclient.NewObjectManager(connector, "terraform_test", "test") + + rec, _ := objMgr.GetZoneDelegatedByRef(res.Primary.ID) + if rec == nil { + return fmt.Errorf("record not found") + } + + if rec.Fqdn != expectedRec.Name { + return fmt.Errorf( + "'fqdn' does not match: got '%s', expected '%s'", + rec.Fqdn, expectedRec.Name) + } + if rec.DelegateTo[0].Address != expectedRec.Addresses[0].Address { + return fmt.Errorf( + "'delegate_to['address']' does not match: got '%s', expected '%s'", + rec.DelegateTo[0].Address, expectedRec.Addresses[0].Address) + } + if rec.DelegateTo[0].Name != expectedRec.Nameserver { + return fmt.Errorf( + "'delegate_to['name']' does not match: got '%s', expected '%s'", + rec.DelegateTo[0].Name, expectedRec.Nameserver) + } + return nil + } +} + +func TestAccResourceZoneDelegated(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckZoneDelegatedDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "infoblox_zone_delegated" "foo"{ + fqdn="subdomain.test.com" + delegate_to { + address = "1.2.3.4" + name = "dns.test.com" + } + }`), + Check: resource.ComposeTestCheckFunc( + testAccZoneDelegatedCompare(t, "infoblox_zone_delegated.foo", &ibclient.RecordNS{ + Name: "subdomain.test.com", + Addresses: []ibclient.ZoneNameServer{ibclient.ZoneNameServer{Address: "1.2.3.4"}}, + Nameserver: "dns.test.com", + }), + ), + }, + }, + }) +} From e913337b53246093aa28ca23a55fa0f010945d1f Mon Sep 17 00:00:00 2001 From: Neil Garratt Date: Tue, 26 Apr 2022 11:34:26 -0400 Subject: [PATCH 02/13] change address in infoblox_zone_delegated to computed attribute, to simplfy integrations with other providers that only list NS FQDNs --- docs/resources/infoblox_zone_delegated.md | 7 +-- .../v0.14/Resources/ZoneDelegated/infoblox.tf | 2 - infoblox/resource_infoblox_zone_delegated.go | 51 +++++++++++++------ .../resource_infoblox_zone_delegated_test.go | 15 ++++-- 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/docs/resources/infoblox_zone_delegated.md b/docs/resources/infoblox_zone_delegated.md index 718c15e6d..e48379665 100644 --- a/docs/resources/infoblox_zone_delegated.md +++ b/docs/resources/infoblox_zone_delegated.md @@ -7,10 +7,13 @@ The following list describes the parameters you can define in the `infoblox_zone ## Argument Reference * `fqdn`: (Required) The subdomain name to be delegated * `delegate_to`: (Required) Nested block(s)s for the delegated name servers - * `address`: (Required) The IP address of the name server * `name`: (Required) The FQDN of the name server * `ext_attrs`: (Optional) A set of NIOS extensible attributes that are attached to the record, using jsonencode. Currently only "Tenant ID" is supported +## Attribute Reference +* `delegate_to`: + * `address`: The computed IP address for each delegated name server + ## Example Usage ```hcl @@ -19,12 +22,10 @@ resource "infoblox_zone_delegated" "subdomain" { fqdn = "subdomain.test.com" delegate_to { - address = "205.251.197.208" name = "ns-1488.awsdns-58.org" } delegate_to { - address = "205.251.199.242" name = "ns-2034.awsdns-62.co.uk" } diff --git a/examples/v0.14/Resources/ZoneDelegated/infoblox.tf b/examples/v0.14/Resources/ZoneDelegated/infoblox.tf index 2137549e2..c2503d6e7 100644 --- a/examples/v0.14/Resources/ZoneDelegated/infoblox.tf +++ b/examples/v0.14/Resources/ZoneDelegated/infoblox.tf @@ -5,12 +5,10 @@ resource "infoblox_zone_delegated" "subdomain" { fqdn = "subdomain.example.com" delegate_to { - address = "205.251.197.208" name = "ns-1488.awsdns-58.org" } delegate_to { - address = "205.251.199.242" name = "ns-2034.awsdns-62.co.uk" } diff --git a/infoblox/resource_infoblox_zone_delegated.go b/infoblox/resource_infoblox_zone_delegated.go index 31d8c17d1..d532f5262 100644 --- a/infoblox/resource_infoblox_zone_delegated.go +++ b/infoblox/resource_infoblox_zone_delegated.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" "log" + "net" + "sort" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ibclient "github.com/infobloxopen/infoblox-go-client/v2" @@ -43,7 +45,7 @@ func resourceNameServer() *schema.Schema { Schema: map[string]*schema.Schema{ "address": { Type: schema.TypeString, - Required: true, + Computed: true, Description: "IP of Name Server", }, "name": { @@ -56,6 +58,27 @@ func resourceNameServer() *schema.Schema { } } +func computeDelegations(delegations []interface{}) ([]ibclient.NameServer, []map[string]interface{}, error) { + + var nameServers []ibclient.NameServer + computedDelegations := make([]map[string]interface{}, 0) + for _, delegation := range delegations { + var ns ibclient.NameServer + var delegationMap = delegation.(map[string]interface{}) + ns.Name = delegationMap["name"].(string) + lookupHosts, err := net.LookupHost(delegationMap["name"].(string)) + if err != nil { + return nil, nil, fmt.Errorf("Failed to resolve delegate_to: %s", err.Error()) + } + sort.Strings(lookupHosts) + ns.Address = lookupHosts[0] + delegationMap["address"] = ns.Address + nameServers = append(nameServers, ns) + computedDelegations = append(computedDelegations, delegationMap) + } + return nameServers, computedDelegations, nil +} + func resourceZoneDelegatedCreate(d *schema.ResourceData, m interface{}) error { log.Printf("[DEBUG] %s: Beginning to create Zone Delegated", resourceZoneDelegatedIDString(d)) @@ -69,14 +92,11 @@ func resourceZoneDelegatedCreate(d *schema.ResourceData, m interface{}) error { delegatedFQDN := d.Get("fqdn").(string) - var nameServers []ibclient.NameServer delegations := d.Get("delegate_to").(*schema.Set).List() - for _, delegation := range delegations { - var ns ibclient.NameServer - var delegationMap = delegation.(map[string]interface{}) - ns.Address = delegationMap["address"].(string) - ns.Name = delegationMap["name"].(string) - nameServers = append(nameServers, ns) + + nameServers, computedDelegations, err := computeDelegations(delegations) + if err != nil { + return err } var tenantID string @@ -95,6 +115,8 @@ func resourceZoneDelegatedCreate(d *schema.ResourceData, m interface{}) error { return fmt.Errorf("Error creating Zone Delegated: %s", err) } + d.Set("delegate_to", computedDelegations) + d.SetId(zoneDelegated.Ref) log.Printf("[DEBUG] %s: Creation of Zone Delegated complete", resourceZoneDelegatedIDString(d)) @@ -158,14 +180,11 @@ func resourceZoneDelegatedUpdate(d *schema.ResourceData, m interface{}) error { } } - var nameServers []ibclient.NameServer delegations := d.Get("delegate_to").(*schema.Set).List() - for _, delegation := range delegations { - var ns ibclient.NameServer - var delegationMap = delegation.(map[string]interface{}) - ns.Address = delegationMap["address"].(string) - ns.Name = delegationMap["name"].(string) - nameServers = append(nameServers, ns) + + nameServers, computedDelegations, err := computeDelegations(delegations) + if err != nil { + return err } var tenantID string @@ -182,6 +201,8 @@ func resourceZoneDelegatedUpdate(d *schema.ResourceData, m interface{}) error { return fmt.Errorf("Updating of Zone Delegated failed : %s", err.Error()) } + d.Set("delegate_to", computedDelegations) + d.SetId(zoneDelegatedUpdated.Ref) return nil } diff --git a/infoblox/resource_infoblox_zone_delegated_test.go b/infoblox/resource_infoblox_zone_delegated_test.go index faddc307f..89afecc4c 100644 --- a/infoblox/resource_infoblox_zone_delegated_test.go +++ b/infoblox/resource_infoblox_zone_delegated_test.go @@ -2,6 +2,8 @@ package infoblox import ( "fmt" + "net" + "sort" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -39,6 +41,13 @@ func testAccZoneDelegatedCompare(t *testing.T, resPath string, expectedRec *ibcl connector := meta.(ibclient.IBConnector) objMgr := ibclient.NewObjectManager(connector, "terraform_test", "test") + lookupHosts, err := net.LookupHost(expectedRec.Nameserver) + if err != nil { + return fmt.Errorf("Failed to resolve delegate_to: %s", err.Error()) + } + sort.Strings(lookupHosts) + expectedRec.Addresses = append(expectedRec.Addresses, ibclient.ZoneNameServer{Address: lookupHosts[0]}) + rec, _ := objMgr.GetZoneDelegatedByRef(res.Primary.ID) if rec == nil { return fmt.Errorf("record not found") @@ -75,15 +84,13 @@ func TestAccResourceZoneDelegated(t *testing.T) { resource "infoblox_zone_delegated" "foo"{ fqdn="subdomain.test.com" delegate_to { - address = "1.2.3.4" - name = "dns.test.com" + name = "ns2.infoblox.com" } }`), Check: resource.ComposeTestCheckFunc( testAccZoneDelegatedCompare(t, "infoblox_zone_delegated.foo", &ibclient.RecordNS{ Name: "subdomain.test.com", - Addresses: []ibclient.ZoneNameServer{ibclient.ZoneNameServer{Address: "1.2.3.4"}}, - Nameserver: "dns.test.com", + Nameserver: "ns2.infoblox.com", }), ), }, From 42698cd734f72fd601c5e9fd62da1b5264e554d7 Mon Sep 17 00:00:00 2001 From: Piper Dougherty Date: Wed, 15 May 2024 20:44:49 -0500 Subject: [PATCH 03/13] fix: removed deprecated function --- infoblox/resource_infoblox_zone_delegated.go | 10 +++------- infoblox/resource_infoblox_zone_delegated_test.go | 5 ++--- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/infoblox/resource_infoblox_zone_delegated.go b/infoblox/resource_infoblox_zone_delegated.go index d532f5262..2591f1bd3 100644 --- a/infoblox/resource_infoblox_zone_delegated.go +++ b/infoblox/resource_infoblox_zone_delegated.go @@ -59,12 +59,11 @@ func resourceNameServer() *schema.Schema { } func computeDelegations(delegations []interface{}) ([]ibclient.NameServer, []map[string]interface{}, error) { - var nameServers []ibclient.NameServer computedDelegations := make([]map[string]interface{}, 0) for _, delegation := range delegations { var ns ibclient.NameServer - var delegationMap = delegation.(map[string]interface{}) + delegationMap := delegation.(map[string]interface{}) ns.Name = delegationMap["name"].(string) lookupHosts, err := net.LookupHost(delegationMap["name"].(string)) if err != nil { @@ -144,12 +143,9 @@ func resourceZoneDelegatedRead(d *schema.ResourceData, m interface{}) error { objMgr := ibclient.NewObjectManager(connector, "Terraform", tenantID) // first attempt to read by ref, otherwise assume import and support fqdn - zoneDelegatedObj, err := objMgr.GetZoneDelegatedByRef(d.Id()) + zoneDelegatedObj, err := objMgr.GetZoneDelegated(d.Id()) if err != nil { - zoneDelegatedObj, err = objMgr.GetZoneDelegated(d.Id()) - if err != nil { - return fmt.Errorf("Getting Zone Delegated failed: %s", err) - } + return fmt.Errorf("Getting Zone Delegated failed: %s", err) } var delegations []map[string]interface{} diff --git a/infoblox/resource_infoblox_zone_delegated_test.go b/infoblox/resource_infoblox_zone_delegated_test.go index 89afecc4c..e205b83ca 100644 --- a/infoblox/resource_infoblox_zone_delegated_test.go +++ b/infoblox/resource_infoblox_zone_delegated_test.go @@ -20,7 +20,7 @@ func testAccCheckZoneDelegatedDestroy(s *terraform.State) error { } connector := meta.(ibclient.IBConnector) objMgr := ibclient.NewObjectManager(connector, "terraform_test", "test") - rec, _ := objMgr.GetZoneDelegatedByRef(rs.Primary.ID) + rec, _ := objMgr.GetZoneDelegated(rs.Primary.ID) if rec != nil { return fmt.Errorf("Zone Delegation record found after destroy") } @@ -48,7 +48,7 @@ func testAccZoneDelegatedCompare(t *testing.T, resPath string, expectedRec *ibcl sort.Strings(lookupHosts) expectedRec.Addresses = append(expectedRec.Addresses, ibclient.ZoneNameServer{Address: lookupHosts[0]}) - rec, _ := objMgr.GetZoneDelegatedByRef(res.Primary.ID) + rec, _ := objMgr.GetZoneDelegated(res.Primary.ID) if rec == nil { return fmt.Errorf("record not found") } @@ -73,7 +73,6 @@ func testAccZoneDelegatedCompare(t *testing.T, resPath string, expectedRec *ibcl } func TestAccResourceZoneDelegated(t *testing.T) { - resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, From 5e271daacd8fd27692a3e9e6c798d8bfbe82311e Mon Sep 17 00:00:00 2001 From: Piper Dougherty Date: Wed, 15 May 2024 20:59:15 -0500 Subject: [PATCH 04/13] fix: refactoring deprecated tests --- infoblox/resource_infoblox_zone_delegated_test.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/infoblox/resource_infoblox_zone_delegated_test.go b/infoblox/resource_infoblox_zone_delegated_test.go index e205b83ca..35176f5cc 100644 --- a/infoblox/resource_infoblox_zone_delegated_test.go +++ b/infoblox/resource_infoblox_zone_delegated_test.go @@ -41,12 +41,12 @@ func testAccZoneDelegatedCompare(t *testing.T, resPath string, expectedRec *ibcl connector := meta.(ibclient.IBConnector) objMgr := ibclient.NewObjectManager(connector, "terraform_test", "test") - lookupHosts, err := net.LookupHost(expectedRec.Nameserver) + lookupHosts, err := net.LookupHost(*expectedRec.Nameserver) if err != nil { return fmt.Errorf("Failed to resolve delegate_to: %s", err.Error()) } sort.Strings(lookupHosts) - expectedRec.Addresses = append(expectedRec.Addresses, ibclient.ZoneNameServer{Address: lookupHosts[0]}) + expectedRec.Addresses = append(expectedRec.Addresses, &ibclient.ZoneNameServer{Address: lookupHosts[0]}) rec, _ := objMgr.GetZoneDelegated(res.Primary.ID) if rec == nil { @@ -63,15 +63,19 @@ func testAccZoneDelegatedCompare(t *testing.T, resPath string, expectedRec *ibcl "'delegate_to['address']' does not match: got '%s', expected '%s'", rec.DelegateTo[0].Address, expectedRec.Addresses[0].Address) } - if rec.DelegateTo[0].Name != expectedRec.Nameserver { + if rec.DelegateTo[0].Name != *expectedRec.Nameserver { return fmt.Errorf( "'delegate_to['name']' does not match: got '%s', expected '%s'", - rec.DelegateTo[0].Name, expectedRec.Nameserver) + rec.DelegateTo[0].Name, *expectedRec.Nameserver) } return nil } } +func strPtr(s string) *string { + return &s +} + func TestAccResourceZoneDelegated(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -89,7 +93,7 @@ func TestAccResourceZoneDelegated(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccZoneDelegatedCompare(t, "infoblox_zone_delegated.foo", &ibclient.RecordNS{ Name: "subdomain.test.com", - Nameserver: "ns2.infoblox.com", + Nameserver: strPtr("ns2.infoblox.com"), }), ), }, From 3e9bdbf4e977351d2ac72c5720e42c6425334dcb Mon Sep 17 00:00:00 2001 From: Piper Dougherty Date: Wed, 15 May 2024 21:12:33 -0500 Subject: [PATCH 05/13] cleanup: #217 suggestions --- infoblox/resource_infoblox_zone_delegated.go | 37 ++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/infoblox/resource_infoblox_zone_delegated.go b/infoblox/resource_infoblox_zone_delegated.go index 2591f1bd3..778c1ffb2 100644 --- a/infoblox/resource_infoblox_zone_delegated.go +++ b/infoblox/resource_infoblox_zone_delegated.go @@ -26,7 +26,24 @@ func resourceZoneDelegated() *schema.Resource { Required: true, Description: "The FQDN of the delegated zone.", }, - "delegate_to": resourceNameServer(), + "delegate_to": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address": { + Type: schema.TypeString, + Computed: true, + Description: "IP of Name Server", + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "FQDN of Name Server", + }, + }, + }, + }, "ext_attrs": { Type: schema.TypeString, Default: "", @@ -67,7 +84,7 @@ func computeDelegations(delegations []interface{}) ([]ibclient.NameServer, []map ns.Name = delegationMap["name"].(string) lookupHosts, err := net.LookupHost(delegationMap["name"].(string)) if err != nil { - return nil, nil, fmt.Errorf("Failed to resolve delegate_to: %s", err.Error()) + return nil, nil, fmt.Errorf("Failed to resolve delegate_to: %w", err) } sort.Strings(lookupHosts) ns.Address = lookupHosts[0] @@ -85,7 +102,7 @@ func resourceZoneDelegatedCreate(d *schema.ResourceData, m interface{}) error { extAttrs := make(map[string]interface{}) if extAttrJSON != "" { if err := json.Unmarshal([]byte(extAttrJSON), &extAttrs); err != nil { - return fmt.Errorf("cannot process 'ext_attrs' field: %s", err.Error()) + return fmt.Errorf("cannot process 'ext_attrs' field: %w", err) } } @@ -111,7 +128,7 @@ func resourceZoneDelegatedCreate(d *schema.ResourceData, m interface{}) error { delegatedFQDN, nameServers) if err != nil { - return fmt.Errorf("Error creating Zone Delegated: %s", err) + return fmt.Errorf("Error creating Zone Delegated: %w", err) } d.Set("delegate_to", computedDelegations) @@ -129,7 +146,7 @@ func resourceZoneDelegatedRead(d *schema.ResourceData, m interface{}) error { extAttrs := make(map[string]interface{}) if extAttrJSON != "" { if err := json.Unmarshal([]byte(extAttrJSON), &extAttrs); err != nil { - return fmt.Errorf("cannot process 'ext_attrs' field: %s", err.Error()) + return fmt.Errorf("cannot process 'ext_attrs' field: %w", err) } } @@ -145,7 +162,7 @@ func resourceZoneDelegatedRead(d *schema.ResourceData, m interface{}) error { // first attempt to read by ref, otherwise assume import and support fqdn zoneDelegatedObj, err := objMgr.GetZoneDelegated(d.Id()) if err != nil { - return fmt.Errorf("Getting Zone Delegated failed: %s", err) + return fmt.Errorf("Getting Zone Delegated failed: %w", err) } var delegations []map[string]interface{} @@ -172,7 +189,7 @@ func resourceZoneDelegatedUpdate(d *schema.ResourceData, m interface{}) error { extAttrs := make(map[string]interface{}) if extAttrJSON != "" { if err := json.Unmarshal([]byte(extAttrJSON), &extAttrs); err != nil { - return fmt.Errorf("cannot process 'ext_attrs' field: %s", err.Error()) + return fmt.Errorf("cannot process 'ext_attrs' field: %w", err) } } @@ -194,7 +211,7 @@ func resourceZoneDelegatedUpdate(d *schema.ResourceData, m interface{}) error { zoneDelegatedUpdated, err := objMgr.UpdateZoneDelegated(d.Id(), nameServers) if err != nil { - return fmt.Errorf("Updating of Zone Delegated failed : %s", err.Error()) + return fmt.Errorf("Updating of Zone Delegated failed : %w", err) } d.Set("delegate_to", computedDelegations) @@ -210,7 +227,7 @@ func resourceZoneDelegatedDelete(d *schema.ResourceData, m interface{}) error { extAttrs := make(map[string]interface{}) if extAttrJSON != "" { if err := json.Unmarshal([]byte(extAttrJSON), &extAttrs); err != nil { - return fmt.Errorf("cannot process 'ext_attrs' field: %s", err.Error()) + return fmt.Errorf("cannot process 'ext_attrs' field: %w", err) } } @@ -225,7 +242,7 @@ func resourceZoneDelegatedDelete(d *schema.ResourceData, m interface{}) error { _, err := objMgr.DeleteZoneDelegated(d.Id()) if err != nil { - return fmt.Errorf("Deletion of Zone Delegated failed : %s", err) + return fmt.Errorf("Deletion of Zone Delegated failed : %w", err) } d.SetId("") From 35a9aa4a91f5fbf12587cda1c5d1a319eaa9ea7d Mon Sep 17 00:00:00 2001 From: Piper Dougherty Date: Wed, 15 May 2024 21:36:30 -0500 Subject: [PATCH 06/13] fix: add back GetZoneDelegatedByRef --- infoblox/resource_infoblox_zone_delegated.go | 7 +++++-- infoblox/resource_infoblox_zone_delegated_test.go | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/infoblox/resource_infoblox_zone_delegated.go b/infoblox/resource_infoblox_zone_delegated.go index 778c1ffb2..ef34d2d88 100644 --- a/infoblox/resource_infoblox_zone_delegated.go +++ b/infoblox/resource_infoblox_zone_delegated.go @@ -160,9 +160,12 @@ func resourceZoneDelegatedRead(d *schema.ResourceData, m interface{}) error { objMgr := ibclient.NewObjectManager(connector, "Terraform", tenantID) // first attempt to read by ref, otherwise assume import and support fqdn - zoneDelegatedObj, err := objMgr.GetZoneDelegated(d.Id()) + zoneDelegatedObj, err := objMgr.GetZoneDelegatedByRef(d.Id()) if err != nil { - return fmt.Errorf("Getting Zone Delegated failed: %w", err) + zoneDelegatedObj, err = objMgr.GetZoneDelegated(d.Id()) + if err != nil { + return fmt.Errorf("Getting Zone Delegated failed: %s", err) + } } var delegations []map[string]interface{} diff --git a/infoblox/resource_infoblox_zone_delegated_test.go b/infoblox/resource_infoblox_zone_delegated_test.go index 35176f5cc..3be553eb5 100644 --- a/infoblox/resource_infoblox_zone_delegated_test.go +++ b/infoblox/resource_infoblox_zone_delegated_test.go @@ -20,7 +20,7 @@ func testAccCheckZoneDelegatedDestroy(s *terraform.State) error { } connector := meta.(ibclient.IBConnector) objMgr := ibclient.NewObjectManager(connector, "terraform_test", "test") - rec, _ := objMgr.GetZoneDelegated(rs.Primary.ID) + rec, _ := objMgr.GetZoneDelegatedByRef(rs.Primary.ID) if rec != nil { return fmt.Errorf("Zone Delegation record found after destroy") } @@ -48,7 +48,7 @@ func testAccZoneDelegatedCompare(t *testing.T, resPath string, expectedRec *ibcl sort.Strings(lookupHosts) expectedRec.Addresses = append(expectedRec.Addresses, &ibclient.ZoneNameServer{Address: lookupHosts[0]}) - rec, _ := objMgr.GetZoneDelegated(res.Primary.ID) + rec, _ := objMgr.GetZoneDelegatedByRef(res.Primary.ID) if rec == nil { return fmt.Errorf("record not found") } @@ -77,6 +77,7 @@ func strPtr(s string) *string { } func TestAccResourceZoneDelegated(t *testing.T) { + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, From 06c76aea95a7e635c5e993ac6e984bbdf8b979ff Mon Sep 17 00:00:00 2001 From: apattar Date: Wed, 21 Aug 2024 15:56:04 +0530 Subject: [PATCH 07/13] adding zone-delegation resource and data source --- .../datasource_infoblox_zone_delegated.go | 192 ++++++ infoblox/provider.go | 1 + infoblox/resource_infoblox_zone_delegated.go | 576 ++++++++++++++---- infoblox/resource_infoblox_zone_forward.go | 4 +- 4 files changed, 637 insertions(+), 136 deletions(-) create mode 100644 infoblox/datasource_infoblox_zone_delegated.go diff --git a/infoblox/datasource_infoblox_zone_delegated.go b/infoblox/datasource_infoblox_zone_delegated.go new file mode 100644 index 000000000..6a047127a --- /dev/null +++ b/infoblox/datasource_infoblox_zone_delegated.go @@ -0,0 +1,192 @@ +package infoblox + +import ( + "context" + "encoding/json" + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + ibclient "github.com/infobloxopen/infoblox-go-client/v2" + "strconv" + "time" +) + +func dataSourceZoneDelegated() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceZoneDelegatedRead, + Schema: map[string]*schema.Schema{ + "filters": { + Type: schema.TypeMap, + Required: true, + }, + "results": { + Type: schema.TypeList, + Computed: true, + Description: "List of Forward Zones matching filters", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "fqdn": { + Type: schema.TypeString, + Required: true, + Description: "The FQDN of the delegated zone.", + }, + "delegate_to": { + Type: schema.TypeSet, + Optional: true, + Description: "The Infoblox appliance redirects queries for data for the delegated zone to this remote name server.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address": { + Type: schema.TypeString, + Required: true, + Description: "The IPv4 Address or IPv6 Address of the server.", + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "A resolvable domain name for the external DNS server.", + }, + }, + }, + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Description: "A descriptive comment.", + }, + "disable": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Determines if the zone is disabled or not.", + }, + "locked": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "If you enable this flag, other administrators cannot make conflicting changes. This is for administration purposes only. " + + "The zone will continue to serve DNS data even when it is locked.", + }, + "ns_group": { + Type: schema.TypeString, + Optional: true, + Description: "The delegation NS group bound with delegated zone.", + }, + "delegated_ttl": { + Type: schema.TypeInt, + Optional: true, + Default: ttlUndef, + Description: "TTL value for zone-delegated.", + }, + "ext_attrs": { + Type: schema.TypeString, + Default: "", + Optional: true, + Description: "Extensible attributes, as a map in JSON format", + }, + "view": { + Type: schema.TypeString, + Optional: true, + Default: "default", + Description: "The DNS view in which the zone is created.", + }, + "zone_format": { + Type: schema.TypeString, + Optional: true, + Default: "FORWARD", + Description: "The format of the zone. Valid values are: FORWARD, IPV4, IPV6.", + }, + }, + }, + }, + }, + } +} + +func dataSourceZoneDelegatedRead(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + connector := m.(ibclient.IBConnector) + + var diags diag.Diagnostics + + filters := filterFromMap(d.Get("filters").(map[string]interface{})) + + objMgr := ibclient.NewObjectManager(connector, "Terraform", "") + + qp := ibclient.NewQueryParams(false, filters) + res, err := objMgr.GetZoneDelegatedByFilters(qp) + if err != nil { + return diag.FromErr(fmt.Errorf("failed to get zone delegated records: %w", err)) + } + + if res == nil { + return diag.FromErr(fmt.Errorf("API returns a nil/empty ID for zone delegated")) + } + // TODO: temporary scaffold, need to rework marshalling/unmarshalling of EAs + // (avoiding additional layer of keys ("value" key) + results := make([]interface{}, 0, len(res)) + for _, r := range res { + zoneDelegatedFlat, err := flattenZoneDelegated(r) + if err != nil { + return diag.FromErr(fmt.Errorf("failed to flatten zone delegated : %w", err)) + } + results = append(results, zoneDelegatedFlat) + } + + err = d.Set("results", results) + if err != nil { + return diag.FromErr(err) + } + + // always run + d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) + + return diags + +} + +func flattenZoneDelegated(zoneDelegated ibclient.ZoneDelegated) (map[string]interface{}, error) { + var eaMap map[string]interface{} + if zoneDelegated.Ea != nil && len(zoneDelegated.Ea) > 0 { + eaMap = zoneDelegated.Ea + } else { + eaMap = make(map[string]interface{}) + } + + ea, err := json.Marshal(eaMap) + if err != nil { + return nil, err + } + + res := map[string]interface{}{ + "id": zoneDelegated.Ref, + "fqdn": zoneDelegated.Fqdn, + "ext_attrs": string(ea), + "zone_format": zoneDelegated.ZoneFormat, + "view": *zoneDelegated.View, + } + if zoneDelegated.Comment != nil { + res["comment"] = *zoneDelegated.Comment + } + if zoneDelegated.Disable != nil { + res["disable"] = *zoneDelegated.Disable + } + if zoneDelegated.Locked != nil { + res["locked"] = *zoneDelegated.Locked + } + if zoneDelegated.NsGroup != nil { + res["ns_group"] = *zoneDelegated.NsGroup + } + if zoneDelegated.DelegatedTtl != nil { + res["delegated_ttl"] = *zoneDelegated.DelegatedTtl + } + if zoneDelegated.DelegateTo.IsNull == false { + nsInterface := convertForwardToInterface(zoneDelegated.DelegateTo) + res["delegate_to"] = nsInterface + } + + return res, nil +} diff --git a/infoblox/provider.go b/infoblox/provider.go index 31e1e713c..dd2752b77 100644 --- a/infoblox/provider.go +++ b/infoblox/provider.go @@ -222,6 +222,7 @@ func Provider() *schema.Provider { "infoblox_aaaa_record": dataSourceAAAARecord(), "infoblox_cname_record": dataSourceCNameRecord(), "infoblox_ptr_record": dataSourcePtrRecord(), + "infoblox_zone_delegated": dataSourceZoneDelegated(), "infoblox_txt_record": dataSourceTXTRecord(), "infoblox_mx_record": dataSourceMXRecord(), "infoblox_srv_record": dataSourceSRVRecord(), diff --git a/infoblox/resource_infoblox_zone_delegated.go b/infoblox/resource_infoblox_zone_delegated.go index ef34d2d88..a971c70f1 100644 --- a/infoblox/resource_infoblox_zone_delegated.go +++ b/infoblox/resource_infoblox_zone_delegated.go @@ -3,10 +3,6 @@ package infoblox import ( "encoding/json" "fmt" - "log" - "net" - "sort" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ibclient "github.com/infobloxopen/infoblox-go-client/v2" ) @@ -18,7 +14,7 @@ func resourceZoneDelegated() *schema.Resource { Update: resourceZoneDelegatedUpdate, Delete: resourceZoneDelegatedDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + State: resourceZoneDelegatedImport, }, Schema: map[string]*schema.Schema{ "fqdn": { @@ -27,240 +23,552 @@ func resourceZoneDelegated() *schema.Resource { Description: "The FQDN of the delegated zone.", }, "delegate_to": { - Type: schema.TypeSet, - Required: true, + Type: schema.TypeSet, + Optional: true, + Description: "The Infoblox appliance redirects queries for data for the delegated zone to this remote name server.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "address": { Type: schema.TypeString, - Computed: true, - Description: "IP of Name Server", + Required: true, + Description: "The IPv4 Address or IPv6 Address of the server.", }, "name": { Type: schema.TypeString, Required: true, - Description: "FQDN of Name Server", + Description: "A resolvable domain name for the external DNS server.", }, }, }, }, + "comment": { + Type: schema.TypeString, + Optional: true, + Description: "A descriptive comment.", + }, + "disable": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Determines if the zone is disabled or not.", + }, + "locked": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "If you enable this flag, other administrators cannot make conflicting changes. This is for administration purposes only. " + + "The zone will continue to serve DNS data even when it is locked.", + }, + "ns_group": { + Type: schema.TypeString, + Optional: true, + Description: "The delegation NS group bound with delegated zone.", + }, + "delegated_ttl": { + Type: schema.TypeInt, + Optional: true, + Default: ttlUndef, + Description: "TTL value for zone-delegated.", + }, "ext_attrs": { Type: schema.TypeString, Default: "", Optional: true, Description: "Extensible attributes, as a map in JSON format", }, - }, - } -} - -func resourceNameServer() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeSet, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "address": { - Type: schema.TypeString, - Computed: true, - Description: "IP of Name Server", - }, - "name": { - Type: schema.TypeString, - Required: true, - Description: "FQDN of Name Server", - }, + "view": { + Type: schema.TypeString, + Optional: true, + Default: "default", + Description: "The DNS view in which the zone is created.", + }, + "zone_format": { + Type: schema.TypeString, + Optional: true, + Default: "FORWARD", + Description: "The format of the zone. Valid values are: FORWARD, IPV4, IPV6.", + }, + "internal_id": { + Type: schema.TypeString, + Computed: true, + Description: "Internal ID of an object at NIOS side," + + " used by Infoblox Terraform plugin to search for a NIOS's object" + + " which corresponds to the Terraform resource.", + }, + "ref": { + Type: schema.TypeString, + Computed: true, + Description: "NIOS object's reference, not to be set by a user.", }, }, } } -func computeDelegations(delegations []interface{}) ([]ibclient.NameServer, []map[string]interface{}, error) { - var nameServers []ibclient.NameServer - computedDelegations := make([]map[string]interface{}, 0) - for _, delegation := range delegations { - var ns ibclient.NameServer - delegationMap := delegation.(map[string]interface{}) - ns.Name = delegationMap["name"].(string) - lookupHosts, err := net.LookupHost(delegationMap["name"].(string)) - if err != nil { - return nil, nil, fmt.Errorf("Failed to resolve delegate_to: %w", err) - } - sort.Strings(lookupHosts) - ns.Address = lookupHosts[0] - delegationMap["address"] = ns.Address - nameServers = append(nameServers, ns) - computedDelegations = append(computedDelegations, delegationMap) +func resourceZoneDelegatedCreate(d *schema.ResourceData, m interface{}) error { + + if intId := d.Get("internal_id"); intId.(string) != "" { + return fmt.Errorf("the value of 'internal_id' field must not be set manually") } - return nameServers, computedDelegations, nil -} + fqdn := d.Get("fqdn").(string) -func resourceZoneDelegatedCreate(d *schema.ResourceData, m interface{}) error { - log.Printf("[DEBUG] %s: Beginning to create Zone Delegated", resourceZoneDelegatedIDString(d)) + nsGroup, nsGroupOk := d.GetOk("ns_group") + dtInterface, delegateToOk := d.GetOk("delegate_to") - extAttrJSON := d.Get("ext_attrs").(string) - extAttrs := make(map[string]interface{}) - if extAttrJSON != "" { - if err := json.Unmarshal([]byte(extAttrJSON), &extAttrs); err != nil { - return fmt.Errorf("cannot process 'ext_attrs' field: %w", err) + var delegateTo []ibclient.NameServer + var nullDT ibclient.NullForwardTo + if !nsGroupOk && !delegateToOk { + return fmt.Errorf("either 'ns_group' or 'delegate_to' must be set") + } + if delegateToOk { + dtSlice := dtInterface.(*schema.Set).List() + var err error + delegateTo, err = validateForwardTo(dtSlice) + if err != nil { + return err } + nullDT = ibclient.NullForwardTo{IsNull: false, ForwardTo: delegateTo} } - delegatedFQDN := d.Get("fqdn").(string) + comment := d.Get("comment").(string) + disable := d.Get("disable").(bool) + locked := d.Get("locked").(bool) + delegatedTtl := d.Get("delegated_ttl") + + var ttl uint32 + useTtl := false + tempTTL := delegatedTtl.(int) + if tempTTL >= 0 { + useTtl = true + ttl = uint32(tempTTL) + } else if tempTTL != ttlUndef { + return fmt.Errorf("TTL value must be 0 or higher") + } - delegations := d.Get("delegate_to").(*schema.Set).List() + view := d.Get("view").(string) + zoneFormat := d.Get("zone_format").(string) - nameServers, computedDelegations, err := computeDelegations(delegations) + extAttrJSON := d.Get("ext_attrs").(string) + extAttrs, err := terraformDeserializeEAs(extAttrJSON) if err != nil { return err } + // Generate internal ID and add it to the extensible attributes + internalId := generateInternalId() + extAttrs[eaNameForInternalId] = internalId.String() + var tenantID string - if tempVal, ok := extAttrs["Tenant ID"]; ok { + if tempVal, found := extAttrs[eaNameForTenantId]; found { tenantID = tempVal.(string) } - connector := m.(*ibclient.Connector) - + connector := m.(ibclient.IBConnector) objMgr := ibclient.NewObjectManager(connector, "Terraform", tenantID) - zoneDelegated, err := objMgr.CreateZoneDelegated( - delegatedFQDN, - nameServers) + newZoneDelegated, err := objMgr.CreateZoneDelegated(fqdn, nullDT, comment, disable, locked, nsGroup.(string), ttl, useTtl, extAttrs, view, zoneFormat) if err != nil { - return fmt.Errorf("Error creating Zone Delegated: %w", err) + return fmt.Errorf("failed to create zone delegation : %s", err) } - - d.Set("delegate_to", computedDelegations) - - d.SetId(zoneDelegated.Ref) - - log.Printf("[DEBUG] %s: Creation of Zone Delegated complete", resourceZoneDelegatedIDString(d)) - return nil + d.SetId(newZoneDelegated.Ref) + if err = d.Set("internal_id", internalId.String()); err != nil { + return err + } + if err = d.Set("ref", newZoneDelegated.Ref); err != nil { + return err + } + return resourceZoneDelegatedRead(d, m) } func resourceZoneDelegatedRead(d *schema.ResourceData, m interface{}) error { - log.Printf("[DEBUG] %s: Begining to Get Zone Delegated", resourceZoneDelegatedIDString(d)) + var ttl int extAttrJSON := d.Get("ext_attrs").(string) extAttrs := make(map[string]interface{}) - if extAttrJSON != "" { - if err := json.Unmarshal([]byte(extAttrJSON), &extAttrs); err != nil { - return fmt.Errorf("cannot process 'ext_attrs' field: %w", err) - } + extAttrs, err := terraformDeserializeEAs(extAttrJSON) + if err != nil { + return err } - var tenantID string - if tempVal, ok := extAttrs["Tenant ID"]; ok { - tenantID = tempVal.(string) + rec, err := searchObjectByRefOrInternalId("ZoneDelegated", d, m) + if err != nil { + if _, ok := err.(*ibclient.NotFoundError); !ok { + return ibclient.NewNotFoundError(fmt.Sprintf( + "cannot find appropriate object on NIOS side for resource with ID '%s': %s;", d.Id(), err)) + } else { + d.SetId("") + return nil + } } - connector := m.(*ibclient.Connector) + var zoneDelegated *ibclient.ZoneDelegated + recJson, err := json.Marshal(rec) + if err != nil { + return fmt.Errorf("failed to marshal zone delegated record : %s", err.Error()) + } + err = json.Unmarshal(recJson, &zoneDelegated) + if err != nil { + return fmt.Errorf("failed getting zone delegated record : %s", err.Error()) + } - objMgr := ibclient.NewObjectManager(connector, "Terraform", tenantID) + delete(zoneDelegated.Ea, eaNameForInternalId) + omittedEAs := omitEAs(zoneDelegated.Ea, extAttrs) - // first attempt to read by ref, otherwise assume import and support fqdn - zoneDelegatedObj, err := objMgr.GetZoneDelegatedByRef(d.Id()) - if err != nil { - zoneDelegatedObj, err = objMgr.GetZoneDelegated(d.Id()) + if omittedEAs != nil && len(omittedEAs) > 0 { + eaJSON, err := terraformSerializeEAs(omittedEAs) if err != nil { - return fmt.Errorf("Getting Zone Delegated failed: %s", err) + return err + } + if err = d.Set("ext_attrs", eaJSON); err != nil { + return err } } - var delegations []map[string]interface{} - for _, delegation := range zoneDelegatedObj.DelegateTo { - ns := make(map[string]interface{}) - ns["address"] = delegation.Address - ns["name"] = delegation.Name - delegations = append(delegations, ns) + if zoneDelegated.DelegatedTtl != nil { + ttl = int(*zoneDelegated.DelegatedTtl) + } + if !*zoneDelegated.UseDelegatedTtl { + ttl = ttlUndef + } + if err = d.Set("delegated_ttl", ttl); err != nil { + return err } - d.Set("fqdn", zoneDelegatedObj.Fqdn) - d.Set("delegate_to", delegations) + if err := d.Set("fqdn", zoneDelegated.Fqdn); err != nil { + return err + } - d.SetId(zoneDelegatedObj.Ref) + if zoneDelegated.View != nil { + if err := d.Set("view", *zoneDelegated.View); err != nil { + return err + } + } - log.Printf("[DEBUG] %s: Completed reading Zone Delegated ", resourceZoneDelegatedIDString(d)) + if err := d.Set("zone_format", zoneDelegated.ZoneFormat); err != nil { + return err + } + + if zoneDelegated.NsGroup != nil { + if err := d.Set("ns_group", *zoneDelegated.NsGroup); err != nil { + return err + } + } else { + if err := d.Set("ns_group", ""); err != nil { + return err + } + } + + if zoneDelegated.Comment != nil { + if err := d.Set("comment", *zoneDelegated.Comment); err != nil { + return err + } + } + + if zoneDelegated.Disable != nil { + if err := d.Set("disable", *zoneDelegated.Disable); err != nil { + return err + } + } + + if zoneDelegated.DelegateTo.ForwardTo != nil { + nsInterface := convertForwardToInterface(zoneDelegated.DelegateTo) + if err = d.Set("delegate_to", nsInterface); err != nil { + return err + } + } else { + if err := d.Set("delegate_to", nil); err != nil { + return err + } + } + + if zoneDelegated.Locked != nil { + if err := d.Set("locked", *zoneDelegated.Locked); err != nil { + return err + } + } + + d.SetId(zoneDelegated.Ref) return nil } func resourceZoneDelegatedUpdate(d *schema.ResourceData, m interface{}) error { - log.Printf("[DEBUG] %s: Beginning to update Zone Delegated", resourceZoneDelegatedIDString(d)) + var updateSuccessful bool + defer func() { + // Reverting the state back, in case of a failure, + // otherwise Terraform will keep the values, which leaded to the failure, in the state file. + if !updateSuccessful { + prevComment, _ := d.GetChange("comment") + prevDisable, _ := d.GetChange("disable") + prevLocked, _ := d.GetChange("locked") + prevNsGroup, _ := d.GetChange("ns_group") + prevDelegateTo, _ := d.GetChange("delegate_to") + prevExtAttrs, _ := d.GetChange("ext_attrs") + prevTtl, _ := d.GetChange("delegated_ttl") + + _ = d.Set("comment", prevComment.(string)) + _ = d.Set("disable", prevDisable.(bool)) + _ = d.Set("locked", prevLocked.(bool)) + _ = d.Set("ns_group", prevNsGroup.(string)) + _ = d.Set("delegate_to", prevDelegateTo) + _ = d.Set("ext_attrs", prevExtAttrs.(string)) + _ = d.Set("delegated_ttl", prevTtl.(int)) + } + }() - extAttrJSON := d.Get("ext_attrs").(string) - extAttrs := make(map[string]interface{}) - if extAttrJSON != "" { - if err := json.Unmarshal([]byte(extAttrJSON), &extAttrs); err != nil { - return fmt.Errorf("cannot process 'ext_attrs' field: %w", err) + _, nsGroupOk := d.GetOk("ns_group") + dtInterface, delegateToOk := d.GetOk("delegate_to") + + if d.HasChange("internal_id") { + return fmt.Errorf("changing the value of 'internal_id' field is not allowed") + } + if d.HasChange("fqdn") { + return fmt.Errorf("changing the value of 'fqdn' field is not allowed") + } + if d.HasChange("view") { + return fmt.Errorf("changing the value of 'view' field is not allowed") + } + if d.HasChange("zone_format") { + return fmt.Errorf("changing the value of 'zone_format' field is not allowed") + } + + var delegateTo []ibclient.NameServer + var nullDT ibclient.NullForwardTo + if !nsGroupOk && !delegateToOk { + return fmt.Errorf("either ns_group or delegate_to must be set") + } else if !delegateToOk { + nullDT = ibclient.NullForwardTo{IsNull: false, ForwardTo: []ibclient.NameServer{}} + } else { + dtSlice := dtInterface.(*schema.Set).List() + var err error + delegateTo, err = validateForwardTo(dtSlice) + if err != nil { + return err } + nullDT = ibclient.NullForwardTo{IsNull: false, ForwardTo: delegateTo} } - delegations := d.Get("delegate_to").(*schema.Set).List() + oldExtAttrsJSON, newExtAttrsJSON := d.GetChange("ext_attrs") + + newExtAttrs, err := terraformDeserializeEAs(newExtAttrsJSON.(string)) + if err != nil { + return err + } - nameServers, computedDelegations, err := computeDelegations(delegations) + oldExtAttrs, err := terraformDeserializeEAs(oldExtAttrsJSON.(string)) if err != nil { return err } var tenantID string - if tempVal, ok := extAttrs["Tenant ID"]; ok { + if tempVal, found := newExtAttrs[eaNameForTenantId]; found { tenantID = tempVal.(string) } - connector := m.(*ibclient.Connector) - + connector := m.(ibclient.IBConnector) objMgr := ibclient.NewObjectManager(connector, "Terraform", tenantID) - zoneDelegatedUpdated, err := objMgr.UpdateZoneDelegated(d.Id(), nameServers) + var zoneDelegated *ibclient.ZoneDelegated + + rec, err := searchObjectByRefOrInternalId("ZoneDelegated", d, m) + if err != nil { + if _, ok := err.(*ibclient.NotFoundError); !ok { + return ibclient.NewNotFoundError(fmt.Sprintf( + "cannot find appropriate object on NIOS side for resource with ID '%s': %s;", d.Id(), err)) + } else { + d.SetId("") + return nil + } + } + recJson, err := json.Marshal(rec) + if err != nil { + return fmt.Errorf("failed to marshal zone delegated record : %s", err.Error()) + } + err = json.Unmarshal(recJson, &zoneDelegated) if err != nil { - return fmt.Errorf("Updating of Zone Delegated failed : %w", err) + return fmt.Errorf("failed getting zone delegated record : %s", err.Error()) + } + + // If 'internal_id' is not set, then generate a new one and set it to the EA. + internalId := d.Get("internal_id").(string) + if internalId == "" { + internalId = generateInternalId().String() } + newInternalId := newInternalResourceIdFromString(internalId) + newExtAttrs[eaNameForInternalId] = newInternalId.String() - d.Set("delegate_to", computedDelegations) + newExtAttrs, err = mergeEAs(zoneDelegated.Ea, newExtAttrs, oldExtAttrs, connector) + if err != nil { + return err + } + + comment := d.Get("comment").(string) + disable := d.Get("disable").(bool) + locked := d.Get("locked").(bool) + delegatedTtl := d.Get("delegated_ttl") + var nsGroup string + if d.Get("ns_group") != "" { + nsGroup = d.Get("ns_group").(string) + } else { + nsGroup = "" + } + var ttl uint32 + useTtl := false + tempTTL := delegatedTtl.(int) + if tempTTL >= 0 { + useTtl = true + ttl = uint32(tempTTL) + } else if tempTTL != ttlUndef { + return fmt.Errorf("TTL value must be 0 or higher") + } + + zoneDelegated, err = objMgr.UpdateZoneDelegated(d.Id(), nullDT, comment, disable, locked, nsGroup, ttl, useTtl, newExtAttrs) + if err != nil { + return fmt.Errorf("Failed to update zone delegated with %s, ", err.Error()) + } + + updateSuccessful = true + + if err = d.Set("internal_id", newInternalId.String()); err != nil { + return err + } + if err = d.Set("ref", zoneDelegated.Ref); err != nil { + return err + } + d.SetId(zoneDelegated.Ref) - d.SetId(zoneDelegatedUpdated.Ref) return nil } func resourceZoneDelegatedDelete(d *schema.ResourceData, m interface{}) error { - log.Printf("[DEBUG] %s: Beginning Deletion of Zone Delegated", resourceZoneDelegatedIDString(d)) - extAttrJSON := d.Get("ext_attrs").(string) - extAttrs := make(map[string]interface{}) - if extAttrJSON != "" { - if err := json.Unmarshal([]byte(extAttrJSON), &extAttrs); err != nil { - return fmt.Errorf("cannot process 'ext_attrs' field: %w", err) - } + extAttrs, err := terraformDeserializeEAs(extAttrJSON) + if err != nil { + return err } var tenantID string - if tempVal, ok := extAttrs["Tenant ID"]; ok { + tempVal, found := extAttrs[eaNameForTenantId] + if found { tenantID = tempVal.(string) } - connector := m.(*ibclient.Connector) - + connector := m.(ibclient.IBConnector) objMgr := ibclient.NewObjectManager(connector, "Terraform", tenantID) - _, err := objMgr.DeleteZoneDelegated(d.Id()) + rec, err := searchObjectByRefOrInternalId("ZoneDelegated", d, m) + if err != nil { + if _, ok := err.(*ibclient.NotFoundError); !ok { + return ibclient.NewNotFoundError(fmt.Sprintf( + "cannot find appropriate object on NIOS side for resource with ID '%s': %s;", d.Id(), err)) + } else { + d.SetId("") + return nil + } + } + + var zd *ibclient.ZoneDelegated + recJson, err := json.Marshal(rec) if err != nil { - return fmt.Errorf("Deletion of Zone Delegated failed : %w", err) + return fmt.Errorf("failed to marshal zone delegated record : %s", err.Error()) + } + err = json.Unmarshal(recJson, &zd) + if err != nil { + return err + } + _, err = objMgr.DeleteZoneDelegated(zd.Ref) + if err != nil { + return fmt.Errorf("failed to delete zone delegated : %s", err.Error()) } - d.SetId("") - log.Printf("[DEBUG] %s: Deletion of Zone Delegated complete", resourceZoneDelegatedIDString(d)) return nil } -type resourceZoneDelegatedIDStringInterface interface { - Id() string -} +func resourceZoneDelegatedImport(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + extAttrJSON := d.Get("ext_attrs").(string) + _, err := terraformDeserializeEAs(extAttrJSON) + if err != nil { + return nil, err + } + + connector := m.(ibclient.IBConnector) + objMgr := ibclient.NewObjectManager(connector, "Terraform", "") + + zoneDelegated, err := objMgr.GetZoneDelegatedByRef(d.Id()) + if err != nil { + return nil, fmt.Errorf("failed getting zone delegated record: %w", err) + } + + if zoneDelegated.Ea != nil && len(zoneDelegated.Ea) > 0 { + eaJSON, err := terraformSerializeEAs(zoneDelegated.Ea) + if err != nil { + return nil, err + } + if err = d.Set("ext_attrs", eaJSON); err != nil { + return nil, err + } + } + delete(zoneDelegated.Ea, eaNameForInternalId) -func resourceZoneDelegatedIDString(d resourceZoneDelegatedIDStringInterface) string { - id := d.Id() - if id == "" { - id = "" + if err = d.Set("fqdn", zoneDelegated.Fqdn); err != nil { + return nil, err + } + + if zoneDelegated.View != nil { + if err = d.Set("view", *zoneDelegated.View); err != nil { + return nil, err + } + } + + if zoneDelegated.NsGroup != nil { + if err = d.Set("ns_group", *zoneDelegated.NsGroup); err != nil { + return nil, err + } + } + + if err = d.Set("zone_format", zoneDelegated.ZoneFormat); err != nil { + return nil, err + } + + if zoneDelegated.Comment != nil { + if err = d.Set("comment", *zoneDelegated.Comment); err != nil { + return nil, err + } + } + + if zoneDelegated.Disable != nil { + if err = d.Set("disable", *zoneDelegated.Disable); err != nil { + return nil, err + } + } + + if zoneDelegated.Locked != nil { + if err = d.Set("locked", *zoneDelegated.Locked); err != nil { + return nil, err + } + } + + if zoneDelegated.DelegatedTtl != nil { + if err = d.Set("delegated_ttl", *zoneDelegated.DelegatedTtl); err != nil { + return nil, err + } + } + + if zoneDelegated.DelegateTo.ForwardTo != nil { + nsInterface := convertForwardToInterface(zoneDelegated.DelegateTo) + if err = d.Set("delegate_to", nsInterface); err != nil { + return nil, err + } + } else { + if err := d.Set("delegate_to", nil); err != nil { + return nil, err + } + } + + d.SetId(zoneDelegated.Ref) + + // Update the resource with the EA Terraform Internal ID + err = resourceZoneDelegatedUpdate(d, m) + if err != nil { + return nil, err } - return fmt.Sprintf("infoblox_zone_delegated (ID = %s)", id) + return []*schema.ResourceData{d}, nil } diff --git a/infoblox/resource_infoblox_zone_forward.go b/infoblox/resource_infoblox_zone_forward.go index 7e0e837ad..8e38ed3a2 100644 --- a/infoblox/resource_infoblox_zone_forward.go +++ b/infoblox/resource_infoblox_zone_forward.go @@ -261,12 +261,12 @@ func validateForwardingServers(fsSlice []interface{}) ([]*ibclient.Forwardingmem func validateForwardTo(ftSlice []interface{}) ([]ibclient.NameServer, error) { nsStr, err := json.Marshal(ftSlice) if err != nil { - return nil, fmt.Errorf("failed to marshal forward_to: %s", err) + return nil, fmt.Errorf("failed to marshal nameservers: %s", err) } var forwardTo []ibclient.NameServer err = json.Unmarshal(nsStr, &forwardTo) if err != nil { - return nil, fmt.Errorf("failed to unmarshal forward_to: %s", err) + return nil, fmt.Errorf("failed to unmarshal nameservers: %s", err) } return forwardTo, nil } From f5f61958f611d308115ac113208ceb1253ac2b78 Mon Sep 17 00:00:00 2001 From: apattar Date: Tue, 27 Aug 2024 12:07:35 +0530 Subject: [PATCH 08/13] adding acceptance testcase for zone-delegated object for resource and datasource --- ...datasource_infoblox_zone_delegated_test.go | 130 +++++++++++++ .../resource_infoblox_zone_delegated_test.go | 183 +++++++++++++----- 2 files changed, 267 insertions(+), 46 deletions(-) create mode 100644 infoblox/datasource_infoblox_zone_delegated_test.go diff --git a/infoblox/datasource_infoblox_zone_delegated_test.go b/infoblox/datasource_infoblox_zone_delegated_test.go new file mode 100644 index 000000000..67c208b11 --- /dev/null +++ b/infoblox/datasource_infoblox_zone_delegated_test.go @@ -0,0 +1,130 @@ +package infoblox + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "testing" +) + +var testDataSourceZoneDelegated = fmt.Sprintf( + + `resource "infoblox_zone_auth" "zone11" { + fqdn = "test33.com" + view = "default" + zone_format = "FORWARD" + ns_group = "" + restart_if_needed = true + soa_default_ttl = 36000 + soa_expire = 72000 + soa_negative_ttl = 600 + soa_refresh = 1800 + soa_retry = 900 + comment = "Zone Auth created newly" + ext_attrs = jsonencode({ + Location = "AcceptanceTerraform" + }) +} + +resource "infoblox_zone_delegated" "zone_delegated_data_src" { + fqdn = "test_fz_ds.test33.com" + comment = "test sample delegate zone" + delegate_to { + name = "test_ds_123.dz.ex.com" + address = "10.0.0.1" + } + delegate_to { + name = "test_ds_245.dz.ex.com" + address = "10.0.0.2" + } + depends_on = [infoblox_zone_auth.zone11] +} +data "infoblox_zone_delegated" "zone_delegated_data_src_read" { + filters = { + fqdn = infoblox_zone_delegated.zone_delegated_data_src.fqdn + } + depends_on = [infoblox_zone_delegated.zone_delegated_data_src] +}`) + +var testDataSourceZoneDelegatedEA = fmt.Sprintf( + `resource "infoblox_zone_auth" "zone12" { +fqdn = "test313.com" +view = "default" +zone_format = "FORWARD" +ns_group = "" +restart_if_needed = true +soa_default_ttl = 36000 +soa_expire = 72000 +soa_negative_ttl = 600 +soa_refresh = 1800 +soa_retry = 900 +comment = "Zone Auth created newly" +ext_attrs = jsonencode({ +Location = "AcceptanceTerraform" +}) +} + +resource "infoblox_zone_delegated" "zone_delegated_data_src_ea" { + fqdn = "test2.test313.com" + comment = "test sample delegate zone with EA" + delegate_to { + name = "test_ds_123.ea.ex.com" + address = "10.0.0.1" + } + delegate_to { + name = "test_ds_245.ea.ex.com" + address = "10.0.0.2" + } + ext_attrs = jsonencode({ + "Location" = "TBD" + }) + depends_on = [infoblox_zone_auth.zone12] +} + +data "infoblox_zone_delegated" "zone_delegated_data_src_ea_read" { + filters = { + "*Location" = "TBD" + } + depends_on = [infoblox_zone_delegated.zone_delegated_data_src_ea] +}`) + +func TestAccDataSourceZoneDelegated(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckZoneDelegatedDestroy, + Steps: []resource.TestStep{ + { + Config: testDataSourceZoneDelegated, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_read", "results.#", "1"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_read", "results.0.fqdn", "test_fz_ds.test33.com"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_read", "results.0.comment", "test sample delegate zone"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_read", "results.0.delegate_to.0.name", "test_ds_123.dz.ex.com"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_read", "results.0.delegate_to.0.address", "10.0.0.1"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_read", "results.0.delegate_to.1.name", "test_ds_245.dz.ex.com"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_read", "results.0.delegate_to.1.address", "10.0.0.2"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_read", "results.0.view", "default"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_read", "results.0.zone_format", "FORWARD"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_read", "results.0.ns_group", ""), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_read", "results.0.disable", "false"), + ), + }, + { + Config: testDataSourceZoneDelegatedEA, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_ea_read", "results.#", "1"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_ea_read", "results.0.fqdn", "test2.test313.com"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_ea_read", "results.0.comment", "test sample delegate zone with EA"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_ea_read", "results.0.delegate_to.0.name", "test_ds_123.ea.ex.com"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_ea_read", "results.0.delegate_to.0.address", "10.0.0.1"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_ea_read", "results.0.delegate_to.1.name", "test_ds_245.ea.ex.com"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_ea_read", "results.0.delegate_to.1.address", "10.0.0.2"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_ea_read", "results.0.view", "default"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_ea_read", "results.0.zone_format", "FORWARD"), + resource.TestCheckResourceAttr("data.infoblox_zone_delegated.zone_delegated_data_src_ea_read", "results.0.disable", "false"), + resource.TestCheckResourceAttrPair("data.infoblox_zone_delegated.zone_delegated_data_src_ea_read", "results.0.ext_attrs.Location", "infoblox_zone_delegated.zone_delegated_data_src_ea", "ext_attrs.Location"), + ), + }, + }, + }) +} diff --git a/infoblox/resource_infoblox_zone_delegated_test.go b/infoblox/resource_infoblox_zone_delegated_test.go index 3be553eb5..2ed177ae1 100644 --- a/infoblox/resource_infoblox_zone_delegated_test.go +++ b/infoblox/resource_infoblox_zone_delegated_test.go @@ -1,9 +1,10 @@ package infoblox import ( + "encoding/json" "fmt" - "net" - "sort" + "github.com/infobloxopen/infoblox-go-client/v2/utils" + "reflect" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -11,69 +12,162 @@ import ( ibclient "github.com/infobloxopen/infoblox-go-client/v2" ) +var testResourceZoneDelegatedRecord = `resource "infoblox_zone_auth" "zone1" { + fqdn = "test3.com" + view = "default" + zone_format = "FORWARD" + ns_group = "" + restart_if_needed = true + soa_default_ttl = 36000 + soa_expire = 72000 + soa_negative_ttl = 600 + soa_refresh = 1800 + soa_retry = 900 + comment = "Zone Auth created newly" + ext_attrs = jsonencode({ + Location = "AcceptanceTerraform" + }) +} + +resource "infoblox_zone_delegated" "testzd1" { + fqdn = "test_zd.test3.com" + delegate_to { + name = "ns2.infoblox.com" + address = "10.0.0.1" + } + depends_on = [infoblox_zone_auth.zone1] +}` + func testAccCheckZoneDelegatedDestroy(s *terraform.State) error { meta := testAccProvider.Meta() for _, rs := range s.RootModule().Resources { if rs.Type != "infoblox_zone_delegated" { - return fmt.Errorf("Resource type %s is invalid after destroy", rs.Type) + continue } connector := meta.(ibclient.IBConnector) objMgr := ibclient.NewObjectManager(connector, "terraform_test", "test") rec, _ := objMgr.GetZoneDelegatedByRef(rs.Primary.ID) if rec != nil { - return fmt.Errorf("Zone Delegation record found after destroy") + return fmt.Errorf("zone delegation record found after destroy") } } return nil } -func testAccZoneDelegatedCompare(t *testing.T, resPath string, expectedRec *ibclient.RecordNS) resource.TestCheckFunc { +func testAccZoneDelegatedCompare(t *testing.T, resPath string, expectedRec *ibclient.ZoneDelegated) resource.TestCheckFunc { return func(s *terraform.State) error { res, found := s.RootModule().Resources[resPath] if !found { - return fmt.Errorf("Not found: %s", resPath) + return fmt.Errorf("not found: %s", resPath) } if res.Primary.ID == "" { return fmt.Errorf("ID is not set") } - meta := testAccProvider.Meta() - connector := meta.(ibclient.IBConnector) - objMgr := ibclient.NewObjectManager(connector, "terraform_test", "test") - lookupHosts, err := net.LookupHost(*expectedRec.Nameserver) + internalId := res.Primary.Attributes["internal_id"] + if internalId == "" { + return fmt.Errorf("ID is not set") + } + + ref, found := res.Primary.Attributes["ref"] + if !found { + return fmt.Errorf("'ref' attribute is not set") + } + + connector := testAccProvider.Meta().(ibclient.IBConnector) + objMgr := ibclient.NewObjectManager( + connector, + "terraform_test", + "test") + zd, err := objMgr.SearchObjectByAltId("ZoneDelegated", ref, internalId, eaNameForInternalId) if err != nil { - return fmt.Errorf("Failed to resolve delegate_to: %s", err.Error()) + if isNotFoundError(err) { + if expectedRec == nil { + return nil + } + return fmt.Errorf("object with Terraform ID '%s' not found, but expected to exist", internalId) + } } - sort.Strings(lookupHosts) - expectedRec.Addresses = append(expectedRec.Addresses, &ibclient.ZoneNameServer{Address: lookupHosts[0]}) - rec, _ := objMgr.GetZoneDelegatedByRef(res.Primary.ID) - if rec == nil { - return fmt.Errorf("record not found") + // Assertion of object type and error handling + var rec *ibclient.ZoneDelegated + recJson, _ := json.Marshal(zd) + err = json.Unmarshal(recJson, &rec) + + if zd == nil { + return fmt.Errorf("zone delegated record not found") } - if rec.Fqdn != expectedRec.Name { - return fmt.Errorf( - "'fqdn' does not match: got '%s', expected '%s'", - rec.Fqdn, expectedRec.Name) + if expectedRec == nil { + return fmt.Errorf("expected record is nil") } - if rec.DelegateTo[0].Address != expectedRec.Addresses[0].Address { + + if rec.Fqdn != expectedRec.Fqdn { return fmt.Errorf( - "'delegate_to['address']' does not match: got '%s', expected '%s'", - rec.DelegateTo[0].Address, expectedRec.Addresses[0].Address) + "the value of 'fqdn' field is '%s', but expected '%s'", + rec.Fqdn, expectedRec.Fqdn) } - if rec.DelegateTo[0].Name != *expectedRec.Nameserver { + if rec.View != nil && expectedRec.View != nil { + if *rec.View != *expectedRec.View { + return fmt.Errorf( + "the value of 'view' field is '%s', but expected '%s'", + *rec.View, *expectedRec.View) + } + } + + if rec.ZoneFormat != expectedRec.ZoneFormat { return fmt.Errorf( - "'delegate_to['name']' does not match: got '%s', expected '%s'", - rec.DelegateTo[0].Name, *expectedRec.Nameserver) + "the value of 'zone_format' field is '%s', but expected '%s'", + rec.ZoneFormat, expectedRec.ZoneFormat) } - return nil - } -} + if rec.Comment != nil && expectedRec.Comment != nil { + if *rec.Comment != *expectedRec.Comment { + return fmt.Errorf( + "the value of 'comment' field is '%s', but expected '%s'", + *rec.Comment, *expectedRec.Comment) + } + } + if rec.Disable != nil && expectedRec.Disable != nil { + if *rec.Disable != *expectedRec.Disable { + return fmt.Errorf( + "the value of 'disable' field is '%t', but expected '%t'", + *rec.Disable, *expectedRec.Disable) + } + } + if rec.Locked != nil && expectedRec.Locked != nil { + if *rec.Locked != *expectedRec.Locked { + return fmt.Errorf( + "the value of 'locked' field is '%t', but expected '%t'", + *rec.Locked, *expectedRec.Locked) + } + } + if rec.DelegatedTtl != nil && expectedRec.DelegatedTtl != nil { + if *rec.DelegatedTtl != *expectedRec.DelegatedTtl { + return fmt.Errorf( + "the value of 'delegated_ttl' field is '%d', but expected '%d'", + *rec.DelegatedTtl, *expectedRec.DelegatedTtl) + } + } + + if rec.NsGroup != nil && expectedRec.NsGroup != nil { + if *rec.NsGroup != *expectedRec.NsGroup { + return fmt.Errorf( + "the value of 'ns_group' field is '%s', but expected '%s'", + *rec.NsGroup, *expectedRec.NsGroup) + } + } + if rec.DelegateTo.ForwardTo != nil && expectedRec.DelegateTo.ForwardTo != nil { + if !reflect.DeepEqual(rec.DelegateTo, expectedRec.DelegateTo) { + return fmt.Errorf( + "the value of 'delegate_to' field is '%v', but expected '%v'", + rec.DelegateTo, expectedRec.DelegateTo) + } + } + + return validateEAs(rec.Ea, expectedRec.Ea) -func strPtr(s string) *string { - return &s + } } func TestAccResourceZoneDelegated(t *testing.T) { @@ -84,20 +178,17 @@ func TestAccResourceZoneDelegated(t *testing.T) { CheckDestroy: testAccCheckZoneDelegatedDestroy, Steps: []resource.TestStep{ { - Config: fmt.Sprintf(` - resource "infoblox_zone_delegated" "foo"{ - fqdn="subdomain.test.com" - delegate_to { - name = "ns2.infoblox.com" - } - }`), - Check: resource.ComposeTestCheckFunc( - testAccZoneDelegatedCompare(t, "infoblox_zone_delegated.foo", &ibclient.RecordNS{ - Name: "subdomain.test.com", - Nameserver: strPtr("ns2.infoblox.com"), - }), - ), - }, - }, + Config: testResourceZoneDelegatedRecord, + Check: testAccZoneDelegatedCompare(t, "infoblox_zone_delegated.testzd1", &ibclient.ZoneDelegated{ + Fqdn: "test_zd.test3.com", + DelegateTo: ibclient.NullForwardTo{ + IsNull: false, + ForwardTo: []ibclient.NameServer{ + {Name: "ns2.infoblox.com", Address: "10.0.0.1"}, + }}, + ZoneFormat: "FORWARD", + View: utils.StringPtr("default"), + }), + }}, }) } From 36d74a1975c9f5cc9d2d91048ec7a69797c05d92 Mon Sep 17 00:00:00 2001 From: apattar Date: Wed, 28 Aug 2024 12:50:17 +0530 Subject: [PATCH 09/13] changing name of NullForwardTo struct to NullableServers for zone-forward and zone-delegated objects. --- .../datasource_infoblox_zone_delegated.go | 2 +- infoblox/datasource_infoblox_zone_forward.go | 11 +++++----- infoblox/resource_infoblox_zone_delegated.go | 18 ++++++++--------- .../resource_infoblox_zone_delegated_test.go | 6 +++--- infoblox/resource_infoblox_zone_forward.go | 20 +++++++++---------- .../resource_infoblox_zone_forward_test.go | 6 +++--- 6 files changed, 32 insertions(+), 31 deletions(-) diff --git a/infoblox/datasource_infoblox_zone_delegated.go b/infoblox/datasource_infoblox_zone_delegated.go index 6a047127a..f6d6772c4 100644 --- a/infoblox/datasource_infoblox_zone_delegated.go +++ b/infoblox/datasource_infoblox_zone_delegated.go @@ -184,7 +184,7 @@ func flattenZoneDelegated(zoneDelegated ibclient.ZoneDelegated) (map[string]inte res["delegated_ttl"] = *zoneDelegated.DelegatedTtl } if zoneDelegated.DelegateTo.IsNull == false { - nsInterface := convertForwardToInterface(zoneDelegated.DelegateTo) + nsInterface := convertNullableNameServersToInterface(zoneDelegated.DelegateTo) res["delegate_to"] = nsInterface } diff --git a/infoblox/datasource_infoblox_zone_forward.go b/infoblox/datasource_infoblox_zone_forward.go index 09b13df20..cb0f59624 100644 --- a/infoblox/datasource_infoblox_zone_forward.go +++ b/infoblox/datasource_infoblox_zone_forward.go @@ -229,7 +229,7 @@ func flattenZoneForward(zf ibclient.ZoneForward) (map[string]interface{}, error) } if zf.ForwardTo.IsNull == false { - nsInterface := convertForwardToInterface(zf.ForwardTo) + nsInterface := convertNullableNameServersToInterface(zf.ForwardTo) res["forward_to"] = nsInterface } @@ -251,16 +251,17 @@ func convertForwardingServersToInterface(zf []*ibclient.Forwardingmemberserver) sMap["forwarders_only"] = fs.ForwardersOnly sMap["use_override_forwarders"] = fs.UseOverrideForwarders if fs.ForwardTo.IsNull == false { - nsInterface := convertForwardToInterface(fs.ForwardTo) + nsInterface := convertNullableNameServersToInterface(fs.ForwardTo) sMap["forward_to"] = nsInterface } fwServers = append(fwServers, sMap) } return fwServers, nil } -func convertForwardToInterface(nameServers ibclient.NullForwardTo) []map[string]interface{} { - nsInterface := make([]map[string]interface{}, 0, len(nameServers.ForwardTo)) - for _, ns := range nameServers.ForwardTo { + +func convertNullableNameServersToInterface(nameServers ibclient.NullableNameServers) []map[string]interface{} { + nsInterface := make([]map[string]interface{}, 0, len(nameServers.NameServers)) + for _, ns := range nameServers.NameServers { nsMap := make(map[string]interface{}) nsMap["address"] = ns.Address nsMap["name"] = ns.Name diff --git a/infoblox/resource_infoblox_zone_delegated.go b/infoblox/resource_infoblox_zone_delegated.go index a971c70f1..c6314fc79 100644 --- a/infoblox/resource_infoblox_zone_delegated.go +++ b/infoblox/resource_infoblox_zone_delegated.go @@ -115,7 +115,7 @@ func resourceZoneDelegatedCreate(d *schema.ResourceData, m interface{}) error { dtInterface, delegateToOk := d.GetOk("delegate_to") var delegateTo []ibclient.NameServer - var nullDT ibclient.NullForwardTo + var nullDT ibclient.NullableNameServers if !nsGroupOk && !delegateToOk { return fmt.Errorf("either 'ns_group' or 'delegate_to' must be set") } @@ -126,7 +126,7 @@ func resourceZoneDelegatedCreate(d *schema.ResourceData, m interface{}) error { if err != nil { return err } - nullDT = ibclient.NullForwardTo{IsNull: false, ForwardTo: delegateTo} + nullDT = ibclient.NullableNameServers{IsNull: false, NameServers: delegateTo} } comment := d.Get("comment").(string) @@ -269,8 +269,8 @@ func resourceZoneDelegatedRead(d *schema.ResourceData, m interface{}) error { } } - if zoneDelegated.DelegateTo.ForwardTo != nil { - nsInterface := convertForwardToInterface(zoneDelegated.DelegateTo) + if zoneDelegated.DelegateTo.NameServers != nil { + nsInterface := convertNullableNameServersToInterface(zoneDelegated.DelegateTo) if err = d.Set("delegate_to", nsInterface); err != nil { return err } @@ -331,11 +331,11 @@ func resourceZoneDelegatedUpdate(d *schema.ResourceData, m interface{}) error { } var delegateTo []ibclient.NameServer - var nullDT ibclient.NullForwardTo + var nullDT ibclient.NullableNameServers if !nsGroupOk && !delegateToOk { return fmt.Errorf("either ns_group or delegate_to must be set") } else if !delegateToOk { - nullDT = ibclient.NullForwardTo{IsNull: false, ForwardTo: []ibclient.NameServer{}} + nullDT = ibclient.NullableNameServers{IsNull: false, NameServers: []ibclient.NameServer{}} } else { dtSlice := dtInterface.(*schema.Set).List() var err error @@ -343,7 +343,7 @@ func resourceZoneDelegatedUpdate(d *schema.ResourceData, m interface{}) error { if err != nil { return err } - nullDT = ibclient.NullForwardTo{IsNull: false, ForwardTo: delegateTo} + nullDT = ibclient.NullableNameServers{IsNull: false, NameServers: delegateTo} } oldExtAttrsJSON, newExtAttrsJSON := d.GetChange("ext_attrs") @@ -552,8 +552,8 @@ func resourceZoneDelegatedImport(d *schema.ResourceData, m interface{}) ([]*sche } } - if zoneDelegated.DelegateTo.ForwardTo != nil { - nsInterface := convertForwardToInterface(zoneDelegated.DelegateTo) + if zoneDelegated.DelegateTo.NameServers != nil { + nsInterface := convertNullableNameServersToInterface(zoneDelegated.DelegateTo) if err = d.Set("delegate_to", nsInterface); err != nil { return nil, err } diff --git a/infoblox/resource_infoblox_zone_delegated_test.go b/infoblox/resource_infoblox_zone_delegated_test.go index 2ed177ae1..fc10d55f5 100644 --- a/infoblox/resource_infoblox_zone_delegated_test.go +++ b/infoblox/resource_infoblox_zone_delegated_test.go @@ -157,7 +157,7 @@ func testAccZoneDelegatedCompare(t *testing.T, resPath string, expectedRec *ibcl *rec.NsGroup, *expectedRec.NsGroup) } } - if rec.DelegateTo.ForwardTo != nil && expectedRec.DelegateTo.ForwardTo != nil { + if rec.DelegateTo.NameServers != nil && expectedRec.DelegateTo.NameServers != nil { if !reflect.DeepEqual(rec.DelegateTo, expectedRec.DelegateTo) { return fmt.Errorf( "the value of 'delegate_to' field is '%v', but expected '%v'", @@ -181,9 +181,9 @@ func TestAccResourceZoneDelegated(t *testing.T) { Config: testResourceZoneDelegatedRecord, Check: testAccZoneDelegatedCompare(t, "infoblox_zone_delegated.testzd1", &ibclient.ZoneDelegated{ Fqdn: "test_zd.test3.com", - DelegateTo: ibclient.NullForwardTo{ + DelegateTo: ibclient.NullableNameServers{ IsNull: false, - ForwardTo: []ibclient.NameServer{ + NameServers: []ibclient.NameServer{ {Name: "ns2.infoblox.com", Address: "10.0.0.1"}, }}, ZoneFormat: "FORWARD", diff --git a/infoblox/resource_infoblox_zone_forward.go b/infoblox/resource_infoblox_zone_forward.go index 8e38ed3a2..362c4bf61 100644 --- a/infoblox/resource_infoblox_zone_forward.go +++ b/infoblox/resource_infoblox_zone_forward.go @@ -169,11 +169,11 @@ func resourceZoneForwardCreate(d *schema.ResourceData, m interface{}) error { ftInterface, forwardToOk := d.GetOk("forward_to") var forwardTo []ibclient.NameServer - var nullFWT ibclient.NullForwardTo + var nullFWT ibclient.NullableNameServers if !externalNsGroupOk && !forwardToOk { return fmt.Errorf("either external_ns_group or forward_to must be set") } else if !forwardToOk { - nullFWT = ibclient.NullForwardTo{IsNull: false, ForwardTo: []ibclient.NameServer{}} + nullFWT = ibclient.NullableNameServers{IsNull: false, NameServers: []ibclient.NameServer{}} } else { ftSlice, ok := ftInterface.([]interface{}) if !ok { @@ -184,7 +184,7 @@ func resourceZoneForwardCreate(d *schema.ResourceData, m interface{}) error { if err != nil { return err } - nullFWT = ibclient.NullForwardTo{IsNull: false, ForwardTo: forwardTo} + nullFWT = ibclient.NullableNameServers{IsNull: false, NameServers: forwardTo} } fqdn := d.Get("fqdn").(string) @@ -357,8 +357,8 @@ func resourceZoneForwardRead(d *schema.ResourceData, m interface{}) error { } } - if zoneForward.ForwardTo.ForwardTo != nil { - nsInterface := convertForwardToInterface(zoneForward.ForwardTo) + if zoneForward.ForwardTo.NameServers != nil { + nsInterface := convertNullableNameServersToInterface(zoneForward.ForwardTo) if err = d.Set("forward_to", nsInterface); err != nil { return err } @@ -427,11 +427,11 @@ func resourceZoneForwardUpdate(d *schema.ResourceData, m interface{}) error { } var forwardTo []ibclient.NameServer - var nullFWT ibclient.NullForwardTo + var nullFWT ibclient.NullableNameServers if !externalNsGroupOk && !forwardToOk { return fmt.Errorf("either external_ns_group or forward_to must be set") } else if !forwardToOk { - nullFWT = ibclient.NullForwardTo{IsNull: false, ForwardTo: []ibclient.NameServer{}} + nullFWT = ibclient.NullableNameServers{IsNull: false, NameServers: []ibclient.NameServer{}} } else { ftSlice, ok := ftInterface.([]interface{}) if !ok { @@ -442,7 +442,7 @@ func resourceZoneForwardUpdate(d *schema.ResourceData, m interface{}) error { if err != nil { return err } - nullFWT = ibclient.NullForwardTo{IsNull: false, ForwardTo: forwardTo} + nullFWT = ibclient.NullableNameServers{IsNull: false, NameServers: forwardTo} } oldExtAttrsJSON, newExtAttrsJSON := d.GetChange("ext_attrs") @@ -661,8 +661,8 @@ func resourceZoneForwardImport(d *schema.ResourceData, m interface{}) ([]*schema } } - if zf.ForwardTo.ForwardTo != nil { - nsInterface := convertForwardToInterface(zf.ForwardTo) + if zf.ForwardTo.NameServers != nil { + nsInterface := convertNullableNameServersToInterface(zf.ForwardTo) if err = d.Set("forward_to", nsInterface); err != nil { return nil, err } diff --git a/infoblox/resource_infoblox_zone_forward_test.go b/infoblox/resource_infoblox_zone_forward_test.go index 847705764..4b1576a5d 100644 --- a/infoblox/resource_infoblox_zone_forward_test.go +++ b/infoblox/resource_infoblox_zone_forward_test.go @@ -140,7 +140,7 @@ func testForwardZoneCompare(t *testing.T, resourceName string, expectedZF *ibcli *rec.NsGroup, *expectedZF.NsGroup) } } - if rec.ForwardTo.ForwardTo != nil && expectedZF.ForwardTo.ForwardTo != nil { + if rec.ForwardTo.NameServers != nil && expectedZF.ForwardTo.NameServers != nil { if !reflect.DeepEqual(rec.ForwardTo, expectedZF.ForwardTo) { return fmt.Errorf( "the value of 'forward_to' field is '%v', but expected '%v'", @@ -171,9 +171,9 @@ func TestAccResourceZoneForward(t *testing.T) { View: utils.StringPtr("default"), ZoneFormat: "FORWARD", Comment: utils.StringPtr("test sample forward zone"), - ForwardTo: ibclient.NullForwardTo{ + ForwardTo: ibclient.NullableNameServers{ IsNull: false, - ForwardTo: []ibclient.NameServer{ + NameServers: []ibclient.NameServer{ {Name: "test123.dz.ex.com", Address: "10.0.0.1"}, {Name: "test245.dz.ex.com", Address: "10.0.0.2"}, }}, From 3aa287a0db33778bd3119feaa2203e0e558eb953 Mon Sep 17 00:00:00 2001 From: apattar Date: Thu, 29 Aug 2024 13:35:56 +0530 Subject: [PATCH 10/13] updating function name from validateForwardTo to validateNameServers --- infoblox/resource_infoblox_zone_delegated.go | 4 ++-- infoblox/resource_infoblox_zone_forward.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infoblox/resource_infoblox_zone_delegated.go b/infoblox/resource_infoblox_zone_delegated.go index c6314fc79..983e0f8df 100644 --- a/infoblox/resource_infoblox_zone_delegated.go +++ b/infoblox/resource_infoblox_zone_delegated.go @@ -122,7 +122,7 @@ func resourceZoneDelegatedCreate(d *schema.ResourceData, m interface{}) error { if delegateToOk { dtSlice := dtInterface.(*schema.Set).List() var err error - delegateTo, err = validateForwardTo(dtSlice) + delegateTo, err = validateNameServers(dtSlice) if err != nil { return err } @@ -339,7 +339,7 @@ func resourceZoneDelegatedUpdate(d *schema.ResourceData, m interface{}) error { } else { dtSlice := dtInterface.(*schema.Set).List() var err error - delegateTo, err = validateForwardTo(dtSlice) + delegateTo, err = validateNameServers(dtSlice) if err != nil { return err } diff --git a/infoblox/resource_infoblox_zone_forward.go b/infoblox/resource_infoblox_zone_forward.go index 362c4bf61..64fd4150f 100644 --- a/infoblox/resource_infoblox_zone_forward.go +++ b/infoblox/resource_infoblox_zone_forward.go @@ -180,7 +180,7 @@ func resourceZoneForwardCreate(d *schema.ResourceData, m interface{}) error { return fmt.Errorf("forward_to is not a slice of Nameservers") } var err error - forwardTo, err = validateForwardTo(ftSlice) + forwardTo, err = validateNameServers(ftSlice) if err != nil { return err } @@ -258,7 +258,7 @@ func validateForwardingServers(fsSlice []interface{}) ([]*ibclient.Forwardingmem return forwardingServer, nil } -func validateForwardTo(ftSlice []interface{}) ([]ibclient.NameServer, error) { +func validateNameServers(ftSlice []interface{}) ([]ibclient.NameServer, error) { nsStr, err := json.Marshal(ftSlice) if err != nil { return nil, fmt.Errorf("failed to marshal nameservers: %s", err) @@ -438,7 +438,7 @@ func resourceZoneForwardUpdate(d *schema.ResourceData, m interface{}) error { return fmt.Errorf("forward_to is not a slice of Nameservers") } var err error - forwardTo, err = validateForwardTo(ftSlice) + forwardTo, err = validateNameServers(ftSlice) if err != nil { return err } From 241dd713db39472c95b273c3ebac664f6462403d Mon Sep 17 00:00:00 2001 From: apattar Date: Thu, 19 Sep 2024 11:49:23 +0530 Subject: [PATCH 11/13] Fix for NPA-166: inconsistent 'delegated_ttl' value for zone_delegated resource and datasource --- infoblox/datasource_infoblox_zone_delegated.go | 11 ++++++++++- infoblox/resource_infoblox_zone_delegated.go | 12 +++++++++++- infoblox/resource_infoblox_zone_forward.go | 6 +++--- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/infoblox/datasource_infoblox_zone_delegated.go b/infoblox/datasource_infoblox_zone_delegated.go index f6d6772c4..bd9939be0 100644 --- a/infoblox/datasource_infoblox_zone_delegated.go +++ b/infoblox/datasource_infoblox_zone_delegated.go @@ -180,9 +180,18 @@ func flattenZoneDelegated(zoneDelegated ibclient.ZoneDelegated) (map[string]inte if zoneDelegated.NsGroup != nil { res["ns_group"] = *zoneDelegated.NsGroup } - if zoneDelegated.DelegatedTtl != nil { + if zoneDelegated.UseDelegatedTtl != nil { + if !*zoneDelegated.UseDelegatedTtl { + res["delegated_ttl"] = ttlUndef + } + } + + if zoneDelegated.DelegatedTtl != nil && *zoneDelegated.DelegatedTtl > 0 { res["delegated_ttl"] = *zoneDelegated.DelegatedTtl + } else { + res["delegated_ttl"] = ttlUndef } + if zoneDelegated.DelegateTo.IsNull == false { nsInterface := convertNullableNameServersToInterface(zoneDelegated.DelegateTo) res["delegate_to"] = nsInterface diff --git a/infoblox/resource_infoblox_zone_delegated.go b/infoblox/resource_infoblox_zone_delegated.go index 983e0f8df..f33862207 100644 --- a/infoblox/resource_infoblox_zone_delegated.go +++ b/infoblox/resource_infoblox_zone_delegated.go @@ -489,6 +489,7 @@ func resourceZoneDelegatedImport(d *schema.ResourceData, m interface{}) ([]*sche return nil, err } + var ttl int connector := m.(ibclient.IBConnector) objMgr := ibclient.NewObjectManager(connector, "Terraform", "") @@ -546,8 +547,17 @@ func resourceZoneDelegatedImport(d *schema.ResourceData, m interface{}) ([]*sche } } + if zoneDelegated.DelegatedTtl != nil && *zoneDelegated.DelegatedTtl > 0 { + ttl = int(*zoneDelegated.DelegatedTtl) + } else { + ttl = ttlUndef + } + if !*zoneDelegated.UseDelegatedTtl { + ttl = ttlUndef + } + if zoneDelegated.DelegatedTtl != nil { - if err = d.Set("delegated_ttl", *zoneDelegated.DelegatedTtl); err != nil { + if err = d.Set("delegated_ttl", ttl); err != nil { return nil, err } } diff --git a/infoblox/resource_infoblox_zone_forward.go b/infoblox/resource_infoblox_zone_forward.go index 64fd4150f..10346a646 100644 --- a/infoblox/resource_infoblox_zone_forward.go +++ b/infoblox/resource_infoblox_zone_forward.go @@ -263,12 +263,12 @@ func validateNameServers(ftSlice []interface{}) ([]ibclient.NameServer, error) { if err != nil { return nil, fmt.Errorf("failed to marshal nameservers: %s", err) } - var forwardTo []ibclient.NameServer - err = json.Unmarshal(nsStr, &forwardTo) + var nameServers []ibclient.NameServer + err = json.Unmarshal(nsStr, &nameServers) if err != nil { return nil, fmt.Errorf("failed to unmarshal nameservers: %s", err) } - return forwardTo, nil + return nameServers, nil } func resourceZoneForwardRead(d *schema.ResourceData, m interface{}) error { From 46ba5b7e4d523c986368232abd51ef1fbc192f3d Mon Sep 17 00:00:00 2001 From: apattar Date: Mon, 7 Oct 2024 14:53:04 +0530 Subject: [PATCH 12/13] updating documentation in docs/index.md file for zone-delegated object. --- docs/index.md | 98 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 7 deletions(-) diff --git a/docs/index.md b/docs/index.md index ea64b9456..97b9c5d9c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,88 @@ # Infoblox IPAM Driver for Terraform +## Prerequisites + +Whether you intend to use the published plug-in or the customized version that you have built yourself, you must complete the following prerequisites: + +- Install and set up a physical or virtual Infoblox NIOS appliance and has necessary licenses installed. Configure the access permissions for Terraform to interact with NIOS Grid objects. +- To use the Infoblox IPAM Plug-In for Terraform, you must either define the following extensible attributes or install the Cloud Network Automation license in the NIOS Grid, which adds the extensible attributes by default: +```json +{ + "Tenant ID": "String Type", + "CMP Type": "String Type", + "Cloud API Owned": "List Type (Values True, False)" +} +``` +You may add other extensible attributes that you want to use. +- Create an extensible attribute by name Terraform Internal ID of type string in Infoblox NIOS as given in below curl command. +```bash +curl -k -u : -H "Content-Type: application/json" -X POST https:///wapi/v2.12/extensibleattributedef -d '{"name": "Terraform Internal ID", "flags": "CR", "type": "STRING", "comment": "Internal ID for Terraform Resource"}' +``` + +> **Note:** +> +>Either the Terraform Internal ID extensible attribute definition must be present in NIOS or IPAM Plug-In for Terraform +must be configured with superuser access for it to automatically create the extensible attribute. If not, the connection +to Terraform will fail. +> +>If you choose to create the Terraform Internal ID extensible attribute manually or by using the cURL command, +the creation of the extensible attribute is not managed by IPAM Plug-In for Terraform. +> +>You must not modify the Terraform Internal ID for a resource under any circumstances. If it is modified, the resource +will no longer be managed by Terraform. + + +## Configuring Infoblox Terraform IPAM Plug-In + +Terraform relies on an Infoblox provider to interact with NIOS Grid objects. You can either use the published Infoblox provider (Infoblox IPAM Plug-In for Terraform) available on the Terraform Registry page or develop a plug-in with features that are not available in the published plug-in. + +As a prerequisite, configure provider authentication to set up the required access permissions for Terraform to interact with NIOS Grid objects. Additionally, declare the version of IPAM Plug-In for Terraform in the .tf file to allow Terraform to automatically install the published plug-in available in the Terraform Registry. + +To configure IPAM Plug-In for Terraform for use, complete the following steps: + +In the .tf file, specify the plug-in version in the required_providers block as follows in .tf file: +```hcl +terraform { + required_providers { + infoblox = { + source = "infobloxopen/infoblox" + version = ">= 2.7.0" + } + } +} +``` + +Configure the credentials required to access the NIOS Grid as environment variables or provider block in .tf file: + + +```bash + # Using environment variable + $ export INFOBLOX_SERVER= + $ export INFOBLOX_USERNAME= + $ export INFOBLOX_PASSWORD= +``` + +```hcl +// Using Provider block +provider "infoblox" { + server = var.server + username = var.username + password = var.password +} +``` + +Add other environment variables that you intend to use. +You can set the following environment variables instead of defining them as attributes inside the provider block in the .tf file. Each of these environment variables has a corresponding attribute in the provider block. +``` +PORT +SSLMODE +CONNECT_TIMEOUT +POOL_CONNECTIONS +WAPI_VERSION +``` + +Run the terraform init command in the directory where the .tf file is located to initialize the plug-in. + ## Resources There are resources for the following objects, supported by the plugin: @@ -18,7 +101,7 @@ There are resources for the following objects, supported by the plugin: * Zone Auth (`infoblox_zone_auth`) * Zone Forward (`infoblox_zone_forward`) * Host record (`infoblox_ip_allocation` / `infoblox_ip_association`) -* Zone delegated +* * Zone Delegated (`infoblox_zone_delegated`) Network and network container resources have two versions: IPv4 and IPv6. In addition, there are two operations which are implemented as resources: @@ -70,12 +153,13 @@ There are data sources for the following objects: * Zone Auth (`infoblox_zone_auth`) * Zone Forward (`infoblox_zone_forward`) * Host Record (`infoblox_host_record`) +* * Zone Delegated (`infoblox_zone_delegated`) !> From version 2.5.0, new feature filters are introduced. Now the data sources support to populate more than one matching NIOS objects. * `filters`: the schema, with passing combination of searchable fields are supported by NIOS server, which -returns one or more matching objects from the NIOS server. + returns one or more matching objects from the NIOS server. For usage of filters, add the fields as keys and appropriate values to be passed to the keys like `name`, `view` corresponding to object. @@ -183,11 +267,11 @@ with a randomly generated value in the form of a UUID to the record. - You may use the command-line tool `uuid` for Linux-based systems to generate a UUID. > The `Terraform Internal ID` extensible attribute is not shown in to terraform.tfstate file. Use it to create - or import the `infoblox_ip_allocation` and `infoblox_ip_association` resources. - You must not add it in a resource block with other extensible attributes. +or import the `infoblox_ip_allocation` and `infoblox_ip_association` resources. +You must not add it in a resource block with other extensible attributes. > You must not delete (ex. with 'terraform destroy' command) an `infoblox_ip_association` resource right after importing, but you may do this after 'terraform apply'. - The reason: after 'terraform import' the dependency between `infoblox_ip_association` and respective `infoblox_ip_allocation` is not established by Terraform. +The reason: after 'terraform import' the dependency between `infoblox_ip_association` and respective `infoblox_ip_allocation` is not established by Terraform. ### Utilizing the Import Block to Import Resources: @@ -229,5 +313,5 @@ resource "infoblox_a_record" "imported_records" { } ``` > **Note:** -> -> When using the Terraform import block for a resource, a new Terraform internal ID is assigned to the resource when the terraform plan command is run for the first time. If a subsequent terraform apply is aborted, the record will still retain the Terraform Internal ID though the resource is not managed by Terraform. +> +> When using the Terraform import block for a resource, a new Terraform internal ID is assigned to the resource when the terraform plan command is run for the first time. If a subsequent terraform apply is aborted, the record will still retain the Terraform Internal ID though the resource is not managed by Terraform. \ No newline at end of file From 9117b855787db8f5392df02191d7de1b84a71895 Mon Sep 17 00:00:00 2001 From: apattar Date: Tue, 8 Oct 2024 10:51:10 +0530 Subject: [PATCH 13/13] updating documentation and adding example for zone-delegated object. --- docs/index.md | 4 ++-- .../infoblox.tf => resources/infoblox_zone_delegated.tf} | 6 ++---- examples/v0.14/Resources/ZoneDelegated/versions.tf | 8 -------- 3 files changed, 4 insertions(+), 14 deletions(-) rename examples/{v0.14/Resources/ZoneDelegated/infoblox.tf => resources/infoblox_zone_delegated.tf} (80%) delete mode 100644 examples/v0.14/Resources/ZoneDelegated/versions.tf diff --git a/docs/index.md b/docs/index.md index 97b9c5d9c..e13e844e7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -101,7 +101,7 @@ There are resources for the following objects, supported by the plugin: * Zone Auth (`infoblox_zone_auth`) * Zone Forward (`infoblox_zone_forward`) * Host record (`infoblox_ip_allocation` / `infoblox_ip_association`) -* * Zone Delegated (`infoblox_zone_delegated`) +* Zone Delegated (`infoblox_zone_delegated`) Network and network container resources have two versions: IPv4 and IPv6. In addition, there are two operations which are implemented as resources: @@ -153,7 +153,7 @@ There are data sources for the following objects: * Zone Auth (`infoblox_zone_auth`) * Zone Forward (`infoblox_zone_forward`) * Host Record (`infoblox_host_record`) -* * Zone Delegated (`infoblox_zone_delegated`) +* Zone Delegated (`infoblox_zone_delegated`) !> From version 2.5.0, new feature filters are introduced. Now the data sources support to populate more than one matching NIOS objects. diff --git a/examples/v0.14/Resources/ZoneDelegated/infoblox.tf b/examples/resources/infoblox_zone_delegated.tf similarity index 80% rename from examples/v0.14/Resources/ZoneDelegated/infoblox.tf rename to examples/resources/infoblox_zone_delegated.tf index c2503d6e7..6339224ed 100644 --- a/examples/v0.14/Resources/ZoneDelegated/infoblox.tf +++ b/examples/resources/infoblox_zone_delegated.tf @@ -1,15 +1,13 @@ # Zone Delegated resource "infoblox_zone_delegated" "subdomain" { - fqdn = "subdomain.example.com" - delegate_to { name = "ns-1488.awsdns-58.org" + address = "10.1.1.1" } - delegate_to { name = "ns-2034.awsdns-62.co.uk" + address = "10.10.1.1" } - } diff --git a/examples/v0.14/Resources/ZoneDelegated/versions.tf b/examples/v0.14/Resources/ZoneDelegated/versions.tf deleted file mode 100644 index 0ac2a2b98..000000000 --- a/examples/v0.14/Resources/ZoneDelegated/versions.tf +++ /dev/null @@ -1,8 +0,0 @@ -terraform { - required_providers { - infoblox = { - source = "infobloxopen/infoblox" - version = ">= 2.1" - } - } -}