2015-04-30 05:32:34 +00:00
package google
import (
"fmt"
"log"
2018-03-01 20:50:47 +00:00
"strings"
2015-04-30 05:32:34 +00:00
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/dns/v1"
2018-05-29 21:43:39 +00:00
"net"
2015-04-30 05:32:34 +00:00
)
func resourceDnsRecordSet ( ) * schema . Resource {
return & schema . Resource {
Create : resourceDnsRecordSetCreate ,
Read : resourceDnsRecordSetRead ,
Delete : resourceDnsRecordSetDelete ,
2016-08-08 00:36:27 +00:00
Update : resourceDnsRecordSetUpdate ,
2018-01-17 19:19:55 +00:00
Importer : & schema . ResourceImporter {
State : resourceDnsRecordSetImportState ,
} ,
2015-04-30 05:32:34 +00:00
Schema : map [ string ] * schema . Schema {
2016-04-10 21:34:15 +00:00
"managed_zone" : & schema . Schema {
2015-04-30 05:32:34 +00:00
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
} ,
2016-04-10 21:34:15 +00:00
"name" : & schema . Schema {
2015-04-30 05:32:34 +00:00
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
} ,
2016-04-10 21:34:15 +00:00
"rrdatas" : & schema . Schema {
Type : schema . TypeList ,
2015-04-30 05:32:34 +00:00
Required : true ,
2016-04-10 21:34:15 +00:00
Elem : & schema . Schema {
Type : schema . TypeString ,
2018-05-29 21:43:39 +00:00
DiffSuppressFunc : func ( k , old , new string , d * schema . ResourceData ) bool {
if d . Get ( "type" ) == "AAAA" {
return ipv6AddressDiffSuppress ( k , old , new , d )
}
return false
} ,
2016-04-10 21:34:15 +00:00
} ,
2018-03-01 20:50:47 +00:00
DiffSuppressFunc : func ( k , old , new string , d * schema . ResourceData ) bool {
2018-03-01 21:01:30 +00:00
return strings . ToLower ( strings . Trim ( old , ` " ` ) ) == strings . ToLower ( strings . Trim ( new , ` " ` ) )
2018-03-01 20:50:47 +00:00
} ,
2015-04-30 05:32:34 +00:00
} ,
"ttl" : & schema . Schema {
Type : schema . TypeInt ,
Required : true ,
} ,
2016-04-10 21:34:15 +00:00
"type" : & schema . Schema {
Type : schema . TypeString ,
2015-04-30 05:32:34 +00:00
Required : true ,
} ,
2016-04-10 16:59:57 +00:00
"project" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
2017-11-28 00:32:20 +00:00
Computed : true ,
2016-04-10 16:59:57 +00:00
ForceNew : true ,
} ,
2015-04-30 05:32:34 +00:00
} ,
}
}
func resourceDnsRecordSetCreate ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
2016-04-10 16:59:57 +00:00
project , err := getProject ( d , config )
if err != nil {
return err
}
2018-01-17 19:19:55 +00:00
name := d . Get ( "name" ) . ( string )
2015-04-30 05:32:34 +00:00
zone := d . Get ( "managed_zone" ) . ( string )
2018-01-17 19:19:55 +00:00
rType := d . Get ( "type" ) . ( string )
2015-04-30 05:32:34 +00:00
// Build the change
chg := & dns . Change {
2015-06-24 05:31:24 +00:00
Additions : [ ] * dns . ResourceRecordSet {
& dns . ResourceRecordSet {
2018-01-17 19:19:55 +00:00
Name : name ,
Type : rType ,
2015-06-24 05:31:24 +00:00
Ttl : int64 ( d . Get ( "ttl" ) . ( int ) ) ,
2016-08-08 00:36:27 +00:00
Rrdatas : rrdata ( d ) ,
2015-04-30 05:32:34 +00:00
} ,
} ,
}
2017-11-08 00:42:25 +00:00
// we need to replace NS record sets in the same call. That means
// we need to list all the current NS record sets attached to the
// zone and add them to the change as deletions. We can't just add
// new NS record sets, or we'll get an error about the NS record set
// already existing; see terraform-providers/terraform-provider-google#95.
// We also can't just remove the NS recordsets on creation, as at
// least one is required. So the solution is to "update in place" by
// putting the addition and the removal in the same API call.
2018-01-17 19:19:55 +00:00
if rType == "NS" {
2017-10-24 21:43:35 +00:00
log . Printf ( "[DEBUG] DNS record list request for %q" , zone )
2017-08-25 09:08:35 +00:00
res , err := config . clientDns . ResourceRecordSets . List ( project , zone ) . Do ( )
if err != nil {
return fmt . Errorf ( "Error retrieving record sets for %q: %s" , zone , err )
}
var deletions [ ] * dns . ResourceRecordSet
for _ , record := range res . Rrsets {
2018-01-17 19:19:55 +00:00
if record . Type != "NS" || record . Name != name {
2017-08-25 09:08:35 +00:00
continue
}
deletions = append ( deletions , record )
}
if len ( deletions ) > 0 {
chg . Deletions = deletions
}
}
2015-04-30 05:32:34 +00:00
log . Printf ( "[DEBUG] DNS Record create request: %#v" , chg )
2016-04-10 16:59:57 +00:00
chg , err = config . clientDns . Changes . Create ( project , zone , chg ) . Do ( )
2015-04-30 05:32:34 +00:00
if err != nil {
return fmt . Errorf ( "Error creating DNS RecordSet: %s" , err )
}
2018-01-17 19:19:55 +00:00
d . SetId ( fmt . Sprintf ( "%s/%s/%s" , zone , name , rType ) )
2015-04-30 05:32:34 +00:00
w := & DnsChangeWaiter {
2015-06-24 05:31:24 +00:00
Service : config . clientDns ,
Change : chg ,
2016-04-10 16:59:57 +00:00
Project : project ,
2015-04-30 05:32:34 +00:00
ManagedZone : zone ,
}
2016-08-08 00:36:27 +00:00
_ , err = w . Conf ( ) . WaitForState ( )
2015-04-30 05:32:34 +00:00
if err != nil {
return fmt . Errorf ( "Error waiting for Google DNS change: %s" , err )
}
return resourceDnsRecordSetRead ( d , meta )
}
func resourceDnsRecordSetRead ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
2016-04-10 16:59:57 +00:00
project , err := getProject ( d , config )
if err != nil {
return err
}
2015-04-30 05:32:34 +00:00
zone := d . Get ( "managed_zone" ) . ( string )
// name and type are effectively the 'key'
name := d . Get ( "name" ) . ( string )
dnsType := d . Get ( "type" ) . ( string )
resp , err := config . clientDns . ResourceRecordSets . List (
2016-04-10 16:59:57 +00:00
project , zone ) . Name ( name ) . Type ( dnsType ) . Do ( )
2015-04-30 05:32:34 +00:00
if err != nil {
2017-05-09 23:00:47 +00:00
return handleNotFoundError ( err , d , fmt . Sprintf ( "DNS Record Set %q" , d . Get ( "name" ) . ( string ) ) )
2015-04-30 05:32:34 +00:00
}
if len ( resp . Rrsets ) == 0 {
// The resource doesn't exist anymore
d . SetId ( "" )
return nil
}
if len ( resp . Rrsets ) > 1 {
return fmt . Errorf ( "Only expected 1 record set, got %d" , len ( resp . Rrsets ) )
}
2018-01-17 19:19:55 +00:00
d . Set ( "type" , resp . Rrsets [ 0 ] . Type )
2015-04-30 05:32:34 +00:00
d . Set ( "ttl" , resp . Rrsets [ 0 ] . Ttl )
d . Set ( "rrdatas" , resp . Rrsets [ 0 ] . Rrdatas )
2017-11-28 00:32:20 +00:00
d . Set ( "project" , project )
2015-04-30 05:32:34 +00:00
return nil
}
func resourceDnsRecordSetDelete ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
2016-04-10 16:59:57 +00:00
project , err := getProject ( d , config )
if err != nil {
return err
}
2015-04-30 05:32:34 +00:00
zone := d . Get ( "managed_zone" ) . ( string )
2017-11-30 00:21:01 +00:00
// NS records must always have a value, so we short-circuit delete
// this allows terraform delete to work, but may have unexpected
// side-effects when deleting just that record set.
// Unfortunately, you can set NS records on subdomains, and those
// CAN and MUST be deleted, so we need to retrieve the managed zone,
// check if what we're looking at is a subdomain, and only not delete
// if it's not actually a subdomain
if d . Get ( "type" ) . ( string ) == "NS" {
2017-11-30 01:17:33 +00:00
mz , err := config . clientDns . ManagedZones . Get ( project , zone ) . Do ( )
if err != nil {
return fmt . Errorf ( "Error retrieving managed zone %q from %q: %s" , zone , project , err )
2017-11-30 00:21:01 +00:00
}
2017-11-30 01:17:33 +00:00
domain := mz . DnsName
2017-11-30 00:21:01 +00:00
if domain == d . Get ( "name" ) . ( string ) {
log . Println ( "[DEBUG] NS records can't be deleted due to API restrictions, so they're being left in place. See https://www.terraform.io/docs/providers/google/r/dns_record_set.html for more information." )
return nil
}
}
2015-04-30 05:32:34 +00:00
// Build the change
chg := & dns . Change {
2015-06-24 05:31:24 +00:00
Deletions : [ ] * dns . ResourceRecordSet {
& dns . ResourceRecordSet {
Name : d . Get ( "name" ) . ( string ) ,
Type : d . Get ( "type" ) . ( string ) ,
Ttl : int64 ( d . Get ( "ttl" ) . ( int ) ) ,
2016-08-08 00:36:27 +00:00
Rrdatas : rrdata ( d ) ,
2015-04-30 05:32:34 +00:00
} ,
} ,
}
log . Printf ( "[DEBUG] DNS Record delete request: %#v" , chg )
2016-04-10 16:59:57 +00:00
chg , err = config . clientDns . Changes . Create ( project , zone , chg ) . Do ( )
2015-04-30 05:32:34 +00:00
if err != nil {
return fmt . Errorf ( "Error deleting DNS RecordSet: %s" , err )
}
w := & DnsChangeWaiter {
2015-06-24 05:31:24 +00:00
Service : config . clientDns ,
Change : chg ,
2016-04-10 16:59:57 +00:00
Project : project ,
2015-04-30 05:32:34 +00:00
ManagedZone : zone ,
}
2016-08-08 00:36:27 +00:00
_ , err = w . Conf ( ) . WaitForState ( )
2015-04-30 05:32:34 +00:00
if err != nil {
return fmt . Errorf ( "Error waiting for Google DNS change: %s" , err )
}
d . SetId ( "" )
return nil
}
2016-08-08 00:36:27 +00:00
func resourceDnsRecordSetUpdate ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
project , err := getProject ( d , config )
if err != nil {
return err
}
zone := d . Get ( "managed_zone" ) . ( string )
recordName := d . Get ( "name" ) . ( string )
oldTtl , newTtl := d . GetChange ( "ttl" )
oldType , newType := d . GetChange ( "type" )
oldCountRaw , _ := d . GetChange ( "rrdatas.#" )
oldCount := oldCountRaw . ( int )
chg := & dns . Change {
Deletions : [ ] * dns . ResourceRecordSet {
& dns . ResourceRecordSet {
Name : recordName ,
Type : oldType . ( string ) ,
Ttl : int64 ( oldTtl . ( int ) ) ,
Rrdatas : make ( [ ] string , oldCount ) ,
} ,
} ,
Additions : [ ] * dns . ResourceRecordSet {
& dns . ResourceRecordSet {
Name : recordName ,
Type : newType . ( string ) ,
Ttl : int64 ( newTtl . ( int ) ) ,
Rrdatas : rrdata ( d ) ,
} ,
} ,
}
for i := 0 ; i < oldCount ; i ++ {
rrKey := fmt . Sprintf ( "rrdatas.%d" , i )
oldRR , _ := d . GetChange ( rrKey )
chg . Deletions [ 0 ] . Rrdatas [ i ] = oldRR . ( string )
}
log . Printf ( "[DEBUG] DNS Record change request: %#v old: %#v new: %#v" , chg , chg . Deletions [ 0 ] , chg . Additions [ 0 ] )
chg , err = config . clientDns . Changes . Create ( project , zone , chg ) . Do ( )
if err != nil {
return fmt . Errorf ( "Error changing DNS RecordSet: %s" , err )
}
w := & DnsChangeWaiter {
Service : config . clientDns ,
Change : chg ,
Project : project ,
ManagedZone : zone ,
}
if _ , err = w . Conf ( ) . WaitForState ( ) ; err != nil {
return fmt . Errorf ( "Error waiting for Google DNS change: %s" , err )
}
return resourceDnsRecordSetRead ( d , meta )
}
2018-01-17 19:19:55 +00:00
func resourceDnsRecordSetImportState ( d * schema . ResourceData , _ interface { } ) ( [ ] * schema . ResourceData , error ) {
parts := strings . Split ( d . Id ( ) , "/" )
if len ( parts ) != 3 {
return nil , fmt . Errorf ( "Invalid dns record specifier. Expecting {zone-name}/{record-name}/{record-type}. The record name must include a trailing '.' at the end." )
}
d . Set ( "managed_zone" , parts [ 0 ] )
d . Set ( "name" , parts [ 1 ] )
d . Set ( "type" , parts [ 2 ] )
return [ ] * schema . ResourceData { d } , nil
}
2016-08-08 00:36:27 +00:00
func rrdata (
d * schema . ResourceData ,
) [ ] string {
rrdatasCount := d . Get ( "rrdatas.#" ) . ( int )
data := make ( [ ] string , rrdatasCount )
for i := 0 ; i < rrdatasCount ; i ++ {
data [ i ] = d . Get ( fmt . Sprintf ( "rrdatas.%d" , i ) ) . ( string )
}
return data
}
2018-05-29 21:43:39 +00:00
func ipv6AddressDiffSuppress ( _ , old , new string , _ * schema . ResourceData ) bool {
oldIp := net . ParseIP ( old )
newIp := net . ParseIP ( new )
return oldIp . Equal ( newIp )
}