Merge branch 'master' into paddy_10984_better_image_resolution

This commit is contained in:
Paddy 2017-02-23 15:12:47 -08:00
commit a720f10b27
23 changed files with 1114 additions and 63 deletions

View File

@ -13,6 +13,7 @@ import (
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
"google.golang.org/api/cloudbilling/v1"
"google.golang.org/api/cloudresourcemanager/v1"
"google.golang.org/api/compute/v1"
"google.golang.org/api/container/v1"
@ -31,6 +32,7 @@ type Config struct {
Project string
Region string
clientBilling *cloudbilling.Service
clientCompute *compute.Service
clientContainer *container.Service
clientDns *dns.Service
@ -160,6 +162,13 @@ func (c *Config) loadAndValidate() error {
}
c.clientServiceMan.UserAgent = userAgent
log.Printf("[INFO] Instantiating Google Cloud Billing Client...")
c.clientBilling, err = cloudbilling.New(client)
if err != nil {
return err
}
c.clientBilling.UserAgent = userAgent
return nil
}

View File

@ -0,0 +1,80 @@
package google
import (
"fmt"
"log"
"sort"
"time"
"github.com/hashicorp/terraform/helper/schema"
compute "google.golang.org/api/compute/v1"
)
func dataSourceGoogleComputeZones() *schema.Resource {
return &schema.Resource{
Read: dataSourceGoogleComputeZonesRead,
Schema: map[string]*schema.Schema{
"region": {
Type: schema.TypeString,
Optional: true,
},
"names": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"status": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if value != "UP" && value != "DOWN" {
es = append(es, fmt.Errorf("%q can only be 'UP' or 'DOWN' (%q given)", k, value))
}
return
},
},
},
}
}
func dataSourceGoogleComputeZonesRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
region := config.Region
if r, ok := d.GetOk("region"); ok {
region = r.(string)
}
regionUrl := fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/regions/%s",
config.Project, region)
filter := fmt.Sprintf("(region eq %s)", regionUrl)
if s, ok := d.GetOk("status"); ok {
filter += fmt.Sprintf(" (status eq %s)", s)
}
call := config.clientCompute.Zones.List(config.Project).Filter(filter)
resp, err := call.Do()
if err != nil {
return err
}
zones := flattenZones(resp.Items)
log.Printf("[DEBUG] Received Google Compute Zones: %q", zones)
d.Set("names", zones)
d.SetId(time.Now().UTC().String())
return nil
}
func flattenZones(zones []*compute.Zone) []string {
result := make([]string, len(zones), len(zones))
for i, zone := range zones {
result[i] = zone.Name
}
sort.Strings(result)
return result
}

View File

@ -0,0 +1,70 @@
package google
import (
"errors"
"fmt"
"strconv"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccGoogleComputeZones_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckGoogleComputeZonesConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleComputeZonesMeta("data.google_compute_zones.available"),
),
},
},
})
}
func testAccCheckGoogleComputeZonesMeta(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Can't find zones data source: %s", n)
}
if rs.Primary.ID == "" {
return errors.New("zones data source ID not set.")
}
count, ok := rs.Primary.Attributes["names.#"]
if !ok {
return errors.New("can't find 'names' attribute")
}
noOfNames, err := strconv.Atoi(count)
if err != nil {
return errors.New("failed to read number of zones")
}
if noOfNames < 2 {
return fmt.Errorf("expected at least 2 zones, received %d, this is most likely a bug",
noOfNames)
}
for i := 0; i < noOfNames; i++ {
idx := "names." + strconv.Itoa(i)
v, ok := rs.Primary.Attributes[idx]
if !ok {
return fmt.Errorf("zone list is corrupt (%q not found), this is definitely a bug", idx)
}
if len(v) < 1 {
return fmt.Errorf("Empty zone name (%q), this is definitely a bug", idx)
}
}
return nil
}
}
var testAccCheckGoogleComputeZonesConfig = `
data "google_compute_zones" "available" {}
`

View File

@ -57,7 +57,8 @@ func Provider() terraform.ResourceProvider {
},
DataSourcesMap: map[string]*schema.Resource{
"google_iam_policy": dataSourceGoogleIamPolicy(),
"google_iam_policy": dataSourceGoogleIamPolicy(),
"google_compute_zones": dataSourceGoogleComputeZones(),
},
ResourcesMap: map[string]*schema.Resource{

View File

@ -671,6 +671,10 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
d.Set("can_ip_forward", instance.CanIpForward)
machineTypeResource := strings.Split(instance.MachineType, "/")
machineType := machineTypeResource[len(machineTypeResource)-1]
d.Set("machine_type", machineType)
// Set the service accounts
serviceAccounts := make([]map[string]interface{}, 0, 1)
for _, serviceAccount := range instance.ServiceAccounts {
@ -790,13 +794,14 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
disks := make([]map[string]interface{}, 0, 1)
for i, disk := range instance.Disks {
di := map[string]interface{}{
"disk": d.Get(fmt.Sprintf("disk.%d.disk", i)),
"image": d.Get(fmt.Sprintf("disk.%d.image", i)),
"type": d.Get(fmt.Sprintf("disk.%d.type", i)),
"scratch": d.Get(fmt.Sprintf("disk.%d.scratch", i)),
"auto_delete": d.Get(fmt.Sprintf("disk.%d.auto_delete", i)),
"size": d.Get(fmt.Sprintf("disk.%d.size", i)),
"device_name": d.Get(fmt.Sprintf("disk.%d.device_name", i)),
"disk": d.Get(fmt.Sprintf("disk.%d.disk", i)),
"image": d.Get(fmt.Sprintf("disk.%d.image", i)),
"type": d.Get(fmt.Sprintf("disk.%d.type", i)),
"scratch": d.Get(fmt.Sprintf("disk.%d.scratch", i)),
"auto_delete": d.Get(fmt.Sprintf("disk.%d.auto_delete", i)),
"size": d.Get(fmt.Sprintf("disk.%d.size", i)),
"device_name": d.Get(fmt.Sprintf("disk.%d.device_name", i)),
"disk_encryption_key_raw": d.Get(fmt.Sprintf("disk.%d.disk_encryption_key_raw", i)),
}
if disk.DiskEncryptionKey != nil && disk.DiskEncryptionKey.Sha256 != "" {
di["disk_encryption_key_sha256"] = disk.DiskEncryptionKey.Sha256

View File

@ -216,17 +216,33 @@ func resourceComputeInstanceGroupManagerRead(d *schema.ResourceData, meta interf
return config.clientCompute.InstanceGroupManagers.Get(project, zone, d.Id()).Do()
}
resource, err := getZonalResourceFromRegion(getInstanceGroupManager, region, config.clientCompute, project)
if err != nil {
return err
var manager *compute.InstanceGroupManager
var e error
if zone, ok := d.GetOk("zone"); ok {
manager, e = config.clientCompute.InstanceGroupManagers.Get(project, zone.(string), d.Id()).Do()
if e != nil {
return e
}
} else {
// If the resource was imported, the only info we have is the ID. Try to find the resource
// by searching in the region of the project.
var resource interface{}
resource, e = getZonalResourceFromRegion(getInstanceGroupManager, region, config.clientCompute, project)
if e != nil {
return e
}
manager = resource.(*compute.InstanceGroupManager)
}
if resource == nil {
if manager == nil {
log.Printf("[WARN] Removing Instance Group Manager %q because it's gone", d.Get("name").(string))
// The resource doesn't exist anymore
d.SetId("")
return nil
}
manager := resource.(*compute.InstanceGroupManager)
zoneUrl := strings.Split(manager.Zone, "/")
d.Set("base_instance_name", manager.BaseInstanceName)

View File

@ -135,6 +135,30 @@ func TestAccInstanceGroupManager_updateStrategy(t *testing.T) {
})
}
func TestAccInstanceGroupManager_separateRegions(t *testing.T) {
var manager compute.InstanceGroupManager
igm1 := fmt.Sprintf("igm-test-%s", acctest.RandString(10))
igm2 := fmt.Sprintf("igm-test-%s", acctest.RandString(10))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceGroupManagerDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccInstanceGroupManager_separateRegions(igm1, igm2),
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceGroupManagerExists(
"google_compute_instance_group_manager.igm-basic", &manager),
testAccCheckInstanceGroupManagerExists(
"google_compute_instance_group_manager.igm-basic-2", &manager),
),
},
},
})
}
func testAccCheckInstanceGroupManagerDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
@ -571,6 +595,52 @@ func testAccInstanceGroupManager_updateStrategy(igm string) string {
}`, igm)
}
func testAccInstanceGroupManager_separateRegions(igm1, igm2 string) string {
return fmt.Sprintf(`
resource "google_compute_instance_template" "igm-basic" {
machine_type = "n1-standard-1"
can_ip_forward = false
tags = ["foo", "bar"]
disk {
source_image = "debian-cloud/debian-8-jessie-v20160803"
auto_delete = true
boot = true
}
network_interface {
network = "default"
}
metadata {
foo = "bar"
}
service_account {
scopes = ["userinfo-email", "compute-ro", "storage-ro"]
}
}
resource "google_compute_instance_group_manager" "igm-basic" {
description = "Terraform test instance group manager"
name = "%s"
instance_template = "${google_compute_instance_template.igm-basic.self_link}"
base_instance_name = "igm-basic"
zone = "us-central1-c"
target_size = 2
}
resource "google_compute_instance_group_manager" "igm-basic-2" {
description = "Terraform test instance group manager"
name = "%s"
instance_template = "${google_compute_instance_template.igm-basic.self_link}"
base_instance_name = "igm-basic-2"
zone = "us-west1-b"
target_size = 2
}
`, igm1, igm2)
}
func resourceSplitter(resource string) string {
splits := strings.Split(resource, "/")

View File

@ -207,11 +207,13 @@ func resourceComputeInstanceTemplate() *schema.Resource {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"access_config": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"nat_ip": &schema.Schema{

View File

@ -547,6 +547,66 @@ func TestAccComputeInstance_invalid_disk(t *testing.T) {
})
}
func TestAccComputeInstance_forceChangeMachineTypeManually(t *testing.T) {
var instance compute.Instance
var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeInstance_basic(instanceName),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeInstanceExists("google_compute_instance.foobar", &instance),
testAccCheckComputeInstanceUpdateMachineType("google_compute_instance.foobar"),
),
ExpectNonEmptyPlan: true,
},
},
})
}
func testAccCheckComputeInstanceUpdateMachineType(n 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")
}
config := testAccProvider.Meta().(*Config)
op, err := config.clientCompute.Instances.Stop(config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do()
if err != nil {
return fmt.Errorf("Could not stop instance: %s", err)
}
err = computeOperationWaitZone(config, op, config.Project, rs.Primary.Attributes["zone"], "Waiting on stop")
if err != nil {
return fmt.Errorf("Could not stop instance: %s", err)
}
machineType := compute.InstancesSetMachineTypeRequest{
MachineType: "zones/us-central1-a/machineTypes/f1-micro",
}
op, err = config.clientCompute.Instances.SetMachineType(
config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID, &machineType).Do()
if err != nil {
return fmt.Errorf("Could not change machine type: %s", err)
}
err = computeOperationWaitZone(config, op, config.Project, rs.Primary.Attributes["zone"], "Waiting machine type change")
if err != nil {
return fmt.Errorf("Could not change machine type: %s", err)
}
return nil
}
}
func testAccCheckComputeInstanceDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)

View File

@ -192,6 +192,10 @@ func resourceComputeProjectMetadataDelete(d *schema.ResourceData, meta interface
op, err := config.clientCompute.Projects.SetCommonInstanceMetadata(projectID, md).Do()
if err != nil {
return fmt.Errorf("Error removing metadata from project %s: %s", projectID, err)
}
log.Printf("[DEBUG] SetCommonMetadata: %d (%s)", op.Id, op.SelfLink)
err = computeOperationWaitGlobal(config, op, project.Name, "SetCommonMetadata")

View File

@ -65,6 +65,15 @@ func resourceComputeVpnTunnel() *schema.Resource {
},
"local_traffic_selector": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"remote_traffic_selector": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
@ -124,15 +133,24 @@ func resourceComputeVpnTunnelCreate(d *schema.ResourceData, meta interface{}) er
}
}
var remoteTrafficSelectors []string
if v := d.Get("remote_traffic_selector").(*schema.Set); v.Len() > 0 {
remoteTrafficSelectors = make([]string, v.Len())
for i, v := range v.List() {
remoteTrafficSelectors[i] = v.(string)
}
}
vpnTunnelsService := compute.NewVpnTunnelsService(config.clientCompute)
vpnTunnel := &compute.VpnTunnel{
Name: name,
PeerIp: peerIp,
SharedSecret: sharedSecret,
TargetVpnGateway: targetVpnGateway,
IkeVersion: int64(ikeVersion),
LocalTrafficSelector: localTrafficSelectors,
Name: name,
PeerIp: peerIp,
SharedSecret: sharedSecret,
TargetVpnGateway: targetVpnGateway,
IkeVersion: int64(ikeVersion),
LocalTrafficSelector: localTrafficSelectors,
RemoteTrafficSelector: remoteTrafficSelectors,
}
if v, ok := d.GetOk("description"); ok {
@ -182,6 +200,18 @@ func resourceComputeVpnTunnelRead(d *schema.ResourceData, meta interface{}) erro
return fmt.Errorf("Error Reading VPN Tunnel %s: %s", name, err)
}
localTrafficSelectors := []string{}
for _, lts := range vpnTunnel.LocalTrafficSelector {
localTrafficSelectors = append(localTrafficSelectors, lts)
}
d.Set("local_traffic_selector", localTrafficSelectors)
remoteTrafficSelectors := []string{}
for _, rts := range vpnTunnel.RemoteTrafficSelector {
remoteTrafficSelectors = append(remoteTrafficSelectors, rts)
}
d.Set("remote_traffic_selector", remoteTrafficSelectors)
d.Set("detailed_status", vpnTunnel.DetailedStatus)
d.Set("self_link", vpnTunnel.SelfLink)

View File

@ -22,12 +22,32 @@ func TestAccComputeVpnTunnel_basic(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeVpnTunnelExists(
"google_compute_vpn_tunnel.foobar"),
resource.TestCheckResourceAttr(
"google_compute_vpn_tunnel.foobar", "local_traffic_selector.#", "1"),
resource.TestCheckResourceAttr(
"google_compute_vpn_tunnel.foobar", "remote_traffic_selector.#", "2"),
),
},
},
})
}
func TestAccComputeVpnTunnel_defaultTrafficSelectors(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeVpnTunnelDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeVpnTunnelDefaultTrafficSelectors,
Check: testAccCheckComputeVpnTunnelExists(
"google_compute_vpn_tunnel.foobar"),
},
},
})
}
func testAccCheckComputeVpnTunnelDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
project := config.Project
@ -83,7 +103,61 @@ func testAccCheckComputeVpnTunnelExists(n string) resource.TestCheckFunc {
var testAccComputeVpnTunnel_basic = fmt.Sprintf(`
resource "google_compute_network" "foobar" {
name = "tunnel-test-%s"
ipv4_range = "10.0.0.0/16"
}
resource "google_compute_subnetwork" "foobar" {
name = "tunnel-test-%s"
network = "${google_compute_network.foobar.self_link}"
ip_cidr_range = "10.0.0.0/16"
region = "us-central1"
}
resource "google_compute_address" "foobar" {
name = "tunnel-test-%s"
region = "${google_compute_subnetwork.foobar.region}"
}
resource "google_compute_vpn_gateway" "foobar" {
name = "tunnel-test-%s"
network = "${google_compute_network.foobar.self_link}"
region = "${google_compute_subnetwork.foobar.region}"
}
resource "google_compute_forwarding_rule" "foobar_esp" {
name = "tunnel-test-%s"
region = "${google_compute_vpn_gateway.foobar.region}"
ip_protocol = "ESP"
ip_address = "${google_compute_address.foobar.address}"
target = "${google_compute_vpn_gateway.foobar.self_link}"
}
resource "google_compute_forwarding_rule" "foobar_udp500" {
name = "tunnel-test-%s"
region = "${google_compute_forwarding_rule.foobar_esp.region}"
ip_protocol = "UDP"
port_range = "500-500"
ip_address = "${google_compute_address.foobar.address}"
target = "${google_compute_vpn_gateway.foobar.self_link}"
}
resource "google_compute_forwarding_rule" "foobar_udp4500" {
name = "tunnel-test-%s"
region = "${google_compute_forwarding_rule.foobar_udp500.region}"
ip_protocol = "UDP"
port_range = "4500-4500"
ip_address = "${google_compute_address.foobar.address}"
target = "${google_compute_vpn_gateway.foobar.self_link}"
}
resource "google_compute_vpn_tunnel" "foobar" {
name = "tunnel-test-%s"
region = "${google_compute_forwarding_rule.foobar_udp4500.region}"
target_vpn_gateway = "${google_compute_vpn_gateway.foobar.self_link}"
shared_secret = "unguessable"
peer_ip = "8.8.8.8"
local_traffic_selector = ["${google_compute_subnetwork.foobar.ip_cidr_range}"]
remote_traffic_selector = ["192.168.0.0/24", "192.168.1.0/24"]
}`, acctest.RandString(10), acctest.RandString(10), acctest.RandString(10),
acctest.RandString(10), acctest.RandString(10), acctest.RandString(10),
acctest.RandString(10), acctest.RandString(10))
var testAccComputeVpnTunnelDefaultTrafficSelectors = fmt.Sprintf(`
resource "google_compute_network" "foobar" {
name = "tunnel-test-%s"
auto_create_subnetworks = "true"
}
resource "google_compute_address" "foobar" {
name = "tunnel-test-%s"

View File

@ -95,6 +95,7 @@ func resourceContainerCluster() *schema.Resource {
"additional_zones": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
@ -292,18 +293,14 @@ func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) er
if v, ok := d.GetOk("additional_zones"); ok {
locationsList := v.([]interface{})
locations := []string{}
zoneInLocations := false
for _, v := range locationsList {
location := v.(string)
locations = append(locations, location)
if location == zoneName {
zoneInLocations = true
return fmt.Errorf("additional_zones should not contain the original 'zone'.")
}
}
if !zoneInLocations {
// zone must be in locations if specified separately
locations = append(locations, zoneName)
}
locations = append(locations, zoneName)
cluster.Locations = locations
}
@ -444,7 +441,17 @@ func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) erro
d.Set("name", cluster.Name)
d.Set("zone", cluster.Zone)
d.Set("additional_zones", cluster.Locations)
locations := []string{}
if len(cluster.Locations) > 1 {
for _, location := range cluster.Locations {
if location != cluster.Zone {
locations = append(locations, location)
}
}
}
d.Set("additional_zones", locations)
d.Set("endpoint", cluster.Endpoint)
masterAuth := []map[string]interface{}{

View File

@ -39,7 +39,7 @@ func TestAccContainerCluster_withAdditionalZones(t *testing.T) {
testAccCheckContainerClusterExists(
"google_container_cluster.with_additional_zones"),
testAccCheckContainerClusterAdditionalZonesExist(
"google_container_cluster.with_additional_zones"),
"google_container_cluster.with_additional_zones", 2),
),
},
},
@ -163,23 +163,19 @@ func testAccCheckContainerClusterExists(n string) resource.TestCheckFunc {
}
}
func testAccCheckContainerClusterAdditionalZonesExist(n string) resource.TestCheckFunc {
func testAccCheckContainerClusterAdditionalZonesExist(n string, num int) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
var (
additionalZonesSize int
err error
)
if additionalZonesSize, err = strconv.Atoi(rs.Primary.Attributes["additional_zones.#"]); err != nil {
additionalZonesSize, err := strconv.Atoi(rs.Primary.Attributes["additional_zones.#"])
if err != nil {
return err
}
if additionalZonesSize != 2 {
return fmt.Errorf("number of additional zones did not match 2")
if additionalZonesSize != num {
return fmt.Errorf("number of additional zones did not match %d, was %d", num, additionalZonesSize)
}
return nil
@ -219,7 +215,7 @@ var testAccContainerCluster_withVersion = fmt.Sprintf(`
resource "google_container_cluster" "with_version" {
name = "cluster-test-%s"
zone = "us-central1-a"
node_version = "1.4.7"
node_version = "1.5.2"
initial_node_count = 1
master_auth {

View File

@ -79,5 +79,5 @@ func testAccCheckDnsManagedZoneExists(n string, zone *dns.ManagedZone) resource.
var testAccDnsManagedZone_basic = fmt.Sprintf(`
resource "google_dns_managed_zone" "foobar" {
name = "mzone-test-%s"
dns_name = "terraform.test."
dns_name = "hashicorptest.com."
}`, acctest.RandString(10))

View File

@ -138,12 +138,12 @@ func testAccDnsRecordSet_basic(zoneName string, addr2 string, ttl int) string {
return fmt.Sprintf(`
resource "google_dns_managed_zone" "parent-zone" {
name = "%s"
dns_name = "terraform.test."
dns_name = "hashicorptest.com."
description = "Test Description"
}
resource "google_dns_record_set" "foobar" {
managed_zone = "${google_dns_managed_zone.parent-zone.name}"
name = "test-record.terraform.test."
name = "test-record.hashicorptest.com."
type = "A"
rrdatas = ["127.0.0.1", "%s"]
ttl = %d
@ -155,12 +155,12 @@ func testAccDnsRecordSet_bigChange(zoneName string, ttl int) string {
return fmt.Sprintf(`
resource "google_dns_managed_zone" "parent-zone" {
name = "%s"
dns_name = "terraform.test."
dns_name = "hashicorptest.com."
description = "Test Description"
}
resource "google_dns_record_set" "foobar" {
managed_zone = "${google_dns_managed_zone.parent-zone.name}"
name = "test-record.terraform.test."
name = "test-record.hashicorptest.com."
type = "CNAME"
rrdatas = ["www.terraform.io."]
ttl = %d

View File

@ -6,8 +6,10 @@ import (
"log"
"net/http"
"strconv"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudbilling/v1"
"google.golang.org/api/cloudresourcemanager/v1"
"google.golang.org/api/googleapi"
)
@ -86,6 +88,10 @@ func resourceGoogleProject() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"billing_account": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
}
}
@ -172,6 +178,22 @@ func resourceGoogleProjectCreate(d *schema.ResourceData, meta interface{}) error
}
}
// Set the billing account
if v, ok := d.GetOk("billing_account"); ok {
name := v.(string)
ba := cloudbilling.ProjectBillingInfo{
BillingAccountName: "billingAccounts/" + name,
}
_, err = config.clientBilling.Projects.UpdateBillingInfo(prefixedProject(pid), &ba).Do()
if err != nil {
d.Set("billing_account", "")
if _err, ok := err.(*googleapi.Error); ok {
return fmt.Errorf("Error setting billing account %q for project %q: %v", name, prefixedProject(pid), _err)
}
return fmt.Errorf("Error setting billing account %q for project %q: %v", name, prefixedProject(pid), err)
}
}
return resourceGoogleProjectRead(d, meta)
}
@ -196,23 +218,30 @@ func resourceGoogleProjectRead(d *schema.ResourceData, meta interface{}) error {
d.Set("org_id", p.Parent.Id)
}
// Read the IAM policy
pol, err := getProjectIamPolicy(pid, config)
// Read the billing account
ba, err := config.clientBilling.Projects.GetBillingInfo(prefixedProject(pid)).Do()
if err != nil {
return err
return fmt.Errorf("Error reading billing account for project %q: %v", prefixedProject(pid), err)
}
polBytes, err := json.Marshal(pol)
if err != nil {
return err
if ba.BillingAccountName != "" {
// BillingAccountName is contains the resource name of the billing account
// associated with the project, if any. For example,
// `billingAccounts/012345-567890-ABCDEF`. We care about the ID and not
// the `billingAccounts/` prefix, so we need to remove that. If the
// prefix ever changes, we'll validate to make sure it's something we
// recognize.
_ba := strings.TrimPrefix(ba.BillingAccountName, "billingAccounts/")
if ba.BillingAccountName == _ba {
return fmt.Errorf("Error parsing billing account for project %q. Expected value to begin with 'billingAccounts/' but got %s", prefixedProject(pid), ba.BillingAccountName)
}
d.Set("billing_account", _ba)
}
d.Set("policy_etag", pol.Etag)
d.Set("policy_data", string(polBytes))
return nil
}
func prefixedProject(pid string) string {
return "projects/" + pid
}
func resourceGoogleProjectUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pid := d.Id()
@ -238,6 +267,21 @@ func resourceGoogleProjectUpdate(d *schema.ResourceData, meta interface{}) error
}
}
// Billing account has changed
if ok := d.HasChange("billing_account"); ok {
name := d.Get("billing_account").(string)
ba := cloudbilling.ProjectBillingInfo{
BillingAccountName: "billingAccounts/" + name,
}
_, err = config.clientBilling.Projects.UpdateBillingInfo(prefixedProject(pid), &ba).Do()
if err != nil {
d.Set("billing_account", "")
if _err, ok := err.(*googleapi.Error); ok {
return fmt.Errorf("Error updating billing account %q for project %q: %v", name, prefixedProject(pid), _err)
}
return fmt.Errorf("Error updating billing account %q for project %q: %v", name, prefixedProject(pid), err)
}
}
return updateProjectIamPolicy(d, config, pid)
}

View File

@ -257,7 +257,7 @@ func setProjectIamPolicy(policy *cloudresourcemanager.Policy, config *Config, pi
&cloudresourcemanager.SetIamPolicyRequest{Policy: policy}).Do()
if err != nil {
return fmt.Errorf("Error applying IAM policy for project %q. Policy is %+s, error is %s", pid, policy, err)
return fmt.Errorf("Error applying IAM policy for project %q. Policy is %#v, error is %s", pid, policy, err)
}
return nil
}

View File

@ -624,3 +624,13 @@ resource "google_project" "acceptance" {
org_id = "%s"
}`, pid, name, org)
}
func testAccGoogleProject_createBilling(pid, name, org, billing string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
billing_account = "%s"
}`, pid, name, org, billing)
}

View File

@ -3,6 +3,7 @@ package google
import (
"fmt"
"os"
"strings"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
@ -48,6 +49,104 @@ func TestAccGoogleProject_create(t *testing.T) {
})
}
// Test that a Project resource can be created with an associated
// billing account
func TestAccGoogleProject_createBilling(t *testing.T) {
skipIfEnvNotSet(t,
[]string{
"GOOGLE_ORG",
"GOOGLE_BILLING_ACCOUNT",
}...,
)
billingId := os.Getenv("GOOGLE_BILLING_ACCOUNT")
pid := "terraform-" + acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
// This step creates a new project with a billing account
resource.TestStep{
Config: testAccGoogleProject_createBilling(pid, pname, org, billingId),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectHasBillingAccount("google_project.acceptance", pid, billingId),
),
},
},
})
}
// Test that a Project resource can be created and updated
// with billing account information
func TestAccGoogleProject_updateBilling(t *testing.T) {
skipIfEnvNotSet(t,
[]string{
"GOOGLE_ORG",
"GOOGLE_BILLING_ACCOUNT",
"GOOGLE_BILLING_ACCOUNT_2",
}...,
)
billingId := os.Getenv("GOOGLE_BILLING_ACCOUNT")
billingId2 := os.Getenv("GOOGLE_BILLING_ACCOUNT_2")
pid := "terraform-" + acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
// This step creates a new project without a billing account
resource.TestStep{
Config: testAccGoogleProject_create(pid, pname, org),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectExists("google_project.acceptance", pid),
),
},
// Update to include a billing account
resource.TestStep{
Config: testAccGoogleProject_createBilling(pid, pname, org, billingId),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectHasBillingAccount("google_project.acceptance", pid, billingId),
),
},
// Update to a different billing account
resource.TestStep{
Config: testAccGoogleProject_createBilling(pid, pname, org, billingId2),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectHasBillingAccount("google_project.acceptance", pid, billingId2),
),
},
},
})
}
// Test that a Project resource merges the IAM policies that already
// exist, and won't lock people out.
func TestAccGoogleProject_merge(t *testing.T) {
pid := "terraform-" + acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
// when policy_data is set, merge
{
Config: testAccGoogleProject_toMerge(pid, pname, org),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectExists("google_project.acceptance", pid),
testAccCheckGoogleProjectHasMoreBindingsThan(pid, 1),
),
},
// when policy_data is unset, restore to what it was
{
Config: testAccGoogleProject_mergeEmpty(pid, pname, org),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectExists("google_project.acceptance", pid),
testAccCheckGoogleProjectHasMoreBindingsThan(pid, 0),
),
},
},
})
}
func testAccCheckGoogleProjectExists(r, pid string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[r]
@ -67,6 +166,45 @@ func testAccCheckGoogleProjectExists(r, pid string) resource.TestCheckFunc {
}
}
func testAccCheckGoogleProjectHasBillingAccount(r, pid, billingId string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[r]
if !ok {
return fmt.Errorf("Not found: %s", r)
}
// State should match expected
if rs.Primary.Attributes["billing_account"] != billingId {
return fmt.Errorf("Billing ID in state (%s) does not match expected value (%s)", rs.Primary.Attributes["billing_account"], billingId)
}
// Actual value in API should match state and expected
// Read the billing account
config := testAccProvider.Meta().(*Config)
ba, err := config.clientBilling.Projects.GetBillingInfo(prefixedProject(pid)).Do()
if err != nil {
return fmt.Errorf("Error reading billing account for project %q: %v", prefixedProject(pid), err)
}
if billingId != strings.TrimPrefix(ba.BillingAccountName, "billingAccounts/") {
return fmt.Errorf("Billing ID returned by API (%s) did not match expected value (%s)", ba.BillingAccountName, billingId)
}
return nil
}
}
func testAccCheckGoogleProjectHasMoreBindingsThan(pid string, count int) resource.TestCheckFunc {
return func(s *terraform.State) error {
policy, err := getProjectIamPolicy(pid, testAccProvider.Meta().(*Config))
if err != nil {
return err
}
if len(policy.Bindings) <= count {
return fmt.Errorf("Expected more than %d bindings, got %d: %#v", count, len(policy.Bindings), policy.Bindings)
}
return nil
}
}
func testAccGoogleProjectImportExisting(pid string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
@ -98,3 +236,39 @@ data "google_iam_policy" "admin" {
}
}`, pid)
}
func testAccGoogleProject_toMerge(pid, name, org string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
policy_data = "${data.google_iam_policy.acceptance.policy_data}"
}
data "google_iam_policy" "acceptance" {
binding {
role = "roles/storage.objectViewer"
members = [
"user:evanbrown@google.com",
]
}
}`, pid, name, org)
}
func testAccGoogleProject_mergeEmpty(pid, name, org string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
}`, pid, name, org)
}
func skipIfEnvNotSet(t *testing.T, envs ...string) {
for _, k := range envs {
if os.Getenv(k) == "" {
t.Skipf("Environment variable %s is not set", k)
}
}
}

View File

@ -3,6 +3,7 @@ package google
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
@ -70,6 +71,7 @@ func resourceSqlDatabaseInstance() *schema.Resource {
"crash_safe_replication": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"database_flags": &schema.Schema{
Type: schema.TypeList,
@ -87,6 +89,18 @@ func resourceSqlDatabaseInstance() *schema.Resource {
},
},
},
"disk_autoresize": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"disk_size": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"disk_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"ip_configuration": &schema.Schema{
Type: schema.TypeList,
Optional: true,
@ -139,6 +153,33 @@ func resourceSqlDatabaseInstance() *schema.Resource {
},
},
},
"maintenance_window": &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"day": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
return validateNumericRange(v, k, 1, 7)
},
},
"hour": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
return validateNumericRange(v, k, 0, 23)
},
},
"update_track": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
},
},
"pricing_plan": &schema.Schema{
Type: schema.TypeString,
Optional: true,
@ -323,6 +364,18 @@ func resourceSqlDatabaseInstanceCreate(d *schema.ResourceData, meta interface{})
settings.CrashSafeReplicationEnabled = v.(bool)
}
if v, ok := _settings["disk_autoresize"]; ok && v.(bool) {
settings.StorageAutoResize = v.(bool)
}
if v, ok := _settings["disk_size"]; ok && v.(int) > 0 {
settings.DataDiskSizeGb = int64(v.(int))
}
if v, ok := _settings["disk_type"]; ok && len(v.(string)) > 0 {
settings.DataDiskType = v.(string)
}
if v, ok := _settings["database_flags"]; ok {
settings.DatabaseFlags = make([]*sqladmin.DatabaseFlags, 0)
_databaseFlagsList := v.([]interface{})
@ -405,6 +458,25 @@ func resourceSqlDatabaseInstanceCreate(d *schema.ResourceData, meta interface{})
}
}
if v, ok := _settings["maintenance_window"]; ok && len(v.([]interface{})) > 0 {
settings.MaintenanceWindow = &sqladmin.MaintenanceWindow{}
_maintenanceWindow := v.([]interface{})[0].(map[string]interface{})
if vp, okp := _maintenanceWindow["day"]; okp {
settings.MaintenanceWindow.Day = int64(vp.(int))
}
if vp, okp := _maintenanceWindow["hour"]; okp {
settings.MaintenanceWindow.Hour = int64(vp.(int))
}
if vp, ok := _maintenanceWindow["update_track"]; ok {
if len(vp.(string)) > 0 {
settings.MaintenanceWindow.UpdateTrack = vp.(string)
}
}
}
if v, ok := _settings["pricing_plan"]; ok {
settings.PricingPlan = v.(string)
}
@ -500,7 +572,30 @@ func resourceSqlDatabaseInstanceCreate(d *schema.ResourceData, meta interface{})
return err
}
return resourceSqlDatabaseInstanceRead(d, meta)
err = resourceSqlDatabaseInstanceRead(d, meta)
if err != nil {
return err
}
// If a root user exists with a wildcard ('%') hostname, delete it.
users, err := config.clientSqlAdmin.Users.List(project, instance.Name).Do()
if err != nil {
return fmt.Errorf("Error, attempting to list users associated with instance %s: %s", instance.Name, err)
}
for _, u := range users.Items {
if u.Name == "root" && u.Host == "%" {
op, err = config.clientSqlAdmin.Users.Delete(project, instance.Name, u.Host, u.Name).Do()
if err != nil {
return fmt.Errorf("Error, failed to delete default 'root'@'*' user, but the database was created successfully: %s", err)
}
err = sqladminOperationWait(config, op, "Delete default root User")
if err != nil {
return err
}
}
}
return nil
}
func resourceSqlDatabaseInstanceRead(d *schema.ResourceData, meta interface{}) error {
@ -564,7 +659,7 @@ func resourceSqlDatabaseInstanceRead(d *schema.ResourceData, meta interface{}) e
_backupConfiguration["enabled"] = settings.BackupConfiguration.Enabled
}
if vp, okp := _backupConfiguration["start_time"]; okp && vp != nil {
if vp, okp := _backupConfiguration["start_time"]; okp && len(vp.(string)) > 0 {
_backupConfiguration["start_time"] = settings.BackupConfiguration.StartTime
}
@ -577,6 +672,24 @@ func resourceSqlDatabaseInstanceRead(d *schema.ResourceData, meta interface{}) e
_settings["crash_safe_replication"] = settings.CrashSafeReplicationEnabled
}
if v, ok := _settings["disk_autoresize"]; ok && v != nil {
if v.(bool) {
_settings["disk_autoresize"] = settings.StorageAutoResize
}
}
if v, ok := _settings["disk_size"]; ok && v != nil {
if v.(int) > 0 && settings.DataDiskSizeGb < int64(v.(int)) {
_settings["disk_size"] = settings.DataDiskSizeGb
}
}
if v, ok := _settings["disk_type"]; ok && v != nil {
if len(v.(string)) > 0 {
_settings["disk_type"] = settings.DataDiskType
}
}
if v, ok := _settings["database_flags"]; ok && len(v.([]interface{})) > 0 {
_flag_map := make(map[string]string)
// First keep track of localy defined flag pairs
@ -678,6 +791,25 @@ func resourceSqlDatabaseInstanceRead(d *schema.ResourceData, meta interface{}) e
}
}
if v, ok := _settings["maintenance_window"]; ok && len(v.([]interface{})) > 0 &&
settings.MaintenanceWindow != nil {
_maintenanceWindow := v.([]interface{})[0].(map[string]interface{})
if vp, okp := _maintenanceWindow["day"]; okp && vp != nil {
_maintenanceWindow["day"] = settings.MaintenanceWindow.Day
}
if vp, okp := _maintenanceWindow["hour"]; okp && vp != nil {
_maintenanceWindow["hour"] = settings.MaintenanceWindow.Hour
}
if vp, ok := _maintenanceWindow["update_track"]; ok && vp != nil {
if len(vp.(string)) > 0 {
_maintenanceWindow["update_track"] = settings.MaintenanceWindow.UpdateTrack
}
}
}
if v, ok := _settings["pricing_plan"]; ok && len(v.(string)) > 0 {
_settings["pricing_plan"] = settings.PricingPlan
}
@ -758,7 +890,7 @@ func resourceSqlDatabaseInstanceRead(d *schema.ResourceData, meta interface{}) e
d.Set("ip_address", _ipAddresses)
if v, ok := d.GetOk("master_instance_name"); ok && v != nil {
d.Set("master_instance_name", instance.MasterInstanceName)
d.Set("master_instance_name", strings.TrimPrefix(instance.MasterInstanceName, project+":"))
}
d.Set("self_link", instance.SelfLink)
@ -840,6 +972,20 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{})
settings.CrashSafeReplicationEnabled = v.(bool)
}
if v, ok := _settings["disk_autoresize"]; ok && v.(bool) {
settings.StorageAutoResize = v.(bool)
}
if v, ok := _settings["disk_size"]; ok {
if v.(int) > 0 && int64(v.(int)) > instance.Settings.DataDiskSizeGb {
settings.DataDiskSizeGb = int64(v.(int))
}
}
if v, ok := _settings["disk_type"]; ok && len(v.(string)) > 0 {
settings.DataDiskType = v.(string)
}
_oldDatabaseFlags := make([]interface{}, 0)
if ov, ook := _o["database_flags"]; ook {
_oldDatabaseFlags = ov.([]interface{})
@ -981,6 +1127,25 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{})
}
}
if v, ok := _settings["maintenance_window"]; ok && len(v.([]interface{})) > 0 {
settings.MaintenanceWindow = &sqladmin.MaintenanceWindow{}
_maintenanceWindow := v.([]interface{})[0].(map[string]interface{})
if vp, okp := _maintenanceWindow["day"]; okp {
settings.MaintenanceWindow.Day = int64(vp.(int))
}
if vp, okp := _maintenanceWindow["hour"]; okp {
settings.MaintenanceWindow.Hour = int64(vp.(int))
}
if vp, ok := _maintenanceWindow["update_track"]; ok {
if len(vp.(string)) > 0 {
settings.MaintenanceWindow.UpdateTrack = vp.(string)
}
}
}
if v, ok := _settings["pricing_plan"]; ok {
settings.PricingPlan = v.(string)
}
@ -1028,3 +1193,12 @@ func resourceSqlDatabaseInstanceDelete(d *schema.ResourceData, meta interface{})
return nil
}
func validateNumericRange(v interface{}, k string, min int, max int) (ws []string, errors []error) {
value := v.(int)
if min > value || value > max {
errors = append(errors, fmt.Errorf(
"%q outside range %d-%d.", k, min, max))
}
return
}

View File

@ -10,6 +10,7 @@ package google
import (
"fmt"
"strconv"
"strings"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
@ -63,6 +64,30 @@ func TestAccGoogleSqlDatabaseInstance_basic2(t *testing.T) {
})
}
func TestAccGoogleSqlDatabaseInstance_basic3(t *testing.T) {
var instance sqladmin.DatabaseInstance
databaseID := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccGoogleSqlDatabaseInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(
testGoogleSqlDatabaseInstance_basic3, databaseID),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleSqlDatabaseInstanceExists(
"google_sql_database_instance.instance", &instance),
testAccCheckGoogleSqlDatabaseInstanceEquals(
"google_sql_database_instance.instance", &instance),
testAccCheckGoogleSqlDatabaseRootUserDoesNotExist(
&instance),
),
},
},
})
}
func TestAccGoogleSqlDatabaseInstance_settings_basic(t *testing.T) {
var instance sqladmin.DatabaseInstance
databaseID := acctest.RandInt()
@ -86,6 +111,80 @@ func TestAccGoogleSqlDatabaseInstance_settings_basic(t *testing.T) {
})
}
func TestAccGoogleSqlDatabaseInstance_slave(t *testing.T) {
var instance sqladmin.DatabaseInstance
masterID := acctest.RandInt()
slaveID := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccGoogleSqlDatabaseInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(
testGoogleSqlDatabaseInstance_slave, masterID, slaveID),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleSqlDatabaseInstanceExists(
"google_sql_database_instance.instance_master", &instance),
testAccCheckGoogleSqlDatabaseInstanceEquals(
"google_sql_database_instance.instance_master", &instance),
testAccCheckGoogleSqlDatabaseInstanceExists(
"google_sql_database_instance.instance_slave", &instance),
testAccCheckGoogleSqlDatabaseInstanceEquals(
"google_sql_database_instance.instance_slave", &instance),
),
},
},
})
}
func TestAccGoogleSqlDatabaseInstance_diskspecs(t *testing.T) {
var instance sqladmin.DatabaseInstance
masterID := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccGoogleSqlDatabaseInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(
testGoogleSqlDatabaseInstance_diskspecs, masterID),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleSqlDatabaseInstanceExists(
"google_sql_database_instance.instance", &instance),
testAccCheckGoogleSqlDatabaseInstanceEquals(
"google_sql_database_instance.instance", &instance),
),
},
},
})
}
func TestAccGoogleSqlDatabaseInstance_maintenance(t *testing.T) {
var instance sqladmin.DatabaseInstance
masterID := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccGoogleSqlDatabaseInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(
testGoogleSqlDatabaseInstance_maintenance, masterID),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleSqlDatabaseInstanceExists(
"google_sql_database_instance.instance", &instance),
testAccCheckGoogleSqlDatabaseInstanceEquals(
"google_sql_database_instance.instance", &instance),
),
},
},
})
}
func TestAccGoogleSqlDatabaseInstance_settings_upgrade(t *testing.T) {
var instance sqladmin.DatabaseInstance
databaseID := acctest.RandInt()
@ -199,7 +298,7 @@ func testAccCheckGoogleSqlDatabaseInstanceEquals(n string,
return fmt.Errorf("Error settings.tier mismatch, (%s, %s)", server, local)
}
server = instance.MasterInstanceName
server = strings.TrimPrefix(instance.MasterInstanceName, instance.Project+":")
local = attributes["master_instance_name"]
if server != local && len(server) > 0 && len(local) > 0 {
return fmt.Errorf("Error master_instance_name mismatch, (%s, %s)", server, local)
@ -237,6 +336,24 @@ func testAccCheckGoogleSqlDatabaseInstanceEquals(n string,
return fmt.Errorf("Error settings.crash_safe_replication mismatch, (%s, %s)", server, local)
}
server = strconv.FormatBool(instance.Settings.StorageAutoResize)
local = attributes["settings.0.disk_autoresize"]
if server != local && len(server) > 0 && len(local) > 0 {
return fmt.Errorf("Error settings.disk_autoresize mismatch, (%s, %s)", server, local)
}
server = strconv.FormatInt(instance.Settings.DataDiskSizeGb, 10)
local = attributes["settings.0.disk_size"]
if server != local && len(server) > 0 && len(local) > 0 && local != "0" {
return fmt.Errorf("Error settings.disk_size mismatch, (%s, %s)", server, local)
}
server = instance.Settings.DataDiskType
local = attributes["settings.0.disk_type"]
if server != local && len(server) > 0 && len(local) > 0 {
return fmt.Errorf("Error settings.disk_type mismatch, (%s, %s)", server, local)
}
if instance.Settings.IpConfiguration != nil {
server = strconv.FormatBool(instance.Settings.IpConfiguration.Ipv4Enabled)
local = attributes["settings.0.ip_configuration.0.ipv4_enabled"]
@ -265,6 +382,26 @@ func testAccCheckGoogleSqlDatabaseInstanceEquals(n string,
}
}
if instance.Settings.MaintenanceWindow != nil {
server = strconv.FormatInt(instance.Settings.MaintenanceWindow.Day, 10)
local = attributes["settings.0.maintenance_window.0.day"]
if server != local && len(server) > 0 && len(local) > 0 {
return fmt.Errorf("Error settings.maintenance_window.day mismatch, (%s, %s)", server, local)
}
server = strconv.FormatInt(instance.Settings.MaintenanceWindow.Hour, 10)
local = attributes["settings.0.maintenance_window.0.hour"]
if server != local && len(server) > 0 && len(local) > 0 {
return fmt.Errorf("Error settings.maintenance_window.hour mismatch, (%s, %s)", server, local)
}
server = instance.Settings.MaintenanceWindow.UpdateTrack
local = attributes["settings.0.maintenance_window.0.update_track"]
if server != local && len(server) > 0 && len(local) > 0 {
return fmt.Errorf("Error settings.maintenance_window.update_track mismatch, (%s, %s)", server, local)
}
}
server = instance.Settings.PricingPlan
local = attributes["settings.0.pricing_plan"]
if server != local && len(server) > 0 && len(local) > 0 {
@ -377,6 +514,27 @@ func testAccGoogleSqlDatabaseInstanceDestroy(s *terraform.State) error {
return nil
}
func testAccCheckGoogleSqlDatabaseRootUserDoesNotExist(
instance *sqladmin.DatabaseInstance) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
users, err := config.clientSqlAdmin.Users.List(config.Project, instance.Name).Do()
if err != nil {
return fmt.Errorf("Could not list database users for %q: %s", instance.Name, err)
}
for _, u := range users.Items {
if u.Name == "root" && u.Host == "%" {
return fmt.Errorf("%v@%v user still exists", u.Name, u.Host)
}
}
return nil
}
}
var testGoogleSqlDatabaseInstance_basic = `
resource "google_sql_database_instance" "instance" {
name = "tf-lw-%d"
@ -397,6 +555,15 @@ resource "google_sql_database_instance" "instance" {
}
}
`
var testGoogleSqlDatabaseInstance_basic3 = `
resource "google_sql_database_instance" "instance" {
name = "tf-lw-%d"
region = "us-central"
settings {
tier = "db-f1-micro"
}
}
`
var testGoogleSqlDatabaseInstance_settings = `
resource "google_sql_database_instance" "instance" {
@ -474,6 +641,64 @@ resource "google_sql_database_instance" "instance" {
}
`
var testGoogleSqlDatabaseInstance_slave = `
resource "google_sql_database_instance" "instance_master" {
name = "tf-lw-%d"
region = "us-central1"
settings {
tier = "db-f1-micro"
backup_configuration {
enabled = true
binary_log_enabled = true
}
}
}
resource "google_sql_database_instance" "instance_slave" {
name = "tf-lw-%d"
region = "us-central1"
master_instance_name = "${google_sql_database_instance.instance_master.name}"
settings {
tier = "db-f1-micro"
}
}
`
var testGoogleSqlDatabaseInstance_diskspecs = `
resource "google_sql_database_instance" "instance" {
name = "tf-lw-%d"
region = "us-central1"
settings {
tier = "db-f1-micro"
disk_autoresize = true
disk_size = 15
disk_type = "PD_HDD"
}
}
`
var testGoogleSqlDatabaseInstance_maintenance = `
resource "google_sql_database_instance" "instance" {
name = "tf-lw-%d"
region = "us-central1"
settings {
tier = "db-f1-micro"
maintenance_window {
day = 7
hour = 3
update_track = "canary"
}
}
}
`
var testGoogleSqlDatabaseInstance_authNets_step1 = `
resource "google_sql_database_instance" "instance" {
name = "tf-lw-%d"

View File

@ -68,12 +68,12 @@ func TestAccStorageStorageClass(t *testing.T) {
CheckDestroy: testAccGoogleStorageDestroy,
Steps: []resource.TestStep{
{
Config: testGoogleStorageBucketsReaderStorageClass(bucketName, "STANDARD"),
Config: testGoogleStorageBucketsReaderStorageClass(bucketName, "MULTI_REGIONAL"),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStorageBucketExists(
"google_storage_bucket.bucket", bucketName),
resource.TestCheckResourceAttr(
"google_storage_bucket.bucket", "storage_class", "STANDARD"),
"google_storage_bucket.bucket", "storage_class", "MULTI_REGIONAL"),
),
},
{
@ -86,12 +86,12 @@ func TestAccStorageStorageClass(t *testing.T) {
),
},
{
Config: testGoogleStorageBucketsReaderStorageClass(bucketName, "DURABLE_REDUCED_AVAILABILITY"),
Config: testGoogleStorageBucketsReaderStorageClass(bucketName, "REGIONAL"),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStorageBucketExists(
"google_storage_bucket.bucket", bucketName),
resource.TestCheckResourceAttr(
"google_storage_bucket.bucket", "storage_class", "DURABLE_REDUCED_AVAILABILITY"),
"google_storage_bucket.bucket", "storage_class", "REGIONAL"),
),
},
},