Merge pull request #1748 from sparkprime/cloud_dns_pr

Support Google Cloud DNS, Fix #1148
This commit is contained in:
Mitchell Hashimoto 2015-04-29 22:59:35 -07:00
commit 59275301b7
10 changed files with 639 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
}

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

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
}
`

View File

@ -0,0 +1,42 @@
---
layout: "google"
page_title: "Google: google_dns_managed_zone"
sidebar_current: "docs-google-resource-dns-managed-zone"
description: |-
Manages a zone within Google Cloud DNS.
---
# google\_dns\_managed_zone
Manages a zone within Google Cloud DNS.
## Example Usage
```
resource "google_dns_managed_zone" "prod" {
name = "prod-zone"
dns_name = "prod.mydomain.com."
description = "Production DNS zone"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) A unique name for the resource, required by GCE.
Changing this forces a new resource to be created.
* `dns_name` - (Required) The DNS name of this zone, e.g. "terraform.io".
* `description` - (Optional) A textual description field.
## Attributes Reference
The following attributes are exported:
* `name` - The name of the resource.
* `dns_name` - The DNS name of this zone.
* `name_servers` - The list of nameservers that will be authoritative for this
domain. Use NS records to redirect from your DNS provider to these names,
thus making Google Cloud DNS authoritative for this zone.

View File

@ -0,0 +1,64 @@
---
layout: "google"
page_title: "Google: google_dns_record_set"
sidebar_current: "docs-google-dns-record-set"
description: |-
Manages a set of DNS records within Google Cloud DNS.
---
# google\_dns\_record\_set
Manages a set of DNS records within Google Cloud DNS.
## Example Usage
This example is the common case of binding a DNS name to the ephemeral IP of a new instance:
```
resource "google_compute_instance" "frontend" {
name = "frontend"
machine_type = "g1-small"
zone = "us-central1-b"
disk {
image = "debian-7-wheezy-v20140814"
}
network_interface {
network = "default"
access_config {
}
}
}
resource "google_dns_managed_zone" "prod" {
name = "prod-zone"
dns_name = "prod.mydomain.com."
}
resource "google_dns_record_set" "frontend" {
managed_zone = "${google_dns_managed_zone.prod.name}"
name = "frontend.${google_dns_managed_zone.prod.dns_name}"
type = "A"
ttl = 300
rrdatas = ["${google_compute_instance.frontend.network_interface.0.access_config.0.nat_ip}"]
}
```
## Argument Reference
The following arguments are supported:
* `managed_zone` - (Required) The name of the zone in which this record set will reside.
* `name` - (Required) The DNS name this record set will apply to.
* `type` - (Required) The DNS record set type.
* `ttl` - (Required) The time-to-live of this record set (seconds).
* `rrdatas` - (Required) The string data for the records in this record set
whose meaning depends on the DNS type.
## Attributes Reference
All arguments are available as attributes.

View File

@ -52,6 +52,14 @@
<li<%= sidebar_current("docs-google-resource-target-pool") %>>
<a href="/docs/providers/google/r/compute_target_pool.html">google_compute_target_pool</a>
</li>
<li<%= sidebar_current("docs-google-resource-dns-managed-zone") %>>
<a href="/docs/providers/google/r/dns_managed_zone.html">google_dns_managed_zone</a>
</li>
<li<%= sidebar_current("docs-google-resource-dns-record-set") %>>
<a href="/docs/providers/google/r/dns_record_set.html">google_dns_record_set</a>
</li>
</ul>
</li>
</ul>