provider/dns: DNS dynamic updates (RFC 2136)

This commit is contained in:
Roberto Jung Drebes 2016-08-16 22:14:25 +02:00
parent 04b698f409
commit e3934c23c8
20 changed files with 1914 additions and 0 deletions

View File

@ -0,0 +1,12 @@
package main
import (
"github.com/hashicorp/terraform/builtin/providers/dns"
"github.com/hashicorp/terraform/plugin"
)
func main() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: dns.Provider,
})
}

View File

@ -0,0 +1 @@
package main

View File

@ -0,0 +1,36 @@
#!/bin/bash
set -eu
set -x
# Test domains
export DNS_DOMAIN_FORWARD="example.com."
export DNS_DOMAIN_REVERSE="1.168.192.in-addr.arpa."
# Run with no authentication
export DNS_UPDATE_SERVER=127.0.0.1
docker run -d -p 53:53/udp \
-e BIND_DOMAIN_FORWARD=${DNS_DOMAIN_FORWARD} \
-e BIND_DOMAIN_REVERSE=${DNS_DOMAIN_REVERSE} \
-e BIND_INSECURE=true \
--name bind_insecure drebes/bind
make testacc TEST=./builtin/providers/dns
docker stop bind_insecure
docker rm bind_insecure
# Run with authentication
export DNS_UPDATE_KEYNAME=${DNS_DOMAIN_FORWARD}
export DNS_UPDATE_KEYALGORITHM="hmac-md5"
export DNS_UPDATE_KEYSECRET="c3VwZXJzZWNyZXQ="
docker run -d -p 53:53/udp \
-e BIND_DOMAIN_FORWARD=${DNS_DOMAIN_FORWARD} \
-e BIND_DOMAIN_REVERSE=${DNS_DOMAIN_REVERSE} \
-e BIND_KEY_NAME=${DNS_UPDATE_KEYNAME} \
-e BIND_KEY_ALGORITHM=${DNS_UPDATE_KEYALGORITHM} \
-e BIND_KEY_SECRET=${DNS_UPDATE_KEYSECRET} \
--name bind_secure drebes/bind
make testacc TEST=./builtin/providers/dns
docker stop bind_secure
docker rm bind_secure

View File

@ -0,0 +1,67 @@
package dns
import (
"fmt"
"github.com/miekg/dns"
"log"
)
type Config struct {
server string
port int
keyname string
keyalgo string
keysecret string
}
type DNSClient struct {
c *dns.Client
srv_addr string
keyname string
keysecret string
keyalgo string
}
// Configures and returns a fully initialized DNSClient
func (c *Config) Client() (interface{}, error) {
log.Println("[INFO] Building DNSClient config structure")
var client DNSClient
client.srv_addr = fmt.Sprintf("%s:%d", c.server, c.port)
authCfgOk := false
if (c.keyname == "" && c.keysecret == "" && c.keyalgo == "") ||
(c.keyname != "" && c.keysecret != "" && c.keyalgo != "") {
authCfgOk = true
}
if !authCfgOk {
return nil, fmt.Errorf("Error configuring provider: when using authentication, \"key_name\", \"key_secret\" and \"key_algorithm\" should be non empty")
}
client.c = new(dns.Client)
if c.keyname != "" {
client.keyname = c.keyname
client.keysecret = c.keysecret
keyalgo, err := convertHMACAlgorithm(c.keyalgo)
if err != nil {
return nil, fmt.Errorf("Error configuring provider: %s", err)
}
client.keyalgo = keyalgo
client.c.TsigSecret = map[string]string{c.keyname: c.keysecret}
}
return &client, nil
}
// Validates and converts HMAC algorithm
func convertHMACAlgorithm(name string) (string, error) {
switch name {
case "hmac-md5":
return dns.HmacMD5, nil
case "hmac-sha1":
return dns.HmacSHA1, nil
case "hmac-sha256":
return dns.HmacSHA256, nil
case "hmac-sha512":
return dns.HmacSHA512, nil
default:
return "", fmt.Errorf("Unknown HMAC algorithm: %s", name)
}
}

View File

@ -0,0 +1,165 @@
package dns
import (
"fmt"
"os"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
"github.com/miekg/dns"
)
// Provider returns a schema.Provider for DNS dynamic updates.
func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"update": &schema.Schema{
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"server": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("DNS_UPDATE_SERVER", nil),
},
"port": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 53,
},
"key_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("DNS_UPDATE_KEYNAME", nil),
},
"key_algorithm": &schema.Schema{
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("DNS_UPDATE_KEYALGORITHM", nil),
},
"key_secret": &schema.Schema{
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("DNS_UPDATE_KEYSECRET", nil),
},
},
},
},
},
ResourcesMap: map[string]*schema.Resource{
"dns_a_record_set": resourceDnsARecordSet(),
"dns_aaaa_record_set": resourceDnsAAAARecordSet(),
"dns_cname_record": resourceDnsCnameRecord(),
"dns_ptr_record": resourceDnsPtrRecord(),
},
ConfigureFunc: configureProvider,
}
}
func configureProvider(d *schema.ResourceData) (interface{}, error) {
var server, keyname, keyalgo, keysecret string
var port int
// if the update block is missing, schema.EnvDefaultFunc is not called
if v, ok := d.GetOk("update"); ok {
update := v.([]interface{})[0].(map[string]interface{})
if val, ok := update["port"]; ok {
port = int(val.(int))
}
if val, ok := update["server"]; ok {
server = val.(string)
}
if val, ok := update["key_name"]; ok {
keyname = val.(string)
}
if val, ok := update["key_algorithm"]; ok {
keyalgo = val.(string)
}
if val, ok := update["key_secret"]; ok {
keysecret = val.(string)
}
} else {
if len(os.Getenv("DNS_UPDATE_SERVER")) > 0 {
server = os.Getenv("DNS_UPDATE_SERVER")
} else {
return nil, nil
}
port = 53
if len(os.Getenv("DNS_UPDATE_KEYNAME")) > 0 {
keyname = os.Getenv("DNS_UPDATE_KEYNAME")
}
if len(os.Getenv("DNS_UPDATE_KEYALGORITHM")) > 0 {
keyalgo = os.Getenv("DNS_UPDATE_KEYALGORITHM")
}
if len(os.Getenv("DNS_UPDATE_KEYSECRET")) > 0 {
keysecret = os.Getenv("DNS_UPDATE_KEYSECRET")
}
}
config := Config{
server: server,
port: port,
keyname: keyname,
keyalgo: keyalgo,
keysecret: keysecret,
}
return config.Client()
}
func getAVal(record interface{}) (string, error) {
recstr := record.(*dns.A).String()
var name, ttl, class, typ, addr string
_, err := fmt.Sscanf(recstr, "%s\t%s\t%s\t%s\t%s", &name, &ttl, &class, &typ, &addr)
if err != nil {
return "", fmt.Errorf("Error parsing record: %s", err)
}
return addr, nil
}
func getAAAAVal(record interface{}) (string, error) {
recstr := record.(*dns.AAAA).String()
var name, ttl, class, typ, addr string
_, err := fmt.Sscanf(recstr, "%s\t%s\t%s\t%s\t%s", &name, &ttl, &class, &typ, &addr)
if err != nil {
return "", fmt.Errorf("Error parsing record: %s", err)
}
return addr, nil
}
func getCnameVal(record interface{}) (string, error) {
recstr := record.(*dns.CNAME).String()
var name, ttl, class, typ, cname string
_, err := fmt.Sscanf(recstr, "%s\t%s\t%s\t%s\t%s", &name, &ttl, &class, &typ, &cname)
if err != nil {
return "", fmt.Errorf("Error parsing record: %s", err)
}
return cname, nil
}
func getPtrVal(record interface{}) (string, error) {
recstr := record.(*dns.PTR).String()
var name, ttl, class, typ, ptr string
_, err := fmt.Sscanf(recstr, "%s\t%s\t%s\t%s\t%s", &name, &ttl, &class, &typ, &ptr)
if err != nil {
return "", fmt.Errorf("Error parsing record: %s", err)
}
return ptr, nil
}

View File

@ -0,0 +1,36 @@
package dns
import (
"os"
"testing"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider
func init() {
testAccProvider = Provider().(*schema.Provider)
testAccProviders = map[string]terraform.ResourceProvider{
"dns": testAccProvider,
}
}
func TestProvider(t *testing.T) {
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestProvider_impl(t *testing.T) {
var _ terraform.ResourceProvider = Provider()
}
func testAccPreCheck(t *testing.T) {
v := os.Getenv("DNS_UPDATE_SERVER")
if v == "" {
t.Fatal("DNS_UPDATE_SERVER must be set for acceptance tests")
}
}

View File

@ -0,0 +1,212 @@
package dns
import (
"fmt"
"time"
"github.com/hashicorp/terraform/helper/schema"
"github.com/miekg/dns"
)
func resourceDnsARecordSet() *schema.Resource {
return &schema.Resource{
Create: resourceDnsARecordSetCreate,
Read: resourceDnsARecordSetRead,
Update: resourceDnsARecordSetUpdate,
Delete: resourceDnsARecordSetDelete,
Schema: map[string]*schema.Schema{
"zone": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"addresses": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"ttl": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Default: 3600,
},
},
}
}
func resourceDnsARecordSetCreate(d *schema.ResourceData, meta interface{}) error {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error creating DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
d.SetId(rec_fqdn)
return resourceDnsARecordSetUpdate(d, meta)
}
func resourceDnsARecordSetRead(d *schema.ResourceData, meta interface{}) error {
if meta != nil {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
msg := new(dns.Msg)
msg.SetQuestion(rec_fqdn, dns.TypeA)
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
return fmt.Errorf("Error querying DNS record: %v", r.Rcode)
}
addresses := schema.NewSet(schema.HashString, nil)
for _, record := range r.Answer {
addr, err := getAVal(record)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
addresses.Add(addr)
}
if !addresses.Equal(d.Get("addresses")) {
d.SetId("")
return fmt.Errorf("DNS record differs")
}
return nil
} else {
return fmt.Errorf("update server is not set")
}
}
func resourceDnsARecordSetUpdate(d *schema.ResourceData, meta interface{}) error {
if meta != nil {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
ttl := d.Get("ttl").(int)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
keyname := meta.(*DNSClient).keyname
keyalgo := meta.(*DNSClient).keyalgo
msg := new(dns.Msg)
msg.SetUpdate(rec_zone)
if d.HasChange("addresses") {
o, n := d.GetChange("addresses")
os := o.(*schema.Set)
ns := n.(*schema.Set)
remove := os.Difference(ns).List()
add := ns.Difference(os).List()
// Loop through all the old addresses and remove them
for _, addr := range remove {
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s %d A %s", rec_fqdn, ttl, addr.(string)))
msg.Remove([]dns.RR{rr_remove})
}
// Loop through all the new addresses and insert them
for _, addr := range add {
rr_insert, _ := dns.NewRR(fmt.Sprintf("%s %d A %s", rec_fqdn, ttl, addr.(string)))
msg.Insert([]dns.RR{rr_insert})
}
if keyname != "" {
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
}
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
d.SetId("")
return fmt.Errorf("Error updating DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
d.SetId("")
return fmt.Errorf("Error updating DNS record: %v", r.Rcode)
}
addresses := ns
d.Set("addresses", addresses)
}
return resourceDnsARecordSetRead(d, meta)
} else {
return fmt.Errorf("update server is not set")
}
}
func resourceDnsARecordSetDelete(d *schema.ResourceData, meta interface{}) error {
if meta != nil {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
keyname := meta.(*DNSClient).keyname
keyalgo := meta.(*DNSClient).keyalgo
msg := new(dns.Msg)
msg.SetUpdate(rec_zone)
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s 0 A", rec_fqdn))
msg.RemoveRRset([]dns.RR{rr_remove})
if keyname != "" {
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
}
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error deleting DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
return fmt.Errorf("Error deleting DNS record: %v", r.Rcode)
}
return nil
} else {
return fmt.Errorf("update server is not set")
}
}

View File

@ -0,0 +1,133 @@
package dns
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
"github.com/miekg/dns"
)
func TestAccDnsARecordSet_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDnsARecordSetDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDnsARecordSet_basic,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("dns_a_record_set.foo", "addresses.#", "2"),
testAccCheckDnsARecordSetExists(t, "dns_a_record_set.foo", []interface{}{"192.168.0.2", "192.168.0.1"}),
),
},
resource.TestStep{
Config: testAccDnsARecordSet_update,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("dns_a_record_set.foo", "addresses.#", "3"),
testAccCheckDnsARecordSetExists(t, "dns_a_record_set.foo", []interface{}{"10.0.0.3", "10.0.0.2", "10.0.0.1"}),
),
},
},
})
}
func testAccCheckDnsARecordSetDestroy(s *terraform.State) error {
meta := testAccProvider.Meta()
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
for _, rs := range s.RootModule().Resources {
if rs.Type != "dns_a_record_set" {
continue
}
rec_name := rs.Primary.Attributes["name"]
rec_zone := rs.Primary.Attributes["zone"]
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
msg := new(dns.Msg)
msg.SetQuestion(rec_fqdn, dns.TypeA)
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if r.Rcode != dns.RcodeNameError {
return fmt.Errorf("DNS record still exists: %v", r.Rcode)
}
}
return nil
}
func testAccCheckDnsARecordSetExists(t *testing.T, n string, addr []interface{}) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
rec_name := rs.Primary.Attributes["name"]
rec_zone := rs.Primary.Attributes["zone"]
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
meta := testAccProvider.Meta()
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
msg := new(dns.Msg)
msg.SetQuestion(rec_fqdn, dns.TypeA)
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
return fmt.Errorf("Error querying DNS record")
}
addresses := schema.NewSet(schema.HashString, nil)
expected := schema.NewSet(schema.HashString, addr)
for _, record := range r.Answer {
addr, err := getAVal(record)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
addresses.Add(addr)
}
if !addresses.Equal(expected) {
return fmt.Errorf("DNS record differs: expected %v, found %v", expected, addresses)
}
return nil
}
}
var testAccDnsARecordSet_basic = fmt.Sprintf(`
resource "dns_a_record_set" "foo" {
zone = "example.com."
name = "foo"
addresses = ["192.168.0.1", "192.168.0.2"]
ttl = 300
}`)
var testAccDnsARecordSet_update = fmt.Sprintf(`
resource "dns_a_record_set" "foo" {
zone = "example.com."
name = "foo"
addresses = ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
ttl = 300
}`)

View File

@ -0,0 +1,212 @@
package dns
import (
"fmt"
"time"
"github.com/hashicorp/terraform/helper/schema"
"github.com/miekg/dns"
)
func resourceDnsAAAARecordSet() *schema.Resource {
return &schema.Resource{
Create: resourceDnsAAAARecordSetCreate,
Read: resourceDnsAAAARecordSetRead,
Update: resourceDnsAAAARecordSetUpdate,
Delete: resourceDnsAAAARecordSetDelete,
Schema: map[string]*schema.Schema{
"zone": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"addresses": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"ttl": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Default: 3600,
},
},
}
}
func resourceDnsAAAARecordSetCreate(d *schema.ResourceData, meta interface{}) error {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error creating DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
d.SetId(rec_fqdn)
return resourceDnsAAAARecordSetUpdate(d, meta)
}
func resourceDnsAAAARecordSetRead(d *schema.ResourceData, meta interface{}) error {
if meta != nil {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
msg := new(dns.Msg)
msg.SetQuestion(rec_fqdn, dns.TypeAAAA)
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
return fmt.Errorf("Error querying DNS record: %v", r.Rcode)
}
addresses := schema.NewSet(schema.HashString, nil)
for _, record := range r.Answer {
addr, err := getAAAAVal(record)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
addresses.Add(addr)
}
if !addresses.Equal(d.Get("addresses")) {
d.SetId("")
return fmt.Errorf("DNS record differs")
}
return nil
} else {
return fmt.Errorf("update server is not set")
}
}
func resourceDnsAAAARecordSetUpdate(d *schema.ResourceData, meta interface{}) error {
if meta != nil {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
ttl := d.Get("ttl").(int)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
keyname := meta.(*DNSClient).keyname
keyalgo := meta.(*DNSClient).keyalgo
msg := new(dns.Msg)
msg.SetUpdate(rec_zone)
if d.HasChange("addresses") {
o, n := d.GetChange("addresses")
os := o.(*schema.Set)
ns := n.(*schema.Set)
remove := os.Difference(ns).List()
add := ns.Difference(os).List()
// Loop through all the old addresses and remove them
for _, addr := range remove {
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s %d AAAA %s", rec_fqdn, ttl, addr.(string)))
msg.Remove([]dns.RR{rr_remove})
}
// Loop through all the new addresses and insert them
for _, addr := range add {
rr_insert, _ := dns.NewRR(fmt.Sprintf("%s %d AAAA %s", rec_fqdn, ttl, addr.(string)))
msg.Insert([]dns.RR{rr_insert})
}
if keyname != "" {
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
}
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
d.SetId("")
return fmt.Errorf("Error updating DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
d.SetId("")
return fmt.Errorf("Error updating DNS record: %v", r.Rcode)
}
addresses := ns
d.Set("addresses", addresses)
}
return resourceDnsAAAARecordSetRead(d, meta)
} else {
return fmt.Errorf("update server is not set")
}
}
func resourceDnsAAAARecordSetDelete(d *schema.ResourceData, meta interface{}) error {
if meta != nil {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
keyname := meta.(*DNSClient).keyname
keyalgo := meta.(*DNSClient).keyalgo
msg := new(dns.Msg)
msg.SetUpdate(rec_zone)
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s 0 AAAA", rec_fqdn))
msg.RemoveRRset([]dns.RR{rr_remove})
if keyname != "" {
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
}
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error deleting DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
return fmt.Errorf("Error deleting DNS record: %v", r.Rcode)
}
return nil
} else {
return fmt.Errorf("update server is not set")
}
}

View File

@ -0,0 +1,133 @@
package dns
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
"github.com/miekg/dns"
)
func TestAccDnsAAAARecordSet_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDnsAAAARecordSetDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDnsAAAARecordSet_basic,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("dns_aaaa_record_set.bar", "addresses.#", "2"),
testAccCheckDnsAAAARecordSetExists(t, "dns_aaaa_record_set.bar", []interface{}{"fdd5:e282:43b8:5303:dead:beef:cafe:babe", "fdd5:e282:43b8:5303:cafe:babe:dead:beef"}),
),
},
resource.TestStep{
Config: testAccDnsAAAARecordSet_update,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("dns_aaaa_record_set.bar", "addresses.#", "2"),
testAccCheckDnsAAAARecordSetExists(t, "dns_aaaa_record_set.bar", []interface{}{"fdd5:e282:43b8:5303:beef:dead:babe:cafe", "fdd5:e282:43b8:5303:babe:cafe:beef:dead"}),
),
},
},
})
}
func testAccCheckDnsAAAARecordSetDestroy(s *terraform.State) error {
meta := testAccProvider.Meta()
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
for _, rs := range s.RootModule().Resources {
if rs.Type != "dns_aaaa_record_set" {
continue
}
rec_name := rs.Primary.Attributes["name"]
rec_zone := rs.Primary.Attributes["zone"]
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
msg := new(dns.Msg)
msg.SetQuestion(rec_fqdn, dns.TypeAAAA)
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if r.Rcode != dns.RcodeNameError {
return fmt.Errorf("DNS record still exists: %v", r.Rcode)
}
}
return nil
}
func testAccCheckDnsAAAARecordSetExists(t *testing.T, n string, addr []interface{}) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
rec_name := rs.Primary.Attributes["name"]
rec_zone := rs.Primary.Attributes["zone"]
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
meta := testAccProvider.Meta()
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
msg := new(dns.Msg)
msg.SetQuestion(rec_fqdn, dns.TypeAAAA)
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
return fmt.Errorf("Error querying DNS record")
}
addresses := schema.NewSet(schema.HashString, nil)
expected := schema.NewSet(schema.HashString, addr)
for _, record := range r.Answer {
addr, err := getAAAAVal(record)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
addresses.Add(addr)
}
if !addresses.Equal(expected) {
return fmt.Errorf("DNS record differs: expected %v, found %v", expected, addresses)
}
return nil
}
}
var testAccDnsAAAARecordSet_basic = fmt.Sprintf(`
resource "dns_aaaa_record_set" "bar" {
zone = "example.com."
name = "bar"
addresses = ["fdd5:e282:43b8:5303:dead:beef:cafe:babe", "fdd5:e282:43b8:5303:cafe:babe:dead:beef"]
ttl = 300
}`)
var testAccDnsAAAARecordSet_update = fmt.Sprintf(`
resource "dns_aaaa_record_set" "bar" {
zone = "example.com."
name = "bar"
addresses = ["fdd5:e282:43b8:5303:beef:dead:babe:cafe", "fdd5:e282:43b8:5303:babe:cafe:beef:dead"]
ttl = 300
}`)

View File

@ -0,0 +1,219 @@
package dns
import (
"fmt"
"time"
"github.com/hashicorp/terraform/helper/schema"
"github.com/miekg/dns"
)
func resourceDnsCnameRecord() *schema.Resource {
return &schema.Resource{
Create: resourceDnsCnameRecordCreate,
Read: resourceDnsCnameRecordRead,
Update: resourceDnsCnameRecordUpdate,
Delete: resourceDnsCnameRecordDelete,
Schema: map[string]*schema.Schema{
"zone": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"cname": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"ttl": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Default: 3600,
},
},
}
}
func resourceDnsCnameRecordCreate(d *schema.ResourceData, meta interface{}) error {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
rec_cname := d.Get("cname").(string)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error creating DNS record: \"zone\" should be an FQDN")
}
if rec_cname != dns.Fqdn(rec_cname) {
return fmt.Errorf("Error creating DNS record: \"cname\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
d.SetId(rec_fqdn)
return resourceDnsCnameRecordUpdate(d, meta)
}
func resourceDnsCnameRecordRead(d *schema.ResourceData, meta interface{}) error {
if meta != nil {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
rec_cname := d.Get("cname").(string)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
}
if rec_cname != dns.Fqdn(rec_cname) {
return fmt.Errorf("Error reading DNS record: \"cname\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
msg := new(dns.Msg)
msg.SetQuestion(rec_fqdn, dns.TypeCNAME)
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
return fmt.Errorf("Error querying DNS record: %v", r.Rcode)
}
if len(r.Answer) > 1 {
return fmt.Errorf("Error querying DNS record: multiple responses received")
}
record := r.Answer[0]
cname, err := getCnameVal(record)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if rec_cname != cname {
d.SetId("")
return fmt.Errorf("DNS record differs")
}
return nil
} else {
return fmt.Errorf("update server is not set")
}
}
func resourceDnsCnameRecordUpdate(d *schema.ResourceData, meta interface{}) error {
if meta != nil {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
rec_cname := d.Get("cname").(string)
ttl := d.Get("ttl").(int)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
}
if rec_cname != dns.Fqdn(rec_cname) {
return fmt.Errorf("Error updating DNS record: \"cname\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
keyname := meta.(*DNSClient).keyname
keyalgo := meta.(*DNSClient).keyalgo
msg := new(dns.Msg)
msg.SetUpdate(rec_zone)
if d.HasChange("cname") {
o, n := d.GetChange("cname")
if o != "" {
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s %d CNAME %s", rec_fqdn, ttl, o))
msg.Remove([]dns.RR{rr_remove})
}
if n != "" {
rr_insert, _ := dns.NewRR(fmt.Sprintf("%s %d CNAME %s", rec_fqdn, ttl, n))
msg.Insert([]dns.RR{rr_insert})
}
if keyname != "" {
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
}
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
d.SetId("")
return fmt.Errorf("Error updating DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
d.SetId("")
return fmt.Errorf("Error updating DNS record: %v", r.Rcode)
}
cname := n
d.Set("cname", cname)
}
return resourceDnsCnameRecordRead(d, meta)
} else {
return fmt.Errorf("update server is not set")
}
}
func resourceDnsCnameRecordDelete(d *schema.ResourceData, meta interface{}) error {
if meta != nil {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
keyname := meta.(*DNSClient).keyname
keyalgo := meta.(*DNSClient).keyalgo
msg := new(dns.Msg)
msg.SetUpdate(rec_zone)
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s 0 CNAME", rec_fqdn))
msg.RemoveRRset([]dns.RR{rr_remove})
if keyname != "" {
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
}
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error deleting DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
return fmt.Errorf("Error deleting DNS record: %v", r.Rcode)
}
return nil
} else {
return fmt.Errorf("update server is not set")
}
}

View File

@ -0,0 +1,129 @@
package dns
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/miekg/dns"
)
func TestAccDnsCnameRecord_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDnsCnameRecordDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDnsCnameRecord_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckDnsCnameRecordExists(t, "dns_cname_record.foo", "bar.example.com."),
),
},
resource.TestStep{
Config: testAccDnsCnameRecord_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckDnsCnameRecordExists(t, "dns_cname_record.foo", "baz.example.com."),
),
},
},
})
}
func testAccCheckDnsCnameRecordDestroy(s *terraform.State) error {
meta := testAccProvider.Meta()
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
for _, rs := range s.RootModule().Resources {
if rs.Type != "dns_cname_record" {
continue
}
rec_name := rs.Primary.Attributes["name"]
rec_zone := rs.Primary.Attributes["zone"]
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
msg := new(dns.Msg)
msg.SetQuestion(rec_fqdn, dns.TypeCNAME)
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if r.Rcode != dns.RcodeNameError {
return fmt.Errorf("DNS record still exists: %v", r.Rcode)
}
}
return nil
}
func testAccCheckDnsCnameRecordExists(t *testing.T, n string, expected string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
rec_name := rs.Primary.Attributes["name"]
rec_zone := rs.Primary.Attributes["zone"]
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
meta := testAccProvider.Meta()
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
msg := new(dns.Msg)
msg.SetQuestion(rec_fqdn, dns.TypeCNAME)
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
return fmt.Errorf("Error querying DNS record")
}
if len(r.Answer) > 1 {
return fmt.Errorf("Error querying DNS record: multiple responses received")
}
record := r.Answer[0]
cname, err := getCnameVal(record)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if expected != cname {
return fmt.Errorf("DNS record differs: expected %v, found %v", expected, cname)
}
return nil
}
}
var testAccDnsCnameRecord_basic = fmt.Sprintf(`
resource "dns_cname_record" "foo" {
zone = "example.com."
name = "foo"
cname = "bar.example.com."
ttl = 300
}`)
var testAccDnsCnameRecord_update = fmt.Sprintf(`
resource "dns_cname_record" "foo" {
zone = "example.com."
name = "baz"
cname = "baz.example.com."
ttl = 300
}`)

View File

@ -0,0 +1,219 @@
package dns
import (
"fmt"
"time"
"github.com/hashicorp/terraform/helper/schema"
"github.com/miekg/dns"
)
func resourceDnsPtrRecord() *schema.Resource {
return &schema.Resource{
Create: resourceDnsPtrRecordCreate,
Read: resourceDnsPtrRecordRead,
Update: resourceDnsPtrRecordUpdate,
Delete: resourceDnsPtrRecordDelete,
Schema: map[string]*schema.Schema{
"zone": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"ptr": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"ttl": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Default: 3600,
},
},
}
}
func resourceDnsPtrRecordCreate(d *schema.ResourceData, meta interface{}) error {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
rec_ptr := d.Get("ptr").(string)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error creating DNS record: \"zone\" should be an FQDN")
}
if rec_ptr != dns.Fqdn(rec_ptr) {
return fmt.Errorf("Error creating DNS record: \"ptr\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
d.SetId(rec_fqdn)
return resourceDnsPtrRecordUpdate(d, meta)
}
func resourceDnsPtrRecordRead(d *schema.ResourceData, meta interface{}) error {
if meta != nil {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
rec_ptr := d.Get("ptr").(string)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
}
if rec_ptr != dns.Fqdn(rec_ptr) {
return fmt.Errorf("Error reading DNS record: \"ptr\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
msg := new(dns.Msg)
msg.SetQuestion(rec_fqdn, dns.TypePTR)
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
return fmt.Errorf("Error querying DNS record: %v", r.Rcode)
}
if len(r.Answer) > 1 {
return fmt.Errorf("Error querying DNS record: multiple responses received")
}
record := r.Answer[0]
ptr, err := getPtrVal(record)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if rec_ptr != ptr {
d.SetId("")
return fmt.Errorf("DNS record differs")
}
return nil
} else {
return fmt.Errorf("update server is not set")
}
}
func resourceDnsPtrRecordUpdate(d *schema.ResourceData, meta interface{}) error {
if meta != nil {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
rec_ptr := d.Get("ptr").(string)
ttl := d.Get("ttl").(int)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
}
if rec_ptr != dns.Fqdn(rec_ptr) {
return fmt.Errorf("Error updating DNS record: \"ptr\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
keyname := meta.(*DNSClient).keyname
keyalgo := meta.(*DNSClient).keyalgo
msg := new(dns.Msg)
msg.SetUpdate(rec_zone)
if d.HasChange("ptr") {
o, n := d.GetChange("ptr")
if o != "" {
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s %d PTR %s", rec_fqdn, ttl, o))
msg.Remove([]dns.RR{rr_remove})
}
if n != "" {
rr_insert, _ := dns.NewRR(fmt.Sprintf("%s %d PTR %s", rec_fqdn, ttl, n))
msg.Insert([]dns.RR{rr_insert})
}
if keyname != "" {
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
}
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
d.SetId("")
return fmt.Errorf("Error updating DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
d.SetId("")
return fmt.Errorf("Error updating DNS record: %v", r.Rcode)
}
ptr := n
d.Set("ptr", ptr)
}
return resourceDnsPtrRecordRead(d, meta)
} else {
return fmt.Errorf("update server is not set")
}
}
func resourceDnsPtrRecordDelete(d *schema.ResourceData, meta interface{}) error {
if meta != nil {
rec_name := d.Get("name").(string)
rec_zone := d.Get("zone").(string)
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error updating DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
keyname := meta.(*DNSClient).keyname
keyalgo := meta.(*DNSClient).keyalgo
msg := new(dns.Msg)
msg.SetUpdate(rec_zone)
rr_remove, _ := dns.NewRR(fmt.Sprintf("%s 0 PTR", rec_fqdn))
msg.RemoveRRset([]dns.RR{rr_remove})
if keyname != "" {
msg.SetTsig(keyname, keyalgo, 300, time.Now().Unix())
}
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error deleting DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
return fmt.Errorf("Error deleting DNS record: %v", r.Rcode)
}
return nil
} else {
return fmt.Errorf("update server is not set")
}
}

View File

@ -0,0 +1,129 @@
package dns
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/miekg/dns"
)
func TestAccDnsPtrRecord_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDnsPtrRecordDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDnsPtrRecord_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckDnsPtrRecordExists(t, "dns_ptr_record.foo", "bar.example.com."),
),
},
resource.TestStep{
Config: testAccDnsPtrRecord_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckDnsPtrRecordExists(t, "dns_ptr_record.foo", "baz.example.com."),
),
},
},
})
}
func testAccCheckDnsPtrRecordDestroy(s *terraform.State) error {
meta := testAccProvider.Meta()
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
for _, rs := range s.RootModule().Resources {
if rs.Type != "dns_ptr_record" {
continue
}
rec_name := rs.Primary.Attributes["name"]
rec_zone := rs.Primary.Attributes["zone"]
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
msg := new(dns.Msg)
msg.SetQuestion(rec_fqdn, dns.TypePTR)
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if r.Rcode != dns.RcodeNameError {
return fmt.Errorf("DNS record still exists: %v", r.Rcode)
}
}
return nil
}
func testAccCheckDnsPtrRecordExists(t *testing.T, n string, expected string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
rec_name := rs.Primary.Attributes["name"]
rec_zone := rs.Primary.Attributes["zone"]
if rec_zone != dns.Fqdn(rec_zone) {
return fmt.Errorf("Error reading DNS record: \"zone\" should be an FQDN")
}
rec_fqdn := fmt.Sprintf("%s.%s", rec_name, rec_zone)
meta := testAccProvider.Meta()
c := meta.(*DNSClient).c
srv_addr := meta.(*DNSClient).srv_addr
msg := new(dns.Msg)
msg.SetQuestion(rec_fqdn, dns.TypePTR)
r, _, err := c.Exchange(msg, srv_addr)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if r.Rcode != dns.RcodeSuccess {
return fmt.Errorf("Error querying DNS record")
}
if len(r.Answer) > 1 {
return fmt.Errorf("Error querying DNS record: multiple responses received")
}
record := r.Answer[0]
ptr, err := getPtrVal(record)
if err != nil {
return fmt.Errorf("Error querying DNS record: %s", err)
}
if expected != ptr {
return fmt.Errorf("DNS record differs: expected %v, found %v", expected, ptr)
}
return nil
}
}
var testAccDnsPtrRecord_basic = fmt.Sprintf(`
resource "dns_ptr_record" "foo" {
zone = "example.com."
name = "r._dns-sd._udp"
ptr = "bar.example.com."
ttl = 300
}`)
var testAccDnsPtrRecord_update = fmt.Sprintf(`
resource "dns_ptr_record" "foo" {
zone = "example.com."
name = "r._dns-sd._udp"
ptr = "baz.example.com."
ttl = 300
}`)

View File

@ -22,6 +22,7 @@ import (
datadogprovider "github.com/hashicorp/terraform/builtin/providers/datadog"
digitaloceanprovider "github.com/hashicorp/terraform/builtin/providers/digitalocean"
dmeprovider "github.com/hashicorp/terraform/builtin/providers/dme"
dnsprovider "github.com/hashicorp/terraform/builtin/providers/dns"
dnsimpleprovider "github.com/hashicorp/terraform/builtin/providers/dnsimple"
dockerprovider "github.com/hashicorp/terraform/builtin/providers/docker"
dynprovider "github.com/hashicorp/terraform/builtin/providers/dyn"
@ -93,6 +94,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
"datadog": datadogprovider.Provider,
"digitalocean": digitaloceanprovider.Provider,
"dme": dmeprovider.Provider,
"dns": dnsprovider.Provider,
"dnsimple": dnsimpleprovider.Provider,
"docker": dockerprovider.Provider,
"dyn": dynprovider.Provider,

View File

@ -0,0 +1,45 @@
---
layout: "dns"
page_title: "Provider: DNS"
sidebar_current: "docs-dns-index"
description: |-
The DNS provider supports DNS updates (RFC 2136). Additionally, the provider can be configured with secret key based transaction authentication (RFC 2845).
---
# DNS Provider
The DNS provider supports DNS updates (RFC 2136). Additionally, the provider can be configured with secret key based transaction authentication (RFC 2845).
Use the navigation to the left to read about the available resources.
## Example Usage
```
# Configure the DNS Provider
provider "dns" {
update {
server = "192.168.0.1"
key_name = "example.com."
key_algorithm = "hmac-md5"
key_secret = "3VwZXJzZWNyZXQ="
}
}
# Create a DNS A record set
resource "dns_a_record_set" "www" {
...
}
```
## Configuration Reference
`update` - (Optional) When the provider is used for DNS updates, this block is required. Structure is documented below.
The `update` block supports the following attributes:
* `server` - (Required) The IPv4 address of the DNS server to send updates to.
* `port` - (Optional) The target UDP port on the server where updates are sent to. Defaults to `53`.
* `key_name` - (Optional) The name of the TSIG key used to sign the DNS update messages.
* `key_algorithm` - (Optional; Required if `key_name` is set) When using TSIG authentication, the algorithm to use for HMAC. Valid values are `hmac-md5`, `hmac-sha1`, `hmac-sha256` or `hmac-sha512`.
* `key_secret` - (Optional; Required if `key_name` is set)
A Base64-encoded string containing the shared secret to be used for TSIG.

View File

@ -0,0 +1,41 @@
---
layout: "dns"
page_title: "DNS: dns_a_record_set"
sidebar_current: "docs-dns-record"
description: |-
Creates a A type DNS record set.
---
# dns\_a\_record\_set
Creates a A type DNS record set.
## Example Usage
```
resource "dns_a_record_set" "www" {
zone = "example.com."
name = "www"
addresses = ["192.168.0.1", "192.168.0.2", "192.168.0.3"]
ttl = 300
}
```
## Argument Reference
The following arguments are supported:
* `zone` - (Required) DNS zone the record set belongs to. It must be an FQDN, that is, include the trailing dot.
* `name` - (Required) The name of the record set. The `zone` argument will be appended to this value to create the full record path.
* `addresses` - (Required) The IPv4 addresses this record set will point to.
* `ttl` - (Optional) The TTL of the record set. Defaults to `3600`.
## Attributes Reference
The following attributes are exported:
* `zone` - See Argument Reference above.
* `name` - See Argument Reference above.
* `addresses` - See Argument Reference above.
* `ttl` - See Argument Reference above.

View File

@ -0,0 +1,41 @@
---
layout: "dns"
page_title: "DNS: dns_aaaa_record_set"
sidebar_current: "docs-dns-record"
description: |-
Creates a AAAA type DNS record set.
---
# dns\_a\_record\_set
Creates a AAAA type DNS record set.
## Example Usage
```
resource "dns_aaaa_record_set" "www" {
zone = "example.com."
name = "www"
addresses = ["fdd5:e282:43b8:5303:dead:beef:cafe:babe", "fdd5:e282:43b8:5303:cafe:babe:dead:beef"]
ttl = 300
}
```
## Argument Reference
The following arguments are supported:
* `zone` - (Required) DNS zone the record set belongs to. It must be an FQDN, that is, include the trailing dot.
* `name` - (Required) The name of the record set. The `zone` argument will be appended to this value to create the full record path.
* `addresses` - (Required) The IPv6 addresses this record set will point to.
* `ttl` - (Optional) The TTL of the record set. Defaults to `3600`.
## Attributes Reference
The following attributes are exported:
* `zone` - See Argument Reference above.
* `name` - See Argument Reference above.
* `addresses` - See Argument Reference above.
* `ttl` - See Argument Reference above.

View File

@ -0,0 +1,41 @@
---
layout: "dns"
page_title: "DNS: dns_cname_record"
sidebar_current: "docs-dns-record"
description: |-
Creates a CNAME type DNS record.
---
# dns\_ptr\_record
Creates a CNAME type DNS record.
## Example Usage
```
resource "dns_cname_record" "foo" {
zone = "example.com."
name = "foo"
cname = "bar.example.com."
ttl = 300
}
```
## Argument Reference
The following arguments are supported:
* `zone` - (Required) DNS zone the record belongs to. It must be an FQDN, that is, include the trailing dot.
* `name` - (Required) The name of the record. The `zone` argument will be appended to this value to create the full record path.
* `cname` - (Required) The canonical name this record will point to.
* `ttl` - (Optional) The TTL of the record set. Defaults to `3600`.
## Attributes Reference
The following attributes are exported:
* `zone` - See Argument Reference above.
* `name` - See Argument Reference above.
* `cname` - See Argument Reference above.
* `ttl` - See Argument Reference above.

View File

@ -0,0 +1,41 @@
---
layout: "dns"
page_title: "DNS: dns_ptr_record"
sidebar_current: "docs-dns-record"
description: |-
Creates a PTR type DNS record.
---
# dns\_ptr\_record
Creates a PTR type DNS record.
## Example Usage
```
resource "dns_ptr_record" "dns-sd" {
zone = "example.com."
name = "r._dns-sd"
ptr = "example.com."
ttl = 300
}
```
## Argument Reference
The following arguments are supported:
* `zone` - (Required) DNS zone the record belongs to. It must be an FQDN, that is, include the trailing dot.
* `name` - (Required) The name of the record. The `zone` argument will be appended to this value to create the full record path.
* `ptr` - (Required) The canonical name this record will point to.
* `ttl` - (Optional) The TTL of the record set. Defaults to `3600`.
## Attributes Reference
The following attributes are exported:
* `zone` - See Argument Reference above.
* `name` - See Argument Reference above.
* `ptr` - See Argument Reference above.
* `ttl` - See Argument Reference above.