Support Google Cloud DNS, Fix #1148

This commit is contained in:
Dave Cunningham 2015-04-30 01:32:34 -04:00
parent bce21fe6ac
commit 6d763bacfd
7 changed files with 525 additions and 7 deletions

View File

@ -14,6 +14,7 @@ import (
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
"google.golang.org/api/compute/v1"
"google.golang.org/api/dns/v1"
)
// Config is the configuration structure used to instantiate the Google
@ -24,6 +25,7 @@ type Config struct {
Region string
clientCompute *compute.Service
clientDns *dns.Service
}
func (c *Config) loadAndValidate() error {
@ -50,7 +52,10 @@ func (c *Config) loadAndValidate() error {
err)
}
clientScopes := []string{"https://www.googleapis.com/auth/compute"}
clientScopes := []string{
"https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/ndev.clouddns.readwrite",
}
// Get the token for use in our requests
log.Printf("[INFO] Requesting Google token...")
@ -83,23 +88,31 @@ func (c *Config) loadAndValidate() error {
}
log.Printf("[INFO] Instantiating GCE client...")
var err error
c.clientCompute, err = compute.New(client)
// Set UserAgent
// Build UserAgent
versionString := "0.0.0"
// TODO(dcunnin): Use Terraform's version code from version.go
// versionString := main.Version
// if main.VersionPrerelease != "" {
// versionString = fmt.Sprintf("%s-%s", versionString, main.VersionPrerelease)
// }
c.clientCompute.UserAgent = fmt.Sprintf(
userAgent := fmt.Sprintf(
"(%s %s) Terraform/%s", runtime.GOOS, runtime.GOARCH, versionString)
var err error
log.Printf("[INFO] Instantiating GCE client...")
c.clientCompute, err = compute.New(client)
if err != nil {
return err
}
c.clientCompute.UserAgent = userAgent
log.Printf("[INFO] Instantiating Google Cloud DNS client...")
c.clientDns, err = dns.New(client)
if err != nil {
return err
}
c.clientDns.UserAgent = userAgent
return nil
}

38
dns_change.go Normal file
View File

@ -0,0 +1,38 @@
package google
import (
"google.golang.org/api/dns/v1"
"github.com/hashicorp/terraform/helper/resource"
)
type DnsChangeWaiter struct {
Service *dns.Service
Change *dns.Change
Project string
ManagedZone string
}
func (w *DnsChangeWaiter) RefreshFunc() resource.StateRefreshFunc {
return func() (interface{}, string, error) {
var chg *dns.Change
var err error
chg, err = w.Service.Changes.Get(
w.Project, w.ManagedZone, w.Change.Id).Do()
if err != nil {
return nil, "", err
}
return chg, chg.Status, nil
}
}
func (w *DnsChangeWaiter) Conf() *resource.StateChangeConf {
return &resource.StateChangeConf{
Pending: []string{"pending"},
Target: "done",
Refresh: w.RefreshFunc(),
}
}

View File

@ -39,6 +39,8 @@ func Provider() terraform.ResourceProvider {
"google_compute_network": resourceComputeNetwork(),
"google_compute_route": resourceComputeRoute(),
"google_compute_target_pool": resourceComputeTargetPool(),
"google_dns_managed_zone": resourceDnsManagedZone(),
"google_dns_record_set": resourceDnsRecordSet(),
},
ConfigureFunc: providerConfigure,

View File

@ -0,0 +1,108 @@
package google
import (
"fmt"
"log"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/dns/v1"
"google.golang.org/api/googleapi"
)
func resourceDnsManagedZone() *schema.Resource {
return &schema.Resource{
Create: resourceDnsManagedZoneCreate,
Read: resourceDnsManagedZoneRead,
Delete: resourceDnsManagedZoneDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"dns_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"name_servers": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
// Google Cloud DNS ManagedZone resources do not have a SelfLink attribute.
},
}
}
func resourceDnsManagedZoneCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
// Build the parameter
zone := &dns.ManagedZone{
Name: d.Get("name").(string),
DnsName: d.Get("dns_name").(string),
}
// Optional things
if v, ok := d.GetOk("description"); ok {
zone.Description = v.(string)
}
if v, ok := d.GetOk("dns_name"); ok {
zone.DnsName = v.(string)
}
log.Printf("[DEBUG] DNS ManagedZone create request: %#v", zone)
zone, err := config.clientDns.ManagedZones.Create(config.Project, zone).Do()
if err != nil {
return fmt.Errorf("Error creating DNS ManagedZone: %s", err)
}
d.SetId(zone.Name)
return resourceDnsManagedZoneRead(d, meta)
}
func resourceDnsManagedZoneRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
zone, err := config.clientDns.ManagedZones.Get(
config.Project, d.Id()).Do()
if err != nil {
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
// The resource doesn't exist anymore
d.SetId("")
return nil
}
return fmt.Errorf("Error reading DNS ManagedZone: %#v", err)
}
d.Set("name_servers", zone.NameServers)
return nil
}
func resourceDnsManagedZoneDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
err := config.clientDns.ManagedZones.Delete(config.Project, d.Id()).Do()
if err != nil {
return fmt.Errorf("Error deleting DNS ManagedZone: %s", err)
}
d.SetId("")
return nil
}

View File

@ -0,0 +1,83 @@
package google
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"google.golang.org/api/dns/v1"
)
func TestAccDnsManagedZone_basic(t *testing.T) {
var zone dns.ManagedZone
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDnsManagedZoneDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDnsManagedZone_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckDnsManagedZoneExists(
"google_dns_managed_zone.foobar", &zone),
),
},
},
})
}
func testAccCheckDnsManagedZoneDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
for _, rs := range s.RootModule().Resources {
if rs.Type != "google_dns_zone" {
continue
}
_, err := config.clientDns.ManagedZones.Get(
config.Project, rs.Primary.ID).Do()
if err == nil {
return fmt.Errorf("DNS ManagedZone still exists")
}
}
return nil
}
func testAccCheckDnsManagedZoneExists(n string, zone *dns.ManagedZone) 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")
}
config := testAccProvider.Meta().(*Config)
found, err := config.clientDns.ManagedZones.Get(
config.Project, rs.Primary.ID).Do()
if err != nil {
return err
}
if found.Name != rs.Primary.ID {
return fmt.Errorf("DNS Zone not found")
}
*zone = *found
return nil
}
}
const testAccDnsManagedZone_basic = `
resource "google_dns_managed_zone" "foobar" {
name = "terraform-test"
dns_name = "terraform.test."
description = "Test Description"
}`

182
resource_dns_record_set.go Normal file
View File

@ -0,0 +1,182 @@
package google
import (
"fmt"
"log"
"time"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/dns/v1"
)
func resourceDnsRecordSet() *schema.Resource {
return &schema.Resource{
Create: resourceDnsRecordSetCreate,
Read: resourceDnsRecordSetRead,
Delete: resourceDnsRecordSetDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"managed_zone": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"ttl": &schema.Schema{
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},
"rrdatas": &schema.Schema{
Type: schema.TypeList,
Required: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}
func resourceDnsRecordSetCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
zone := d.Get("managed_zone").(string)
rrdatasCount := d.Get("rrdatas.#").(int)
// Build the change
chg := &dns.Change{
Additions: []*dns.ResourceRecordSet {
&dns.ResourceRecordSet {
Name: d.Get("name").(string),
Type: d.Get("type").(string),
Ttl: int64(d.Get("ttl").(int)),
Rrdatas: make([]string, rrdatasCount),
},
},
}
for i := 0; i < rrdatasCount ; i++ {
rrdata := fmt.Sprintf("rrdatas.%d", i)
chg.Additions[0].Rrdatas[i] = d.Get(rrdata).(string)
}
log.Printf("[DEBUG] DNS Record create request: %#v", chg)
chg, err := config.clientDns.Changes.Create(config.Project, zone, chg).Do()
if err != nil {
return fmt.Errorf("Error creating DNS RecordSet: %s", err)
}
d.SetId(chg.Id)
w := &DnsChangeWaiter{
Service: config.clientDns,
Change: chg,
Project: config.Project,
ManagedZone: zone,
}
state := w.Conf()
state.Delay = 10 * time.Second
state.Timeout = 10 * time.Minute
state.MinTimeout = 2 * time.Second
_, err = state.WaitForState()
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)
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(
config.Project, zone).Name(name).Type(dnsType).Do()
if err != nil {
return fmt.Errorf("Error reading DNS RecordSet: %#v", err)
}
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))
}
d.Set("ttl", resp.Rrsets[0].Ttl)
d.Set("rrdatas", resp.Rrsets[0].Rrdatas)
return nil
}
func resourceDnsRecordSetDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
zone := d.Get("managed_zone").(string)
rrdatasCount := d.Get("rrdatas.#").(int)
// Build the change
chg := &dns.Change{
Deletions: []*dns.ResourceRecordSet {
&dns.ResourceRecordSet {
Name: d.Get("name").(string),
Type: d.Get("type").(string),
Ttl: int64(d.Get("ttl").(int)),
Rrdatas: make([]string, rrdatasCount),
},
},
}
for i := 0; i < rrdatasCount ; i++ {
rrdata := fmt.Sprintf("rrdatas.%d", i)
chg.Deletions[0].Rrdatas[i] = d.Get(rrdata).(string)
}
log.Printf("[DEBUG] DNS Record delete request: %#v", chg)
chg, err := config.clientDns.Changes.Create(config.Project, zone, chg).Do()
if err != nil {
return fmt.Errorf("Error deleting DNS RecordSet: %s", err)
}
w := &DnsChangeWaiter{
Service: config.clientDns,
Change: chg,
Project: config.Project,
ManagedZone: zone,
}
state := w.Conf()
state.Delay = 10 * time.Second
state.Timeout = 10 * time.Minute
state.MinTimeout = 2 * time.Second
_, err = state.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for Google DNS change: %s", err)
}
d.SetId("")
return nil
}

View File

@ -0,0 +1,92 @@
package google
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccDnsRecordSet_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDnsRecordSetDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDnsRecordSet_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckDnsRecordSetExists(
"google_dns_record_set.foobar"),
),
},
},
})
}
func testAccCheckDnsRecordSetDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
for _, rs := range s.RootModule().Resources {
// Deletion of the managed_zone implies everything is gone
if rs.Type == "google_dns_managed_zone" {
_, err := config.clientDns.ManagedZones.Get(
config.Project, rs.Primary.ID).Do()
if err == nil {
return fmt.Errorf("DNS ManagedZone still exists")
}
}
}
return nil
}
func testAccCheckDnsRecordSetExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
dnsName := rs.Primary.Attributes["name"]
dnsType := rs.Primary.Attributes["type"]
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
config := testAccProvider.Meta().(*Config)
resp, err := config.clientDns.ResourceRecordSets.List(
config.Project, "terraform-test-zone").Name(dnsName).Type(dnsType).Do()
if err != nil {
return fmt.Errorf("Error confirming DNS RecordSet existence: %#v", err)
}
if len(resp.Rrsets) == 0 {
// The resource doesn't exist anymore
return fmt.Errorf("DNS RecordSet not found")
}
if len(resp.Rrsets) > 1 {
return fmt.Errorf("Only expected 1 record set, got %d", len(resp.Rrsets))
}
return nil
}
}
const testAccDnsRecordSet_basic = `
resource "google_dns_managed_zone" "parent-zone" {
name = "terraform-test-zone"
dns_name = "terraform.test."
description = "Test Description"
}
resource "google_dns_record_set" "foobar" {
managed_zone = "${google_dns_managed_zone.parent-zone.name}"
name = "test-record.terraform.test."
type = "A"
rrdatas = ["127.0.0.1", "127.0.0.10"]
ttl = 600
}
`