Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add infoblox_zone_delegated resource #217

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ There are resources for the following objects, supported by the plugin:
- PTR-record
- CNAME-record
- Host record
- Zone delegated

Network container and network resources have two versions: IPv4 and IPv6. In
addition, there are two operations which are implemented as resources:
Expand Down
40 changes: 40 additions & 0 deletions docs/resources/infoblox_zone_delegated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 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
* `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
resource "infoblox_zone_delegated" "subdomain" {

fqdn = "subdomain.test.com"

delegate_to {
name = "ns-1488.awsdns-58.org"
}

delegate_to {
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
```
15 changes: 15 additions & 0 deletions examples/v0.14/Resources/ZoneDelegated/infoblox.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Zone Delegated

resource "infoblox_zone_delegated" "subdomain" {

fqdn = "subdomain.example.com"

delegate_to {
name = "ns-1488.awsdns-58.org"
}

delegate_to {
name = "ns-2034.awsdns-62.co.uk"
}

}
8 changes: 8 additions & 0 deletions examples/v0.14/Resources/ZoneDelegated/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
terraform {
required_providers {
infoblox = {
source = "infobloxopen/infoblox"
version = ">= 2.1"
}
}
}
1 change: 1 addition & 0 deletions infoblox/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func Provider() *schema.Provider {
"infoblox_aaaa_record": resourceAAAARecord(),
"infoblox_cname_record": resourceCNAMERecord(),
"infoblox_ptr_record": resourcePTRRecord(),
"infoblox_zone_delegated": resourceZoneDelegated(),
},
DataSourcesMap: map[string]*schema.Resource{
"infoblox_ipv4_network": dataSourceIPv4Network(),
Expand Down
250 changes: 250 additions & 0 deletions infoblox/resource_infoblox_zone_delegated.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
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"
)

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(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you move delegate_to schema definition into a separate function? There's no complex initialization logic involved, and this function is used only here.

"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",
},
},
},
}
}

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))

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())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to wrap the error:

Suggested change
return fmt.Errorf("cannot process 'ext_attrs' field: %s", err.Error())
return fmt.Errorf("cannot process 'ext_attrs' field: %w", err)

https://pkg.go.dev/fmt#Errorf

}
}

delegatedFQDN := d.Get("fqdn").(string)

delegations := d.Get("delegate_to").(*schema.Set).List()

nameServers, computedDelegations, err := computeDelegations(delegations)
if err != nil {
return err
}

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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("Error creating Zone Delegated: %s", err)
return fmt.Errorf("Error creating Zone Delegated: %w", err)

}

d.Set("delegate_to", computedDelegations)

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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("Getting Zone Delegated failed: %s", err)
return fmt.Errorf("Getting Zone Delegated failed: %w", 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())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("cannot process 'ext_attrs' field: %s", err.Error())
return fmt.Errorf("cannot process 'ext_attrs' field: %w", err)

}
}

delegations := d.Get("delegate_to").(*schema.Set).List()

nameServers, computedDelegations, err := computeDelegations(delegations)
if err != nil {
return err
}

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())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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)

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())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("cannot process 'ext_attrs' field: %s", err.Error())
return fmt.Errorf("cannot process 'ext_attrs' field: %w", err)

}
}

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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("Deletion of Zone Delegated failed : %s", err)
return fmt.Errorf("Deletion of Zone Delegated failed : %w", 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 = "<new resource>"
}
return fmt.Sprintf("infoblox_zone_delegated (ID = %s)", id)
}
Loading