Merge branch 'master' into oauth2
This commit is contained in:
commit
e898daf573
|
@ -15,3 +15,5 @@ website/node_modules
|
||||||
*.tfstate
|
*.tfstate
|
||||||
*.log
|
*.log
|
||||||
*.bak
|
*.bak
|
||||||
|
*~
|
||||||
|
.*.swp
|
||||||
|
|
|
@ -7,6 +7,11 @@ FEATURES:
|
||||||
|
|
||||||
IMPROVEMENTS:
|
IMPROVEMENTS:
|
||||||
|
|
||||||
|
* **New resources: `google_compute_forwarding_rule`, `google_compute_http_health_check`,
|
||||||
|
and `google_compute_target_pool`** - Together these provide network-level
|
||||||
|
load balancing. [GH-588]
|
||||||
|
* **New resource: `aws_main_route_table_association`** - Manage the main routing table
|
||||||
|
of a VPC. [GH-918]
|
||||||
* core: Formalized the syntax of interpolations and documented it
|
* core: Formalized the syntax of interpolations and documented it
|
||||||
very heavily.
|
very heavily.
|
||||||
* core: Strings in interpolations can now contain further interpolations,
|
* core: Strings in interpolations can now contain further interpolations,
|
||||||
|
@ -17,8 +22,11 @@ IMPROVEMENTS:
|
||||||
* provider/aws: The `aws_db_instance` resource no longer requires both
|
* provider/aws: The `aws_db_instance` resource no longer requires both
|
||||||
`final_snapshot_identifier` and `skip_final_snapshot`; the presence or
|
`final_snapshot_identifier` and `skip_final_snapshot`; the presence or
|
||||||
absence of the former now implies the latter. [GH-874]
|
absence of the former now implies the latter. [GH-874]
|
||||||
|
* provider/aws: Avoid unecessary update of `aws_subnet` when
|
||||||
|
`map_public_ip_on_launch` is not specified in config. [GH-898]
|
||||||
* provider/google: Remove "client secrets file", as it's no longer necessary
|
* provider/google: Remove "client secrets file", as it's no longer necessary
|
||||||
for API authentication [GH-884].
|
for API authentication [GH-884].
|
||||||
|
* provider/google: Expose `self_link` on `google_compute_instance` [GH-906]
|
||||||
|
|
||||||
BUG FIXES:
|
BUG FIXES:
|
||||||
|
|
||||||
|
|
6
Makefile
6
Makefile
|
@ -32,12 +32,10 @@ testrace: generate
|
||||||
# updatedeps installs all the dependencies that Terraform needs to run
|
# updatedeps installs all the dependencies that Terraform needs to run
|
||||||
# and build.
|
# and build.
|
||||||
updatedeps:
|
updatedeps:
|
||||||
go get -u github.com/phinze/deplist
|
|
||||||
go get -u github.com/mitchellh/gox
|
go get -u github.com/mitchellh/gox
|
||||||
go get -u golang.org/x/tools/cmd/stringer
|
go get -u golang.org/x/tools/cmd/stringer
|
||||||
go get -u golang.org/x/tools/cmd/vet
|
go list ./... \
|
||||||
go list github.com/hashicorp/terraform/... \
|
| xargs go list -f '{{join .Deps "\n"}}' \
|
||||||
| xargs -n 1 deplist -s \
|
|
||||||
| grep -v github.com/hashicorp/terraform \
|
| grep -v github.com/hashicorp/terraform \
|
||||||
| sort -u \
|
| sort -u \
|
||||||
| xargs go get -f -u -v
|
| xargs go get -f -u -v
|
||||||
|
|
|
@ -45,26 +45,27 @@ func Provider() terraform.ResourceProvider {
|
||||||
},
|
},
|
||||||
|
|
||||||
ResourcesMap: map[string]*schema.Resource{
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
"aws_autoscaling_group": resourceAwsAutoscalingGroup(),
|
"aws_autoscaling_group": resourceAwsAutoscalingGroup(),
|
||||||
"aws_db_instance": resourceAwsDbInstance(),
|
"aws_db_instance": resourceAwsDbInstance(),
|
||||||
"aws_db_parameter_group": resourceAwsDbParameterGroup(),
|
"aws_db_parameter_group": resourceAwsDbParameterGroup(),
|
||||||
"aws_db_security_group": resourceAwsDbSecurityGroup(),
|
"aws_db_security_group": resourceAwsDbSecurityGroup(),
|
||||||
"aws_db_subnet_group": resourceAwsDbSubnetGroup(),
|
"aws_db_subnet_group": resourceAwsDbSubnetGroup(),
|
||||||
"aws_eip": resourceAwsEip(),
|
"aws_eip": resourceAwsEip(),
|
||||||
"aws_elb": resourceAwsElb(),
|
"aws_elb": resourceAwsElb(),
|
||||||
"aws_instance": resourceAwsInstance(),
|
"aws_instance": resourceAwsInstance(),
|
||||||
"aws_internet_gateway": resourceAwsInternetGateway(),
|
"aws_internet_gateway": resourceAwsInternetGateway(),
|
||||||
"aws_key_pair": resourceAwsKeyPair(),
|
"aws_key_pair": resourceAwsKeyPair(),
|
||||||
"aws_launch_configuration": resourceAwsLaunchConfiguration(),
|
"aws_launch_configuration": resourceAwsLaunchConfiguration(),
|
||||||
"aws_network_acl": resourceAwsNetworkAcl(),
|
"aws_main_route_table_association": resourceAwsMainRouteTableAssociation(),
|
||||||
"aws_route53_record": resourceAwsRoute53Record(),
|
"aws_network_acl": resourceAwsNetworkAcl(),
|
||||||
"aws_route53_zone": resourceAwsRoute53Zone(),
|
"aws_route53_record": resourceAwsRoute53Record(),
|
||||||
"aws_route_table": resourceAwsRouteTable(),
|
"aws_route53_zone": resourceAwsRoute53Zone(),
|
||||||
"aws_route_table_association": resourceAwsRouteTableAssociation(),
|
"aws_route_table": resourceAwsRouteTable(),
|
||||||
"aws_s3_bucket": resourceAwsS3Bucket(),
|
"aws_route_table_association": resourceAwsRouteTableAssociation(),
|
||||||
"aws_security_group": resourceAwsSecurityGroup(),
|
"aws_s3_bucket": resourceAwsS3Bucket(),
|
||||||
"aws_subnet": resourceAwsSubnet(),
|
"aws_security_group": resourceAwsSecurityGroup(),
|
||||||
"aws_vpc": resourceAwsVpc(),
|
"aws_subnet": resourceAwsSubnet(),
|
||||||
|
"aws_vpc": resourceAwsVpc(),
|
||||||
},
|
},
|
||||||
|
|
||||||
ConfigureFunc: providerConfigure,
|
ConfigureFunc: providerConfigure,
|
||||||
|
|
|
@ -119,7 +119,6 @@ func resourceAwsElb() *schema.Resource {
|
||||||
"health_check": &schema.Schema{
|
"health_check": &schema.Schema{
|
||||||
Type: schema.TypeSet,
|
Type: schema.TypeSet,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
|
||||||
Computed: true,
|
Computed: true,
|
||||||
Elem: &schema.Resource{
|
Elem: &schema.Resource{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
|
@ -331,6 +330,28 @@ func resourceAwsElbUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
d.SetPartial("cross_zone_load_balancing")
|
d.SetPartial("cross_zone_load_balancing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d.HasChange("health_check") {
|
||||||
|
vs := d.Get("health_check").(*schema.Set).List()
|
||||||
|
if len(vs) > 0 {
|
||||||
|
check := vs[0].(map[string]interface{})
|
||||||
|
configureHealthCheckOpts := elb.ConfigureHealthCheck{
|
||||||
|
LoadBalancerName: d.Id(),
|
||||||
|
Check: elb.HealthCheck{
|
||||||
|
HealthyThreshold: int64(check["healthy_threshold"].(int)),
|
||||||
|
UnhealthyThreshold: int64(check["unhealthy_threshold"].(int)),
|
||||||
|
Interval: int64(check["interval"].(int)),
|
||||||
|
Target: check["target"].(string),
|
||||||
|
Timeout: int64(check["timeout"].(int)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := elbconn.ConfigureHealthCheck(&configureHealthCheckOpts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failure configuring health check: %s", err)
|
||||||
|
}
|
||||||
|
d.SetPartial("health_check")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
d.Partial(false)
|
d.Partial(false)
|
||||||
return resourceAwsElbRead(d, meta)
|
return resourceAwsElbRead(d, meta)
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,6 +152,31 @@ func TestAccAWSELB_HealthCheck(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccAWSELBUpdate_HealthCheck(t *testing.T) {
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckAWSELBDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccAWSELBConfigHealthCheck,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"aws_elb.bar", "health_check.3484319807.healthy_threshold", "5"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccAWSELBConfigHealthCheck_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"aws_elb.bar", "health_check.2648756019.healthy_threshold", "10"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func testAccCheckAWSELBDestroy(s *terraform.State) error {
|
func testAccCheckAWSELBDestroy(s *terraform.State) error {
|
||||||
conn := testAccProvider.Meta().(*AWSClient).elbconn
|
conn := testAccProvider.Meta().(*AWSClient).elbconn
|
||||||
|
|
||||||
|
@ -418,3 +443,25 @@ resource "aws_elb" "bar" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testAccAWSELBConfigHealthCheck_update = `
|
||||||
|
resource "aws_elb" "bar" {
|
||||||
|
name = "foobar-terraform-test"
|
||||||
|
availability_zones = ["us-west-2a"]
|
||||||
|
|
||||||
|
listener {
|
||||||
|
instance_port = 8000
|
||||||
|
instance_protocol = "http"
|
||||||
|
lb_port = 80
|
||||||
|
lb_protocol = "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
health_check {
|
||||||
|
healthy_threshold = 10
|
||||||
|
unhealthy_threshold = 5
|
||||||
|
target = "HTTP:8000/"
|
||||||
|
interval = 60
|
||||||
|
timeout = 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
|
@ -180,6 +180,8 @@ func TestAccInstance_tags(t *testing.T) {
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckInstanceExists("aws_instance.foo", &v),
|
testAccCheckInstanceExists("aws_instance.foo", &v),
|
||||||
testAccCheckTags(&v.Tags, "foo", "bar"),
|
testAccCheckTags(&v.Tags, "foo", "bar"),
|
||||||
|
// Guard against regression of https://github.com/hashicorp/terraform/issues/914
|
||||||
|
testAccCheckTags(&v.Tags, "#", ""),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/mitchellh/goamz/ec2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceAwsMainRouteTableAssociation() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceAwsMainRouteTableAssociationCreate,
|
||||||
|
Read: resourceAwsMainRouteTableAssociationRead,
|
||||||
|
Update: resourceAwsMainRouteTableAssociationUpdate,
|
||||||
|
Delete: resourceAwsMainRouteTableAssociationDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"vpc_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"route_table_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// We use this field to record the main route table that is automatically
|
||||||
|
// created when the VPC is created. We need this to be able to "destroy"
|
||||||
|
// our main route table association, which we do by returning this route
|
||||||
|
// table to its original place as the Main Route Table for the VPC.
|
||||||
|
"original_route_table_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceAwsMainRouteTableAssociationCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
ec2conn := meta.(*AWSClient).ec2conn
|
||||||
|
vpcId := d.Get("vpc_id").(string)
|
||||||
|
routeTableId := d.Get("route_table_id").(string)
|
||||||
|
|
||||||
|
log.Printf("[INFO] Creating main route table association: %s => %s", vpcId, routeTableId)
|
||||||
|
|
||||||
|
mainAssociation, err := findMainRouteTableAssociation(ec2conn, vpcId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := ec2conn.ReassociateRouteTable(
|
||||||
|
mainAssociation.AssociationId,
|
||||||
|
routeTableId,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("original_route_table_id", mainAssociation.RouteTableId)
|
||||||
|
d.SetId(resp.AssociationId)
|
||||||
|
log.Printf("[INFO] New main route table association ID: %s", d.Id())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceAwsMainRouteTableAssociationRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
ec2conn := meta.(*AWSClient).ec2conn
|
||||||
|
|
||||||
|
mainAssociation, err := findMainRouteTableAssociation(
|
||||||
|
ec2conn,
|
||||||
|
d.Get("vpc_id").(string))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if mainAssociation.AssociationId != d.Id() {
|
||||||
|
// It seems it doesn't exist anymore, so clear the ID
|
||||||
|
d.SetId("")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update is almost exactly like Create, except we want to retain the
|
||||||
|
// original_route_table_id - this needs to stay recorded as the AWS-created
|
||||||
|
// table from VPC creation.
|
||||||
|
func resourceAwsMainRouteTableAssociationUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
ec2conn := meta.(*AWSClient).ec2conn
|
||||||
|
vpcId := d.Get("vpc_id").(string)
|
||||||
|
routeTableId := d.Get("route_table_id").(string)
|
||||||
|
|
||||||
|
log.Printf("[INFO] Updating main route table association: %s => %s", vpcId, routeTableId)
|
||||||
|
|
||||||
|
resp, err := ec2conn.ReassociateRouteTable(d.Id(), routeTableId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(resp.AssociationId)
|
||||||
|
log.Printf("[INFO] New main route table association ID: %s", d.Id())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceAwsMainRouteTableAssociationDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
ec2conn := meta.(*AWSClient).ec2conn
|
||||||
|
vpcId := d.Get("vpc_id").(string)
|
||||||
|
originalRouteTableId := d.Get("original_route_table_id").(string)
|
||||||
|
|
||||||
|
log.Printf("[INFO] Deleting main route table association by resetting Main Route Table for VPC: %s to its original Route Table: %s",
|
||||||
|
vpcId,
|
||||||
|
originalRouteTableId)
|
||||||
|
|
||||||
|
resp, err := ec2conn.ReassociateRouteTable(d.Id(), originalRouteTableId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[INFO] Resulting Association ID: %s", resp.AssociationId)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findMainRouteTableAssociation(ec2conn *ec2.EC2, vpcId string) (*ec2.RouteTableAssociation, error) {
|
||||||
|
mainRouteTable, err := findMainRouteTable(ec2conn, vpcId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range mainRouteTable.Associations {
|
||||||
|
if a.Main {
|
||||||
|
return &a, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Could not find main routing table association for VPC: %s", vpcId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findMainRouteTable(ec2conn *ec2.EC2, vpcId string) (*ec2.RouteTable, error) {
|
||||||
|
filter := ec2.NewFilter()
|
||||||
|
filter.Add("association.main", "true")
|
||||||
|
filter.Add("vpc-id", vpcId)
|
||||||
|
routeResp, err := ec2conn.DescribeRouteTables(nil, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if len(routeResp.RouteTables) != 1 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Expected to find a single main routing table for VPC: %s, but found %d",
|
||||||
|
vpcId,
|
||||||
|
len(routeResp.RouteTables))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &routeResp.RouteTables[0], nil
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccAWSMainRouteTableAssociation(t *testing.T) {
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckMainRouteTableAssociationDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccMainRouteTableAssociationConfig,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckMainRouteTableAssociation(
|
||||||
|
"aws_main_route_table_association.foo",
|
||||||
|
"aws_vpc.foo",
|
||||||
|
"aws_route_table.foo",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccMainRouteTableAssociationConfigUpdate,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckMainRouteTableAssociation(
|
||||||
|
"aws_main_route_table_association.foo",
|
||||||
|
"aws_vpc.foo",
|
||||||
|
"aws_route_table.bar",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMainRouteTableAssociationDestroy(s *terraform.State) error {
|
||||||
|
if len(s.RootModule().Resources) > 0 {
|
||||||
|
return fmt.Errorf("Expected all resources to be gone, but found: %#v", s.RootModule().Resources)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMainRouteTableAssociation(
|
||||||
|
mainRouteTableAssociationResource string,
|
||||||
|
vpcResource string,
|
||||||
|
routeTableResource string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[mainRouteTableAssociationResource]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", mainRouteTableAssociationResource)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
vpc, ok := s.RootModule().Resources[vpcResource]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", vpcResource)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
||||||
|
mainAssociation, err := findMainRouteTableAssociation(conn, vpc.Primary.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if mainAssociation.AssociationId != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("Found wrong main association: %s",
|
||||||
|
mainAssociation.AssociationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccMainRouteTableAssociationConfig = `
|
||||||
|
resource "aws_vpc" "foo" {
|
||||||
|
cidr_block = "10.1.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_subnet" "foo" {
|
||||||
|
vpc_id = "${aws_vpc.foo.id}"
|
||||||
|
cidr_block = "10.1.1.0/24"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_internet_gateway" "foo" {
|
||||||
|
vpc_id = "${aws_vpc.foo.id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_route_table" "foo" {
|
||||||
|
vpc_id = "${aws_vpc.foo.id}"
|
||||||
|
route {
|
||||||
|
cidr_block = "10.0.0.0/8"
|
||||||
|
gateway_id = "${aws_internet_gateway.foo.id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_main_route_table_association" "foo" {
|
||||||
|
vpc_id = "${aws_vpc.foo.id}"
|
||||||
|
route_table_id = "${aws_route_table.foo.id}"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testAccMainRouteTableAssociationConfigUpdate = `
|
||||||
|
resource "aws_vpc" "foo" {
|
||||||
|
cidr_block = "10.1.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_subnet" "foo" {
|
||||||
|
vpc_id = "${aws_vpc.foo.id}"
|
||||||
|
cidr_block = "10.1.1.0/24"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_internet_gateway" "foo" {
|
||||||
|
vpc_id = "${aws_vpc.foo.id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to keep the old route table around when we update the
|
||||||
|
// main_route_table_association, otherwise Terraform will try to destroy the
|
||||||
|
// route table too early, and will fail because it's still the main one
|
||||||
|
resource "aws_route_table" "foo" {
|
||||||
|
vpc_id = "${aws_vpc.foo.id}"
|
||||||
|
route {
|
||||||
|
cidr_block = "10.0.0.0/8"
|
||||||
|
gateway_id = "${aws_internet_gateway.foo.id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_route_table" "bar" {
|
||||||
|
vpc_id = "${aws_vpc.foo.id}"
|
||||||
|
route {
|
||||||
|
cidr_block = "10.0.0.0/8"
|
||||||
|
gateway_id = "${aws_internet_gateway.foo.id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_main_route_table_association" "foo" {
|
||||||
|
vpc_id = "${aws_vpc.foo.id}"
|
||||||
|
route_table_id = "${aws_route_table.bar.id}"
|
||||||
|
}
|
||||||
|
`
|
|
@ -41,6 +41,7 @@ func resourceAwsSubnet() *schema.Resource {
|
||||||
"map_public_ip_on_launch": &schema.Schema{
|
"map_public_ip_on_launch": &schema.Schema{
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"tags": tagsSchema(),
|
"tags": tagsSchema(),
|
||||||
|
|
|
@ -5,9 +5,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/MSOpenTech/azure-sdk-for-go/clients/hostedServiceClient"
|
||||||
|
"github.com/MSOpenTech/azure-sdk-for-go/clients/vmClient"
|
||||||
"github.com/hashicorp/terraform/helper/hashcode"
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/MSOpenTech/azure-sdk-for-go/clients/vmClient"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceVirtualMachine() *schema.Resource {
|
func resourceVirtualMachine() *schema.Resource {
|
||||||
|
@ -220,7 +221,7 @@ func resourceVirtualMachineDelete(d *schema.ResourceData, meta interface{}) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] Deleting Azure Hosted Service: %s", d.Id())
|
log.Printf("[DEBUG] Deleting Azure Hosted Service: %s", d.Id())
|
||||||
if err := vmClient.DeleteHostedService(d.Id()); err != nil {
|
if err := hostedServiceClient.DeleteHostedService(d.Id()); err != nil {
|
||||||
return fmt.Errorf("Error deleting Azure hosted service: %s", err)
|
return fmt.Errorf("Error deleting Azure hosted service: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,12 +29,15 @@ func Provider() terraform.ResourceProvider {
|
||||||
},
|
},
|
||||||
|
|
||||||
ResourcesMap: map[string]*schema.Resource{
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
"google_compute_address": resourceComputeAddress(),
|
"google_compute_address": resourceComputeAddress(),
|
||||||
"google_compute_disk": resourceComputeDisk(),
|
"google_compute_disk": resourceComputeDisk(),
|
||||||
"google_compute_firewall": resourceComputeFirewall(),
|
"google_compute_firewall": resourceComputeFirewall(),
|
||||||
"google_compute_instance": resourceComputeInstance(),
|
"google_compute_forwarding_rule": resourceComputeForwardingRule(),
|
||||||
"google_compute_network": resourceComputeNetwork(),
|
"google_compute_http_health_check": resourceComputeHttpHealthCheck(),
|
||||||
"google_compute_route": resourceComputeRoute(),
|
"google_compute_instance": resourceComputeInstance(),
|
||||||
|
"google_compute_network": resourceComputeNetwork(),
|
||||||
|
"google_compute_route": resourceComputeRoute(),
|
||||||
|
"google_compute_target_pool": resourceComputeTargetPool(),
|
||||||
},
|
},
|
||||||
|
|
||||||
ConfigureFunc: providerConfigure,
|
ConfigureFunc: providerConfigure,
|
||||||
|
|
|
@ -27,6 +27,12 @@ func resourceComputeAddress() *schema.Resource {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"self_link": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,6 +96,7 @@ func resourceComputeAddressRead(d *schema.ResourceData, meta interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Set("address", addr.Address)
|
d.Set("address", addr.Address)
|
||||||
|
d.Set("self_link", addr.SelfLink)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -98,6 +105,7 @@ func resourceComputeAddressDelete(d *schema.ResourceData, meta interface{}) erro
|
||||||
config := meta.(*Config)
|
config := meta.(*Config)
|
||||||
|
|
||||||
// Delete the address
|
// Delete the address
|
||||||
|
log.Printf("[DEBUG] address delete request")
|
||||||
op, err := config.clientCompute.Addresses.Delete(
|
op, err := config.clientCompute.Addresses.Delete(
|
||||||
config.Project, config.Region, d.Id()).Do()
|
config.Project, config.Region, d.Id()).Do()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -26,6 +26,11 @@ func resourceComputeFirewall() *schema.Resource {
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"description": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
|
||||||
"network": &schema.Schema{
|
"network": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
|
@ -306,6 +311,7 @@ func resourceFirewall(
|
||||||
// Build the firewall parameter
|
// Build the firewall parameter
|
||||||
return &compute.Firewall{
|
return &compute.Firewall{
|
||||||
Name: d.Get("name").(string),
|
Name: d.Get("name").(string),
|
||||||
|
Description: d.Get("description").(string),
|
||||||
Network: network.SelfLink,
|
Network: network.SelfLink,
|
||||||
Allowed: allowed,
|
Allowed: allowed,
|
||||||
SourceRanges: sourceRanges,
|
SourceRanges: sourceRanges,
|
||||||
|
|
|
@ -126,6 +126,7 @@ resource "google_compute_network" "foobar" {
|
||||||
|
|
||||||
resource "google_compute_firewall" "foobar" {
|
resource "google_compute_firewall" "foobar" {
|
||||||
name = "terraform-test"
|
name = "terraform-test"
|
||||||
|
description = "Resource created for Terraform acceptance testing"
|
||||||
network = "${google_compute_network.foobar.name}"
|
network = "${google_compute_network.foobar.name}"
|
||||||
source_tags = ["foo"]
|
source_tags = ["foo"]
|
||||||
|
|
||||||
|
@ -142,6 +143,7 @@ resource "google_compute_network" "foobar" {
|
||||||
|
|
||||||
resource "google_compute_firewall" "foobar" {
|
resource "google_compute_firewall" "foobar" {
|
||||||
name = "terraform-test"
|
name = "terraform-test"
|
||||||
|
description = "Resource created for Terraform acceptance testing"
|
||||||
network = "${google_compute_network.foobar.name}"
|
network = "${google_compute_network.foobar.name}"
|
||||||
source_tags = ["foo"]
|
source_tags = ["foo"]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"code.google.com/p/google-api-go-client/googleapi"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceComputeForwardingRule() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceComputeForwardingRuleCreate,
|
||||||
|
Read: resourceComputeForwardingRuleRead,
|
||||||
|
Delete: resourceComputeForwardingRuleDelete,
|
||||||
|
Update: resourceComputeForwardingRuleUpdate,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"ip_address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"ip_protocol": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"description": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"port_range": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"self_link": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"target": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeForwardingRuleCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
frule := &compute.ForwardingRule{
|
||||||
|
IPAddress: d.Get("ip_address").(string),
|
||||||
|
IPProtocol: d.Get("ip_protocol").(string),
|
||||||
|
Description: d.Get("description").(string),
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
PortRange: d.Get("port_range").(string),
|
||||||
|
Target: d.Get("target").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] ForwardingRule insert request: %#v", frule)
|
||||||
|
op, err := config.clientCompute.ForwardingRules.Insert(
|
||||||
|
config.Project, config.Region, frule).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating ForwardingRule: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It probably maybe worked, so store the ID now
|
||||||
|
d.SetId(frule.Name)
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Region: config.Region,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitRegion,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for ForwardingRule to create: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// The resource didn't actually create
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeForwardingRuleRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeForwardingRuleUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
d.Partial(true)
|
||||||
|
|
||||||
|
if d.HasChange("target") {
|
||||||
|
target_name := d.Get("target").(string)
|
||||||
|
target_ref := &compute.TargetReference{Target: target_name}
|
||||||
|
op, err := config.clientCompute.ForwardingRules.SetTarget(
|
||||||
|
config.Project, config.Region, d.Id(), target_ref).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating target: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Region: config.Region,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitRegion,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for ForwardingRule to update target: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// The resource didn't actually create
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
d.SetPartial("target")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Partial(false)
|
||||||
|
|
||||||
|
return resourceComputeForwardingRuleRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeForwardingRuleRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
frule, err := config.clientCompute.ForwardingRules.Get(
|
||||||
|
config.Project, config.Region, 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 ForwardingRule: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("ip_address", frule.IPAddress)
|
||||||
|
d.Set("ip_protocol", frule.IPProtocol)
|
||||||
|
d.Set("self_link", frule.SelfLink)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeForwardingRuleDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Delete the ForwardingRule
|
||||||
|
log.Printf("[DEBUG] ForwardingRule delete request")
|
||||||
|
op, err := config.clientCompute.ForwardingRules.Delete(
|
||||||
|
config.Project, config.Region, d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting ForwardingRule: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Region: config.Region,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitRegion,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for ForwardingRule to delete: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeForwardingRule_basic(t *testing.T) {
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeForwardingRuleDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeForwardingRule_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeForwardingRuleExists(
|
||||||
|
"google_compute_forwarding_rule.foobar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccComputeForwardingRule_ip(t *testing.T) {
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeForwardingRuleDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeForwardingRule_ip,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeForwardingRuleExists(
|
||||||
|
"google_compute_forwarding_rule.foobar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeForwardingRuleDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "google_compute_forwarding_rule" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := config.clientCompute.ForwardingRules.Get(
|
||||||
|
config.Project, config.Region, rs.Primary.ID).Do()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("ForwardingRule still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeForwardingRuleExists(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)
|
||||||
|
|
||||||
|
found, err := config.clientCompute.ForwardingRules.Get(
|
||||||
|
config.Project, config.Region, rs.Primary.ID).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.Name != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("ForwardingRule not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccComputeForwardingRule_basic = `
|
||||||
|
resource "google_compute_target_pool" "foobar-tp" {
|
||||||
|
description = "Resource created for Terraform acceptance testing"
|
||||||
|
instances = ["us-central1-a/foo", "us-central1-b/bar"]
|
||||||
|
name = "terraform-test"
|
||||||
|
}
|
||||||
|
resource "google_compute_forwarding_rule" "foobar" {
|
||||||
|
description = "Resource created for Terraform acceptance testing"
|
||||||
|
ip_protocol = "UDP"
|
||||||
|
name = "terraform-test"
|
||||||
|
port_range = "80-81"
|
||||||
|
target = "${google_compute_target_pool.foobar-tp.self_link}"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testAccComputeForwardingRule_ip = `
|
||||||
|
resource "google_compute_address" "foo" {
|
||||||
|
name = "foo"
|
||||||
|
}
|
||||||
|
resource "google_compute_target_pool" "foobar-tp" {
|
||||||
|
description = "Resource created for Terraform acceptance testing"
|
||||||
|
instances = ["us-central1-a/foo", "us-central1-b/bar"]
|
||||||
|
name = "terraform-test"
|
||||||
|
}
|
||||||
|
resource "google_compute_forwarding_rule" "foobar" {
|
||||||
|
description = "Resource created for Terraform acceptance testing"
|
||||||
|
ip_address = "${google_compute_address.foo.address}"
|
||||||
|
ip_protocol = "TCP"
|
||||||
|
name = "terraform-test"
|
||||||
|
port_range = "80-81"
|
||||||
|
target = "${google_compute_target_pool.foobar-tp.self_link}"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
|
@ -0,0 +1,279 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"code.google.com/p/google-api-go-client/googleapi"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceComputeHttpHealthCheck() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceComputeHttpHealthCheckCreate,
|
||||||
|
Read: resourceComputeHttpHealthCheckRead,
|
||||||
|
Delete: resourceComputeHttpHealthCheckDelete,
|
||||||
|
Update: resourceComputeHttpHealthCheckUpdate,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"check_interval_sec": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"description": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"healthy_threshold": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"host": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"port": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"request_path": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"self_link": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"timeout_sec": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"unhealthy_threshold": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeHttpHealthCheckCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Build the parameter
|
||||||
|
hchk := &compute.HttpHealthCheck{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
}
|
||||||
|
// Optional things
|
||||||
|
if v, ok := d.GetOk("description"); ok {
|
||||||
|
hchk.Description = v.(string)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("host"); ok {
|
||||||
|
hchk.Host = v.(string)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("request_path"); ok {
|
||||||
|
hchk.RequestPath = v.(string)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("check_interval_sec"); ok {
|
||||||
|
hchk.CheckIntervalSec = int64(v.(int))
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("health_threshold"); ok {
|
||||||
|
hchk.HealthyThreshold = int64(v.(int))
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("port"); ok {
|
||||||
|
hchk.Port = int64(v.(int))
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("timeout_sec"); ok {
|
||||||
|
hchk.TimeoutSec = int64(v.(int))
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("unhealthy_threshold"); ok {
|
||||||
|
hchk.UnhealthyThreshold = int64(v.(int))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] HttpHealthCheck insert request: %#v", hchk)
|
||||||
|
op, err := config.clientCompute.HttpHealthChecks.Insert(
|
||||||
|
config.Project, hchk).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating HttpHealthCheck: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It probably maybe worked, so store the ID now
|
||||||
|
d.SetId(hchk.Name)
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitGlobal,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for HttpHealthCheck to create: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// The resource didn't actually create
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeHttpHealthCheckRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeHttpHealthCheckUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Build the parameter
|
||||||
|
hchk := &compute.HttpHealthCheck{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
}
|
||||||
|
// Optional things
|
||||||
|
if v, ok := d.GetOk("description"); ok {
|
||||||
|
hchk.Description = v.(string)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("host"); ok {
|
||||||
|
hchk.Host = v.(string)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("request_path"); ok {
|
||||||
|
hchk.RequestPath = v.(string)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("check_interval_sec"); ok {
|
||||||
|
hchk.CheckIntervalSec = int64(v.(int))
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("health_threshold"); ok {
|
||||||
|
hchk.HealthyThreshold = int64(v.(int))
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("port"); ok {
|
||||||
|
hchk.Port = int64(v.(int))
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("timeout_sec"); ok {
|
||||||
|
hchk.TimeoutSec = int64(v.(int))
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("unhealthy_threshold"); ok {
|
||||||
|
hchk.UnhealthyThreshold = int64(v.(int))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] HttpHealthCheck patch request: %#v", hchk)
|
||||||
|
op, err := config.clientCompute.HttpHealthChecks.Patch(
|
||||||
|
config.Project, hchk.Name, hchk).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error patching HttpHealthCheck: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It probably maybe worked, so store the ID now
|
||||||
|
d.SetId(hchk.Name)
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitGlobal,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for HttpHealthCheck to patch: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// The resource didn't actually create
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeHttpHealthCheckRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeHttpHealthCheckRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
hchk, err := config.clientCompute.HttpHealthChecks.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 HttpHealthCheck: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("host", hchk.Host)
|
||||||
|
d.Set("request_path", hchk.RequestPath)
|
||||||
|
d.Set("check_interval_sec", hchk.CheckIntervalSec)
|
||||||
|
d.Set("health_threshold", hchk.HealthyThreshold)
|
||||||
|
d.Set("port", hchk.Port)
|
||||||
|
d.Set("timeout_sec", hchk.TimeoutSec)
|
||||||
|
d.Set("unhealthy_threshold", hchk.UnhealthyThreshold)
|
||||||
|
d.Set("self_link", hchk.SelfLink)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeHttpHealthCheckDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Delete the HttpHealthCheck
|
||||||
|
op, err := config.clientCompute.HttpHealthChecks.Delete(
|
||||||
|
config.Project, d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting HttpHealthCheck: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitGlobal,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for HttpHealthCheck to delete: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeHttpHealthCheck_basic(t *testing.T) {
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeHttpHealthCheckDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeHttpHealthCheck_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeHttpHealthCheckExists(
|
||||||
|
"google_compute_http_health_check.foobar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeHttpHealthCheckDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "google_compute_http_health_check" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := config.clientCompute.HttpHealthChecks.Get(
|
||||||
|
config.Project, rs.Primary.ID).Do()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("HttpHealthCheck still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeHttpHealthCheckExists(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)
|
||||||
|
|
||||||
|
found, err := config.clientCompute.HttpHealthChecks.Get(
|
||||||
|
config.Project, rs.Primary.ID).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.Name != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("HttpHealthCheck not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccComputeHttpHealthCheck_basic = `
|
||||||
|
resource "google_compute_http_health_check" "foobar" {
|
||||||
|
check_interval_sec = 3
|
||||||
|
description = "Resource created for Terraform acceptance testing"
|
||||||
|
healthy_threshold = 3
|
||||||
|
host = "foobar"
|
||||||
|
name = "terraform-test"
|
||||||
|
port = "80"
|
||||||
|
request_path = "/health_check"
|
||||||
|
timeout_sec = 2
|
||||||
|
unhealthy_threshold = 3
|
||||||
|
}
|
||||||
|
`
|
|
@ -75,20 +75,61 @@ func resourceComputeInstance() *schema.Resource {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"network_interface": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"network": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"access_config": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"nat_ip": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
"network": &schema.Schema{
|
"network": &schema.Schema{
|
||||||
Type: schema.TypeList,
|
Type: schema.TypeList,
|
||||||
Required: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
Elem: &schema.Resource{
|
Elem: &schema.Resource{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"source": &schema.Schema{
|
"source": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"address": &schema.Schema{
|
"address": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"name": &schema.Schema{
|
"name": &schema.Schema{
|
||||||
|
@ -169,10 +210,42 @@ func resourceComputeInstance() *schema.Resource {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"self_link": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resourceOperationWaitZone(
|
||||||
|
config *Config, op *compute.Operation, zone string, activity string) error {
|
||||||
|
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Zone: zone,
|
||||||
|
Type: OperationWaitZone,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Delay = 10 * time.Second
|
||||||
|
state.Timeout = 10 * time.Minute
|
||||||
|
state.MinTimeout = 2 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for %s: %s", activity, err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
config := meta.(*Config)
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
@ -258,32 +331,80 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
|
||||||
disks = append(disks, &disk)
|
disks = append(disks, &disk)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build up the list of networks
|
|
||||||
networksCount := d.Get("network.#").(int)
|
networksCount := d.Get("network.#").(int)
|
||||||
networks := make([]*compute.NetworkInterface, 0, networksCount)
|
networkInterfacesCount := d.Get("network_interface.#").(int)
|
||||||
for i := 0; i < networksCount; i++ {
|
|
||||||
prefix := fmt.Sprintf("network.%d", i)
|
|
||||||
// Load up the name of this network
|
|
||||||
networkName := d.Get(prefix + ".source").(string)
|
|
||||||
network, err := config.clientCompute.Networks.Get(
|
|
||||||
config.Project, networkName).Do()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Error loading network '%s': %s",
|
|
||||||
networkName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the disk
|
if networksCount > 0 && networkInterfacesCount > 0 {
|
||||||
var iface compute.NetworkInterface
|
return fmt.Errorf("Error: cannot define both networks and network_interfaces.")
|
||||||
iface.AccessConfigs = []*compute.AccessConfig{
|
}
|
||||||
&compute.AccessConfig{
|
if networksCount == 0 && networkInterfacesCount == 0 {
|
||||||
Type: "ONE_TO_ONE_NAT",
|
return fmt.Errorf("Error: Must define at least one network_interface.")
|
||||||
NatIP: d.Get(prefix + ".address").(string),
|
}
|
||||||
},
|
|
||||||
}
|
|
||||||
iface.Network = network.SelfLink
|
|
||||||
|
|
||||||
networks = append(networks, &iface)
|
var networkInterfaces []*compute.NetworkInterface
|
||||||
|
|
||||||
|
if networksCount > 0 {
|
||||||
|
// TODO: Delete this block when removing network { }
|
||||||
|
// Build up the list of networkInterfaces
|
||||||
|
networkInterfaces = make([]*compute.NetworkInterface, 0, networksCount)
|
||||||
|
for i := 0; i < networksCount; i++ {
|
||||||
|
prefix := fmt.Sprintf("network.%d", i)
|
||||||
|
// Load up the name of this network
|
||||||
|
networkName := d.Get(prefix + ".source").(string)
|
||||||
|
network, err := config.clientCompute.Networks.Get(
|
||||||
|
config.Project, networkName).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error loading network '%s': %s",
|
||||||
|
networkName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the networkInterface
|
||||||
|
var iface compute.NetworkInterface
|
||||||
|
iface.AccessConfigs = []*compute.AccessConfig{
|
||||||
|
&compute.AccessConfig{
|
||||||
|
Type: "ONE_TO_ONE_NAT",
|
||||||
|
NatIP: d.Get(prefix + ".address").(string),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
iface.Network = network.SelfLink
|
||||||
|
|
||||||
|
networkInterfaces = append(networkInterfaces, &iface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if networkInterfacesCount > 0 {
|
||||||
|
// Build up the list of networkInterfaces
|
||||||
|
networkInterfaces = make([]*compute.NetworkInterface, 0, networkInterfacesCount)
|
||||||
|
for i := 0; i < networkInterfacesCount; i++ {
|
||||||
|
prefix := fmt.Sprintf("network_interface.%d", i)
|
||||||
|
// Load up the name of this network_interfac
|
||||||
|
networkName := d.Get(prefix + ".network").(string)
|
||||||
|
network, err := config.clientCompute.Networks.Get(
|
||||||
|
config.Project, networkName).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error referencing network '%s': %s",
|
||||||
|
networkName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the networkInterface
|
||||||
|
var iface compute.NetworkInterface
|
||||||
|
iface.Network = network.SelfLink
|
||||||
|
|
||||||
|
// Handle access_config structs
|
||||||
|
accessConfigsCount := d.Get(prefix + ".access_config.#").(int)
|
||||||
|
iface.AccessConfigs = make([]*compute.AccessConfig, accessConfigsCount)
|
||||||
|
for j := 0; j < accessConfigsCount; j++ {
|
||||||
|
acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j)
|
||||||
|
iface.AccessConfigs[j] = &compute.AccessConfig{
|
||||||
|
Type: "ONE_TO_ONE_NAT",
|
||||||
|
NatIP: d.Get(acPrefix + ".nat_ip").(string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
networkInterfaces = append(networkInterfaces, &iface)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceAccountsCount := d.Get("service_account.#").(int)
|
serviceAccountsCount := d.Get("service_account.#").(int)
|
||||||
|
@ -314,7 +435,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
|
||||||
MachineType: machineType.SelfLink,
|
MachineType: machineType.SelfLink,
|
||||||
Metadata: resourceInstanceMetadata(d),
|
Metadata: resourceInstanceMetadata(d),
|
||||||
Name: d.Get("name").(string),
|
Name: d.Get("name").(string),
|
||||||
NetworkInterfaces: networks,
|
NetworkInterfaces: networkInterfaces,
|
||||||
Tags: resourceInstanceTags(d),
|
Tags: resourceInstanceTags(d),
|
||||||
ServiceAccounts: serviceAccounts,
|
ServiceAccounts: serviceAccounts,
|
||||||
}
|
}
|
||||||
|
@ -330,28 +451,11 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
|
||||||
d.SetId(instance.Name)
|
d.SetId(instance.Name)
|
||||||
|
|
||||||
// Wait for the operation to complete
|
// Wait for the operation to complete
|
||||||
w := &OperationWaiter{
|
waitErr := resourceOperationWaitZone(config, op, zone.Name, "instance to create")
|
||||||
Service: config.clientCompute,
|
if waitErr != nil {
|
||||||
Op: op,
|
|
||||||
Project: config.Project,
|
|
||||||
Zone: zone.Name,
|
|
||||||
Type: OperationWaitZone,
|
|
||||||
}
|
|
||||||
state := w.Conf()
|
|
||||||
state.Delay = 10 * time.Second
|
|
||||||
state.Timeout = 10 * time.Minute
|
|
||||||
state.MinTimeout = 2 * time.Second
|
|
||||||
opRaw, err := state.WaitForState()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error waiting for instance to create: %s", err)
|
|
||||||
}
|
|
||||||
op = opRaw.(*compute.Operation)
|
|
||||||
if op.Error != nil {
|
|
||||||
// The resource didn't actually create
|
// The resource didn't actually create
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
|
return waitErr
|
||||||
// Return the error
|
|
||||||
return OperationError(*op.Error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resourceComputeInstanceRead(d, meta)
|
return resourceComputeInstanceRead(d, meta)
|
||||||
|
@ -385,26 +489,85 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
networksCount := d.Get("network.#").(int)
|
||||||
|
networkInterfacesCount := d.Get("network_interface.#").(int)
|
||||||
|
|
||||||
|
if networksCount > 0 && networkInterfacesCount > 0 {
|
||||||
|
return fmt.Errorf("Error: cannot define both networks and network_interfaces.")
|
||||||
|
}
|
||||||
|
if networksCount == 0 && networkInterfacesCount == 0 {
|
||||||
|
return fmt.Errorf("Error: Must define at least one network_interface.")
|
||||||
|
}
|
||||||
|
|
||||||
// Set the networks
|
// Set the networks
|
||||||
|
// Use the first external IP found for the default connection info.
|
||||||
externalIP := ""
|
externalIP := ""
|
||||||
for i, iface := range instance.NetworkInterfaces {
|
internalIP := ""
|
||||||
prefix := fmt.Sprintf("network.%d", i)
|
if networksCount > 0 {
|
||||||
d.Set(prefix+".name", iface.Name)
|
// TODO: Remove this when realizing deprecation of .network
|
||||||
|
for i, iface := range instance.NetworkInterfaces {
|
||||||
|
prefix := fmt.Sprintf("network.%d", i)
|
||||||
|
d.Set(prefix+".name", iface.Name)
|
||||||
|
log.Printf(prefix+".name = %s", iface.Name)
|
||||||
|
|
||||||
// Use the first external IP found for the default connection info.
|
var natIP string
|
||||||
natIP := resourceInstanceNatIP(iface)
|
for _, config := range iface.AccessConfigs {
|
||||||
if externalIP == "" && natIP != "" {
|
if config.Type == "ONE_TO_ONE_NAT" {
|
||||||
externalIP = natIP
|
natIP = config.NatIP
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if externalIP == "" && natIP != "" {
|
||||||
|
externalIP = natIP
|
||||||
|
}
|
||||||
|
d.Set(prefix+".external_address", natIP)
|
||||||
|
|
||||||
|
d.Set(prefix+".internal_address", iface.NetworkIP)
|
||||||
}
|
}
|
||||||
d.Set(prefix+".external_address", natIP)
|
}
|
||||||
|
|
||||||
d.Set(prefix+".internal_address", iface.NetworkIP)
|
if networkInterfacesCount > 0 {
|
||||||
|
for i, iface := range instance.NetworkInterfaces {
|
||||||
|
|
||||||
|
prefix := fmt.Sprintf("network_interface.%d", i)
|
||||||
|
d.Set(prefix+".name", iface.Name)
|
||||||
|
|
||||||
|
// The first non-empty ip is left in natIP
|
||||||
|
var natIP string
|
||||||
|
for j, config := range iface.AccessConfigs {
|
||||||
|
acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j)
|
||||||
|
d.Set(acPrefix+".nat_ip", config.NatIP)
|
||||||
|
if natIP == "" {
|
||||||
|
natIP = config.NatIP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if externalIP == "" {
|
||||||
|
externalIP = natIP
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set(prefix+".address", iface.NetworkIP)
|
||||||
|
if internalIP == "" {
|
||||||
|
internalIP = iface.NetworkIP
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back on internal ip if there is no external ip. This makes sense in the situation where
|
||||||
|
// terraform is being used on a cloud instance and can therefore access the instances it creates
|
||||||
|
// via their internal ips.
|
||||||
|
sshIP := externalIP
|
||||||
|
if sshIP == "" {
|
||||||
|
sshIP = internalIP
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the connection info
|
// Initialize the connection info
|
||||||
d.SetConnInfo(map[string]string{
|
d.SetConnInfo(map[string]string{
|
||||||
"type": "ssh",
|
"type": "ssh",
|
||||||
"host": externalIP,
|
"host": sshIP,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set the metadata fingerprint if there is one.
|
// Set the metadata fingerprint if there is one.
|
||||||
|
@ -417,12 +580,29 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
|
||||||
d.Set("tags_fingerprint", instance.Tags.Fingerprint)
|
d.Set("tags_fingerprint", instance.Tags.Fingerprint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.Set("self_link", instance.SelfLink)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
|
func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
config := meta.(*Config)
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
zone := d.Get("zone").(string)
|
||||||
|
|
||||||
|
instance, err := config.clientCompute.Instances.Get(
|
||||||
|
config.Project, zone, 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 instance: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Enable partial mode for the resource since it is possible
|
// Enable partial mode for the resource since it is possible
|
||||||
d.Partial(true)
|
d.Partial(true)
|
||||||
|
|
||||||
|
@ -430,30 +610,15 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
|
||||||
if d.HasChange("metadata") {
|
if d.HasChange("metadata") {
|
||||||
metadata := resourceInstanceMetadata(d)
|
metadata := resourceInstanceMetadata(d)
|
||||||
op, err := config.clientCompute.Instances.SetMetadata(
|
op, err := config.clientCompute.Instances.SetMetadata(
|
||||||
config.Project, d.Get("zone").(string), d.Id(), metadata).Do()
|
config.Project, zone, d.Id(), metadata).Do()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error updating metadata: %s", err)
|
return fmt.Errorf("Error updating metadata: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
w := &OperationWaiter{
|
// 1 5 2
|
||||||
Service: config.clientCompute,
|
opErr := resourceOperationWaitZone(config, op, zone, "metadata to update")
|
||||||
Op: op,
|
if opErr != nil {
|
||||||
Project: config.Project,
|
return opErr
|
||||||
Zone: d.Get("zone").(string),
|
|
||||||
Type: OperationWaitZone,
|
|
||||||
}
|
|
||||||
state := w.Conf()
|
|
||||||
state.Delay = 1 * time.Second
|
|
||||||
state.Timeout = 5 * time.Minute
|
|
||||||
state.MinTimeout = 2 * time.Second
|
|
||||||
opRaw, err := state.WaitForState()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error waiting for metadata to update: %s", err)
|
|
||||||
}
|
|
||||||
op = opRaw.(*compute.Operation)
|
|
||||||
if op.Error != nil {
|
|
||||||
// Return the error
|
|
||||||
return OperationError(*op.Error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
d.SetPartial("metadata")
|
d.SetPartial("metadata")
|
||||||
|
@ -462,35 +627,80 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
|
||||||
if d.HasChange("tags") {
|
if d.HasChange("tags") {
|
||||||
tags := resourceInstanceTags(d)
|
tags := resourceInstanceTags(d)
|
||||||
op, err := config.clientCompute.Instances.SetTags(
|
op, err := config.clientCompute.Instances.SetTags(
|
||||||
config.Project, d.Get("zone").(string), d.Id(), tags).Do()
|
config.Project, zone, d.Id(), tags).Do()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error updating tags: %s", err)
|
return fmt.Errorf("Error updating tags: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
w := &OperationWaiter{
|
opErr := resourceOperationWaitZone(config, op, zone, "tags to update")
|
||||||
Service: config.clientCompute,
|
if opErr != nil {
|
||||||
Op: op,
|
return opErr
|
||||||
Project: config.Project,
|
|
||||||
Zone: d.Get("zone").(string),
|
|
||||||
Type: OperationWaitZone,
|
|
||||||
}
|
|
||||||
state := w.Conf()
|
|
||||||
state.Delay = 1 * time.Second
|
|
||||||
state.Timeout = 5 * time.Minute
|
|
||||||
state.MinTimeout = 2 * time.Second
|
|
||||||
opRaw, err := state.WaitForState()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error waiting for tags to update: %s", err)
|
|
||||||
}
|
|
||||||
op = opRaw.(*compute.Operation)
|
|
||||||
if op.Error != nil {
|
|
||||||
// Return the error
|
|
||||||
return OperationError(*op.Error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
d.SetPartial("tags")
|
d.SetPartial("tags")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
networkInterfacesCount := d.Get("network_interface.#").(int)
|
||||||
|
if networkInterfacesCount > 0 {
|
||||||
|
// Sanity check
|
||||||
|
if networkInterfacesCount != len(instance.NetworkInterfaces) {
|
||||||
|
return fmt.Errorf("Instance had unexpected number of network interfaces: %d", len(instance.NetworkInterfaces))
|
||||||
|
}
|
||||||
|
for i := 0; i < networkInterfacesCount; i++ {
|
||||||
|
prefix := fmt.Sprintf("network_interface.%d", i)
|
||||||
|
instNetworkInterface := instance.NetworkInterfaces[i]
|
||||||
|
networkName := d.Get(prefix+".name").(string)
|
||||||
|
|
||||||
|
// TODO: This sanity check is broken by #929, disabled for now (by forcing the equality)
|
||||||
|
networkName = instNetworkInterface.Name
|
||||||
|
// Sanity check
|
||||||
|
if networkName != instNetworkInterface.Name {
|
||||||
|
return fmt.Errorf("Instance networkInterface had unexpected name: %s", instNetworkInterface.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange(prefix+".access_config") {
|
||||||
|
|
||||||
|
// TODO: This code deletes then recreates accessConfigs. This is bad because it may
|
||||||
|
// leave the machine inaccessible from either ip if the creation part fails (network
|
||||||
|
// timeout etc). However right now there is a GCE limit of 1 accessConfig so it is
|
||||||
|
// the only way to do it. In future this should be revised to only change what is
|
||||||
|
// necessary, and also add before removing.
|
||||||
|
|
||||||
|
// Delete any accessConfig that currently exists in instNetworkInterface
|
||||||
|
for _, ac := range(instNetworkInterface.AccessConfigs) {
|
||||||
|
op, err := config.clientCompute.Instances.DeleteAccessConfig(
|
||||||
|
config.Project, zone, d.Id(), networkName, ac.Name).Do();
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting old access_config: %s", err)
|
||||||
|
}
|
||||||
|
opErr := resourceOperationWaitZone(config, op, zone, "old access_config to delete")
|
||||||
|
if opErr != nil {
|
||||||
|
return opErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new ones
|
||||||
|
accessConfigsCount := d.Get(prefix + ".access_config.#").(int)
|
||||||
|
for j := 0; j < accessConfigsCount; j++ {
|
||||||
|
acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j)
|
||||||
|
ac := &compute.AccessConfig{
|
||||||
|
Type: "ONE_TO_ONE_NAT",
|
||||||
|
NatIP: d.Get(acPrefix + ".nat_ip").(string),
|
||||||
|
}
|
||||||
|
op, err := config.clientCompute.Instances.AddAccessConfig(
|
||||||
|
config.Project, zone, d.Id(), networkName, ac).Do();
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error adding new access_config: %s", err)
|
||||||
|
}
|
||||||
|
opErr := resourceOperationWaitZone(config, op, zone, "new access_config to add")
|
||||||
|
if opErr != nil {
|
||||||
|
return opErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We made it, disable partial mode
|
// We made it, disable partial mode
|
||||||
d.Partial(false)
|
d.Partial(false)
|
||||||
|
|
||||||
|
@ -500,32 +710,16 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
|
||||||
func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error {
|
func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
config := meta.(*Config)
|
config := meta.(*Config)
|
||||||
|
|
||||||
op, err := config.clientCompute.Instances.Delete(
|
zone := d.Get("zone").(string)
|
||||||
config.Project, d.Get("zone").(string), d.Id()).Do()
|
op, err := config.clientCompute.Instances.Delete(config.Project, zone, d.Id()).Do()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error deleting instance: %s", err)
|
return fmt.Errorf("Error deleting instance: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the operation to complete
|
// Wait for the operation to complete
|
||||||
w := &OperationWaiter{
|
opErr := resourceOperationWaitZone(config, op, zone, "instance to delete")
|
||||||
Service: config.clientCompute,
|
if opErr != nil {
|
||||||
Op: op,
|
return opErr
|
||||||
Project: config.Project,
|
|
||||||
Zone: d.Get("zone").(string),
|
|
||||||
Type: OperationWaitZone,
|
|
||||||
}
|
|
||||||
state := w.Conf()
|
|
||||||
state.Delay = 5 * time.Second
|
|
||||||
state.Timeout = 5 * time.Minute
|
|
||||||
state.MinTimeout = 2 * time.Second
|
|
||||||
opRaw, err := state.WaitForState()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error waiting for instance to delete: %s", err)
|
|
||||||
}
|
|
||||||
op = opRaw.(*compute.Operation)
|
|
||||||
if op.Error != nil {
|
|
||||||
// Return the error
|
|
||||||
return OperationError(*op.Error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
|
@ -577,16 +771,3 @@ func resourceInstanceTags(d *schema.ResourceData) *compute.Tags {
|
||||||
|
|
||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
// resourceInstanceNatIP acquires the first NatIP with a "ONE_TO_ONE_NAT" type
|
|
||||||
// in the compute.NetworkInterface's AccessConfigs.
|
|
||||||
func resourceInstanceNatIP(iface *compute.NetworkInterface) (natIP string) {
|
|
||||||
for _, config := range iface.AccessConfigs {
|
|
||||||
if config.Type == "ONE_TO_ONE_NAT" {
|
|
||||||
natIP = config.NatIP
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return natIP
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,6 +10,28 @@ import (
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestAccComputeInstance_basic_deprecated_network(t *testing.T) {
|
||||||
|
var instance compute.Instance
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeInstanceDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeInstance_basic_deprecated_network,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeInstanceExists(
|
||||||
|
"google_compute_instance.foobar", &instance),
|
||||||
|
testAccCheckComputeInstanceTag(&instance, "foo"),
|
||||||
|
testAccCheckComputeInstanceMetadata(&instance, "foo", "bar"),
|
||||||
|
testAccCheckComputeInstanceDisk(&instance, "terraform-test", true, true),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccComputeInstance_basic(t *testing.T) {
|
func TestAccComputeInstance_basic(t *testing.T) {
|
||||||
var instance compute.Instance
|
var instance compute.Instance
|
||||||
|
|
||||||
|
@ -45,7 +67,7 @@ func TestAccComputeInstance_IP(t *testing.T) {
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckComputeInstanceExists(
|
testAccCheckComputeInstanceExists(
|
||||||
"google_compute_instance.foobar", &instance),
|
"google_compute_instance.foobar", &instance),
|
||||||
testAccCheckComputeInstanceNetwork(&instance),
|
testAccCheckComputeInstanceAccessConfigHasIP(&instance),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -73,6 +95,35 @@ func TestAccComputeInstance_disks(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccComputeInstance_update_deprecated_network(t *testing.T) {
|
||||||
|
var instance compute.Instance
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeInstanceDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeInstance_basic_deprecated_network,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeInstanceExists(
|
||||||
|
"google_compute_instance.foobar", &instance),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeInstance_update_deprecated_network,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeInstanceExists(
|
||||||
|
"google_compute_instance.foobar", &instance),
|
||||||
|
testAccCheckComputeInstanceMetadata(
|
||||||
|
&instance, "bar", "baz"),
|
||||||
|
testAccCheckComputeInstanceTag(&instance, "baz"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccComputeInstance_update(t *testing.T) {
|
func TestAccComputeInstance_update(t *testing.T) {
|
||||||
var instance compute.Instance
|
var instance compute.Instance
|
||||||
|
|
||||||
|
@ -96,6 +147,7 @@ func TestAccComputeInstance_update(t *testing.T) {
|
||||||
testAccCheckComputeInstanceMetadata(
|
testAccCheckComputeInstanceMetadata(
|
||||||
&instance, "bar", "baz"),
|
&instance, "bar", "baz"),
|
||||||
testAccCheckComputeInstanceTag(&instance, "baz"),
|
testAccCheckComputeInstanceTag(&instance, "baz"),
|
||||||
|
testAccCheckComputeInstanceAccessConfig(&instance),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -173,7 +225,19 @@ func testAccCheckComputeInstanceMetadata(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckComputeInstanceNetwork(instance *compute.Instance) resource.TestCheckFunc {
|
func testAccCheckComputeInstanceAccessConfig(instance *compute.Instance) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
for _, i := range instance.NetworkInterfaces {
|
||||||
|
if len(i.AccessConfigs) == 0 {
|
||||||
|
return fmt.Errorf("no access_config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeInstanceAccessConfigHasIP(instance *compute.Instance) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
for _, i := range instance.NetworkInterfaces {
|
for _, i := range instance.NetworkInterfaces {
|
||||||
for _, c := range i.AccessConfigs {
|
for _, c := range i.AccessConfigs {
|
||||||
|
@ -219,7 +283,7 @@ func testAccCheckComputeInstanceTag(instance *compute.Instance, n string) resour
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const testAccComputeInstance_basic = `
|
const testAccComputeInstance_basic_deprecated_network = `
|
||||||
resource "google_compute_instance" "foobar" {
|
resource "google_compute_instance" "foobar" {
|
||||||
name = "terraform-test"
|
name = "terraform-test"
|
||||||
machine_type = "n1-standard-1"
|
machine_type = "n1-standard-1"
|
||||||
|
@ -240,7 +304,7 @@ resource "google_compute_instance" "foobar" {
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
const testAccComputeInstance_update = `
|
const testAccComputeInstance_update_deprecated_network = `
|
||||||
resource "google_compute_instance" "foobar" {
|
resource "google_compute_instance" "foobar" {
|
||||||
name = "terraform-test"
|
name = "terraform-test"
|
||||||
machine_type = "n1-standard-1"
|
machine_type = "n1-standard-1"
|
||||||
|
@ -260,6 +324,49 @@ resource "google_compute_instance" "foobar" {
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
const testAccComputeInstance_basic = `
|
||||||
|
resource "google_compute_instance" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
machine_type = "n1-standard-1"
|
||||||
|
zone = "us-central1-a"
|
||||||
|
can_ip_forward = false
|
||||||
|
tags = ["foo", "bar"]
|
||||||
|
|
||||||
|
disk {
|
||||||
|
image = "debian-7-wheezy-v20140814"
|
||||||
|
}
|
||||||
|
|
||||||
|
network_interface {
|
||||||
|
network = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
foo = "bar"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
// Update metadata, tags, and network_interface
|
||||||
|
const testAccComputeInstance_update = `
|
||||||
|
resource "google_compute_instance" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
machine_type = "n1-standard-1"
|
||||||
|
zone = "us-central1-a"
|
||||||
|
tags = ["baz"]
|
||||||
|
|
||||||
|
disk {
|
||||||
|
image = "debian-7-wheezy-v20140814"
|
||||||
|
}
|
||||||
|
|
||||||
|
network_interface {
|
||||||
|
network = "default"
|
||||||
|
access_config { }
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
bar = "baz"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
const testAccComputeInstance_ip = `
|
const testAccComputeInstance_ip = `
|
||||||
resource "google_compute_address" "foo" {
|
resource "google_compute_address" "foo" {
|
||||||
name = "foo"
|
name = "foo"
|
||||||
|
@ -275,9 +382,11 @@ resource "google_compute_instance" "foobar" {
|
||||||
image = "debian-7-wheezy-v20140814"
|
image = "debian-7-wheezy-v20140814"
|
||||||
}
|
}
|
||||||
|
|
||||||
network {
|
network_interface {
|
||||||
source = "default"
|
network = "default"
|
||||||
address = "${google_compute_address.foo.address}"
|
access_config {
|
||||||
|
nat_ip = "${google_compute_address.foo.address}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
|
@ -307,8 +416,8 @@ resource "google_compute_instance" "foobar" {
|
||||||
auto_delete = false
|
auto_delete = false
|
||||||
}
|
}
|
||||||
|
|
||||||
network {
|
network_interface {
|
||||||
source = "default"
|
network = "default"
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
|
|
|
@ -0,0 +1,404 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"code.google.com/p/google-api-go-client/googleapi"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceComputeTargetPool() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceComputeTargetPoolCreate,
|
||||||
|
Read: resourceComputeTargetPoolRead,
|
||||||
|
Delete: resourceComputeTargetPoolDelete,
|
||||||
|
Update: resourceComputeTargetPoolUpdate,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"backup_pool": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"description": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"failover_ratio": &schema.Schema{
|
||||||
|
Type: schema.TypeFloat,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"health_checks": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
},
|
||||||
|
|
||||||
|
"instances": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
},
|
||||||
|
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"self_link": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"session_affinity": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertStringArr(ifaceArr []interface{}) []string {
|
||||||
|
arr := make([]string, len(ifaceArr))
|
||||||
|
for i, v := range ifaceArr {
|
||||||
|
arr[i] = v.(string)
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitOp(config *Config, op *compute.Operation,
|
||||||
|
resource string, action string) (*compute.Operation, error) {
|
||||||
|
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Region: config.Region,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitRegion,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error waiting for %s to %s: %s", resource, action, err)
|
||||||
|
}
|
||||||
|
return opRaw.(*compute.Operation), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Healthchecks need to exist before being referred to from the target pool.
|
||||||
|
func convertHealthChecks(config *Config, names []string) ([]string, error) {
|
||||||
|
urls := make([]string, len(names))
|
||||||
|
for i, name := range names {
|
||||||
|
// Look up the healthcheck
|
||||||
|
res, err := config.clientCompute.HttpHealthChecks.Get(config.Project, name).Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error reading HealthCheck: %s", err)
|
||||||
|
}
|
||||||
|
urls[i] = res.SelfLink
|
||||||
|
}
|
||||||
|
return urls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instances do not need to exist yet, so we simply generate URLs.
|
||||||
|
// Instances can be full URLS or zone/name
|
||||||
|
func convertInstances(config *Config, names []string) ([]string, error) {
|
||||||
|
urls := make([]string, len(names))
|
||||||
|
for i, name := range names {
|
||||||
|
if strings.HasPrefix(name, "https://www.googleapis.com/compute/v1/") {
|
||||||
|
urls[i] = name
|
||||||
|
} else {
|
||||||
|
splitName := strings.Split(name, "/")
|
||||||
|
if len(splitName) != 2 {
|
||||||
|
return nil, fmt.Errorf("Invalid instance name, require URL or zone/name: %s", name)
|
||||||
|
} else {
|
||||||
|
urls[i] = fmt.Sprintf(
|
||||||
|
"https://www.googleapis.com/compute/v1/projects/%s/zones/%s/instances/%s",
|
||||||
|
config.Project, splitName[0], splitName[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return urls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeTargetPoolCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
hchkUrls, err := convertHealthChecks(
|
||||||
|
config, convertStringArr(d.Get("health_checks").([]interface{})))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceUrls, err := convertInstances(
|
||||||
|
config, convertStringArr(d.Get("instances").([]interface{})))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the parameter
|
||||||
|
tpool := &compute.TargetPool{
|
||||||
|
BackupPool: d.Get("backup_pool").(string),
|
||||||
|
Description: d.Get("description").(string),
|
||||||
|
HealthChecks: hchkUrls,
|
||||||
|
Instances: instanceUrls,
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
SessionAffinity: d.Get("session_affinity").(string),
|
||||||
|
}
|
||||||
|
if d.Get("failover_ratio") != nil {
|
||||||
|
tpool.FailoverRatio = d.Get("failover_ratio").(float64)
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] TargetPool insert request: %#v", tpool)
|
||||||
|
op, err := config.clientCompute.TargetPools.Insert(
|
||||||
|
config.Project, config.Region, tpool).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating TargetPool: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It probably maybe worked, so store the ID now
|
||||||
|
d.SetId(tpool.Name)
|
||||||
|
|
||||||
|
op, err = waitOp(config, op, "TargetPool", "create")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if op.Error != nil {
|
||||||
|
// The resource didn't actually create
|
||||||
|
d.SetId("")
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeTargetPoolRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func calcAddRemove(from []string, to []string) ([]string, []string) {
|
||||||
|
add := make([]string, 0)
|
||||||
|
remove := make([]string, 0)
|
||||||
|
for _, u := range to {
|
||||||
|
found := false
|
||||||
|
for _, v := range from {
|
||||||
|
if u == v {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
add = append(add, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, u := range from {
|
||||||
|
found := false
|
||||||
|
for _, v := range to {
|
||||||
|
if u == v {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
remove = append(remove, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return add, remove
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func resourceComputeTargetPoolUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
d.Partial(true)
|
||||||
|
|
||||||
|
if d.HasChange("health_checks") {
|
||||||
|
|
||||||
|
from_, to_ := d.GetChange("health_checks")
|
||||||
|
from := convertStringArr(from_.([]interface{}))
|
||||||
|
to := convertStringArr(to_.([]interface{}))
|
||||||
|
fromUrls, err := convertHealthChecks(config, from)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
toUrls, err := convertHealthChecks(config, to)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
add, remove := calcAddRemove(fromUrls, toUrls)
|
||||||
|
|
||||||
|
removeReq := &compute.TargetPoolsRemoveHealthCheckRequest{
|
||||||
|
HealthChecks: make([]*compute.HealthCheckReference, len(remove)),
|
||||||
|
}
|
||||||
|
for i, v := range remove {
|
||||||
|
removeReq.HealthChecks[i] = &compute.HealthCheckReference{HealthCheck: v}
|
||||||
|
}
|
||||||
|
op, err := config.clientCompute.TargetPools.RemoveHealthCheck(
|
||||||
|
config.Project, config.Region, d.Id(), removeReq).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating health_check: %s", err)
|
||||||
|
}
|
||||||
|
op, err = waitOp(config, op, "TargetPool", "removing HealthChecks")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if op.Error != nil {
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
addReq := &compute.TargetPoolsAddHealthCheckRequest{
|
||||||
|
HealthChecks: make([]*compute.HealthCheckReference, len(add)),
|
||||||
|
}
|
||||||
|
for i, v := range add {
|
||||||
|
addReq.HealthChecks[i] = &compute.HealthCheckReference{HealthCheck: v}
|
||||||
|
}
|
||||||
|
op, err = config.clientCompute.TargetPools.AddHealthCheck(
|
||||||
|
config.Project, config.Region, d.Id(), addReq).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating health_check: %s", err)
|
||||||
|
}
|
||||||
|
op, err = waitOp(config, op, "TargetPool", "adding HealthChecks")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if op.Error != nil {
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetPartial("health_checks")
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("instances") {
|
||||||
|
|
||||||
|
from_, to_ := d.GetChange("instances")
|
||||||
|
from := convertStringArr(from_.([]interface{}))
|
||||||
|
to := convertStringArr(to_.([]interface{}))
|
||||||
|
fromUrls, err := convertInstances(config, from)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
toUrls, err := convertInstances(config, to)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
add, remove := calcAddRemove(fromUrls, toUrls)
|
||||||
|
|
||||||
|
addReq := &compute.TargetPoolsAddInstanceRequest{
|
||||||
|
Instances: make([]*compute.InstanceReference, len(add)),
|
||||||
|
}
|
||||||
|
for i, v := range add {
|
||||||
|
addReq.Instances[i] = &compute.InstanceReference{Instance: v}
|
||||||
|
}
|
||||||
|
op, err := config.clientCompute.TargetPools.AddInstance(
|
||||||
|
config.Project, config.Region, d.Id(), addReq).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating instances: %s", err)
|
||||||
|
}
|
||||||
|
op, err = waitOp(config, op, "TargetPool", "adding instances")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if op.Error != nil {
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeReq := &compute.TargetPoolsRemoveInstanceRequest{
|
||||||
|
Instances: make([]*compute.InstanceReference, len(remove)),
|
||||||
|
}
|
||||||
|
for i, v := range remove {
|
||||||
|
removeReq.Instances[i] = &compute.InstanceReference{Instance: v}
|
||||||
|
}
|
||||||
|
op, err = config.clientCompute.TargetPools.RemoveInstance(
|
||||||
|
config.Project, config.Region, d.Id(), removeReq).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating instances: %s", err)
|
||||||
|
}
|
||||||
|
op, err = waitOp(config, op, "TargetPool", "removing instances")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if op.Error != nil {
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetPartial("instances")
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("backup_pool") {
|
||||||
|
bpool_name := d.Get("backup_pool").(string)
|
||||||
|
tref := &compute.TargetReference{
|
||||||
|
Target: bpool_name,
|
||||||
|
}
|
||||||
|
op, err := config.clientCompute.TargetPools.SetBackup(
|
||||||
|
config.Project, config.Region, d.Id(), tref).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating backup_pool: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
op, err = waitOp(config, op, "TargetPool", "updating backup_pool")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if op.Error != nil {
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetPartial("backup_pool")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Partial(false)
|
||||||
|
|
||||||
|
return resourceComputeTargetPoolRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeTargetPoolRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
tpool, err := config.clientCompute.TargetPools.Get(
|
||||||
|
config.Project, config.Region, 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 TargetPool: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("self_link", tpool.SelfLink)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeTargetPoolDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Delete the TargetPool
|
||||||
|
op, err := config.clientCompute.TargetPools.Delete(
|
||||||
|
config.Project, config.Region, d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting TargetPool: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
op, err = waitOp(config, op, "TargetPool", "delete")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if op.Error != nil {
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeTargetPool_basic(t *testing.T) {
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeTargetPoolDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeTargetPool_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeTargetPoolExists(
|
||||||
|
"google_compute_target_pool.foobar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeTargetPoolDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "google_compute_target_pool" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := config.clientCompute.TargetPools.Get(
|
||||||
|
config.Project, config.Region, rs.Primary.ID).Do()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("TargetPool still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeTargetPoolExists(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)
|
||||||
|
|
||||||
|
found, err := config.clientCompute.TargetPools.Get(
|
||||||
|
config.Project, config.Region, rs.Primary.ID).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.Name != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("TargetPool not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccComputeTargetPool_basic = `
|
||||||
|
resource "google_compute_target_pool" "foobar" {
|
||||||
|
description = "Resource created for Terraform acceptance testing"
|
||||||
|
instances = ["us-central1-a/foo", "us-central1-b/bar"]
|
||||||
|
name = "terraform-test"
|
||||||
|
session_affinity = "CLIENT_IP_PROTO"
|
||||||
|
}`
|
|
@ -34,7 +34,7 @@ func runCheckpoint(c *Config) {
|
||||||
|
|
||||||
version := Version
|
version := Version
|
||||||
if VersionPrerelease != "" {
|
if VersionPrerelease != "" {
|
||||||
version += fmt.Sprintf(".%s", VersionPrerelease)
|
version += fmt.Sprintf("-%s", VersionPrerelease)
|
||||||
}
|
}
|
||||||
|
|
||||||
signaturePath := filepath.Join(configDir, "checkpoint_signature")
|
signaturePath := filepath.Join(configDir, "checkpoint_signature")
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (c *VersionCommand) Run(args []string) int {
|
||||||
|
|
||||||
fmt.Fprintf(&versionString, "Terraform v%s", c.Version)
|
fmt.Fprintf(&versionString, "Terraform v%s", c.Version)
|
||||||
if c.VersionPrerelease != "" {
|
if c.VersionPrerelease != "" {
|
||||||
fmt.Fprintf(&versionString, ".%s", c.VersionPrerelease)
|
fmt.Fprintf(&versionString, "-%s", c.VersionPrerelease)
|
||||||
|
|
||||||
if c.Revision != "" {
|
if c.Revision != "" {
|
||||||
fmt.Fprintf(&versionString, " (%s)", c.Revision)
|
fmt.Fprintf(&versionString, " (%s)", c.Revision)
|
||||||
|
|
|
@ -2,7 +2,6 @@ package module
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,7 +40,7 @@ func Detect(src string, pwd string) (string, error) {
|
||||||
u, err := urlParse(getSrc)
|
u, err := urlParse(getSrc)
|
||||||
if err == nil && u.Scheme != "" {
|
if err == nil && u.Scheme != "" {
|
||||||
// Valid URL
|
// Valid URL
|
||||||
return u.String(), nil
|
return src, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range Detectors {
|
for _, d := range Detectors {
|
||||||
|
@ -67,7 +66,7 @@ func Detect(src string, pwd string) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if subDir != "" {
|
if subDir != "" {
|
||||||
u, err := url.Parse(result)
|
u, err := urlParse(result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Error parsing URL: %s", err)
|
return "", fmt.Errorf("Error parsing URL: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,12 @@ func TestDetect(t *testing.T) {
|
||||||
"git::https://github.com/hashicorp/foo.git//bar",
|
"git::https://github.com/hashicorp/foo.git//bar",
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"git::https://github.com/hashicorp/consul.git",
|
||||||
|
"",
|
||||||
|
"git::https://github.com/hashicorp/consul.git",
|
||||||
|
false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HgGetter is a Getter implementation that will download a module from
|
// HgGetter is a Getter implementation that will download a module from
|
||||||
|
@ -16,34 +17,40 @@ func (g *HgGetter) Get(dst string, u *url.URL) error {
|
||||||
return fmt.Errorf("hg must be available and on the PATH")
|
return fmt.Errorf("hg must be available and on the PATH")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newURL, err := urlParse(u.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fixWindowsDrivePath(newURL) {
|
||||||
|
// See valid file path form on http://www.selenic.com/hg/help/urls
|
||||||
|
newURL.Path = fmt.Sprintf("/%s", newURL.Path)
|
||||||
|
}
|
||||||
|
|
||||||
// Extract some query parameters we use
|
// Extract some query parameters we use
|
||||||
var rev string
|
var rev string
|
||||||
q := u.Query()
|
q := newURL.Query()
|
||||||
if len(q) > 0 {
|
if len(q) > 0 {
|
||||||
rev = q.Get("rev")
|
rev = q.Get("rev")
|
||||||
q.Del("rev")
|
q.Del("rev")
|
||||||
|
|
||||||
// Copy the URL
|
newURL.RawQuery = q.Encode()
|
||||||
var newU url.URL = *u
|
|
||||||
u = &newU
|
|
||||||
u.RawQuery = q.Encode()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := os.Stat(dst)
|
_, err = os.Stat(dst)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err := g.clone(dst, u); err != nil {
|
if err := g.clone(dst, newURL); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.pull(dst, u); err != nil {
|
if err := g.pull(dst, newURL); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return g.update(dst, u, rev)
|
return g.update(dst, newURL, rev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *HgGetter) clone(dst string, u *url.URL) error {
|
func (g *HgGetter) clone(dst string, u *url.URL) error {
|
||||||
|
@ -67,3 +74,14 @@ func (g *HgGetter) update(dst string, u *url.URL, rev string) error {
|
||||||
cmd.Dir = dst
|
cmd.Dir = dst
|
||||||
return getRunCommand(cmd)
|
return getRunCommand(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fixWindowsDrivePath(u *url.URL) bool {
|
||||||
|
// hg assumes a file:/// prefix for Windows drive letter file paths.
|
||||||
|
// (e.g. file:///c:/foo/bar)
|
||||||
|
// If the URL Path does not begin with a '/' character, the resulting URL
|
||||||
|
// path will have a file:// prefix. (e.g. file://c:/foo/bar)
|
||||||
|
// See http://www.selenic.com/hg/help/urls and the examples listed in
|
||||||
|
// http://selenic.com/repo/hg-stable/file/1265a3a71d75/mercurial/util.py#l1936
|
||||||
|
return runtime.GOOS == "windows" && u.Scheme == "file" &&
|
||||||
|
len(u.Path) > 1 && u.Path[0] != '/' && u.Path[1] == ':'
|
||||||
|
}
|
||||||
|
|
|
@ -1,55 +1,63 @@
|
||||||
package module
|
package module
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
"strings"
|
||||||
|
)
|
||||||
func urlParse(rawURL string) (*url.URL, error) {
|
|
||||||
if runtime.GOOS == "windows" {
|
func urlParse(rawURL string) (*url.URL, error) {
|
||||||
if len(rawURL) > 1 && rawURL[1] == ':' {
|
if runtime.GOOS == "windows" {
|
||||||
// Assume we're dealing with a file path.
|
// Make sure we're using "/" on Windows. URLs are "/"-based.
|
||||||
rawURL = fmtFileURL(rawURL)
|
rawURL = filepath.ToSlash(rawURL)
|
||||||
} else {
|
}
|
||||||
// Make sure we're using "/" on Windows. URLs are "/"-based.
|
u, err := url.Parse(rawURL)
|
||||||
rawURL = filepath.ToSlash(rawURL)
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
}
|
}
|
||||||
u, err := url.Parse(rawURL)
|
|
||||||
if err != nil {
|
if runtime.GOOS != "windows" {
|
||||||
return nil, err
|
return u, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS != "windows" {
|
if len(rawURL) > 1 && rawURL[1] == ':' {
|
||||||
return u, err
|
// Assume we're dealing with a drive letter file path on Windows.
|
||||||
}
|
// We need to adjust the URL Path for drive letter file paths
|
||||||
|
// because url.Parse("c:/users/user") yields URL Scheme = "c"
|
||||||
if u.Scheme != "file" {
|
// and URL path = "/users/user".
|
||||||
return u, err
|
u.Path = fmt.Sprintf("%s:%s", u.Scheme, u.Path)
|
||||||
}
|
u.Scheme = ""
|
||||||
|
}
|
||||||
// Remove leading slash for absolute file paths on Windows.
|
|
||||||
// For example, url.Parse yields u.Path = "/C:/Users/user" for
|
if len(u.Host) > 1 && u.Host[1] == ':' && strings.HasPrefix(rawURL, "file://") {
|
||||||
// rawurl = "file:///C:/Users/user", which is an incorrect syntax.
|
// Assume we're dealing with a drive letter file path on Windows
|
||||||
if len(u.Path) > 2 && u.Path[0] == '/' && u.Path[2] == ':' {
|
// where the drive letter has been parsed into the URL Host.
|
||||||
u.Path = u.Path[1:]
|
u.Path = fmt.Sprintf("%s%s", u.Host, u.Path)
|
||||||
}
|
u.Host = ""
|
||||||
|
}
|
||||||
return u, err
|
|
||||||
}
|
// Remove leading slash for absolute file paths on Windows.
|
||||||
|
// For example, url.Parse yields u.Path = "/C:/Users/user" for
|
||||||
func fmtFileURL(path string) string {
|
// rawURL = "file:///C:/Users/user", which is an incorrect syntax.
|
||||||
if runtime.GOOS == "windows" {
|
if len(u.Path) > 2 && u.Path[0] == '/' && u.Path[2] == ':' {
|
||||||
// Make sure we're using "/" on Windows. URLs are "/"-based.
|
u.Path = u.Path[1:]
|
||||||
path = filepath.ToSlash(path)
|
}
|
||||||
}
|
|
||||||
|
return u, err
|
||||||
// Make sure that we don't start with "/" since we add that below.
|
}
|
||||||
if path[0] == '/' {
|
|
||||||
path = path[1:]
|
func fmtFileURL(path string) string {
|
||||||
}
|
if runtime.GOOS == "windows" {
|
||||||
|
// Make sure we're using "/" on Windows. URLs are "/"-based.
|
||||||
return fmt.Sprintf("file:///%s", path)
|
path = filepath.ToSlash(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure that we don't start with "/" since we add that below.
|
||||||
|
if path[0] == '/' {
|
||||||
|
path = path[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("file:///%s", path)
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
output "address" {
|
output "address" {
|
||||||
value = "Instances: ${aws_instance.web.*.id}"
|
value = "Instances: ${element(aws_instance.web.*.id, 0)}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,11 @@ func (r *DiffFieldReader) readMap(
|
||||||
if !strings.HasPrefix(k, prefix) {
|
if !strings.HasPrefix(k, prefix) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(k, prefix+"#") {
|
||||||
|
// Ignore the count field
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
resultSet = true
|
resultSet = true
|
||||||
|
|
||||||
k = k[len(prefix):]
|
k = k[len(prefix):]
|
||||||
|
@ -148,8 +153,8 @@ func (r *DiffFieldReader) readSet(
|
||||||
if !strings.HasPrefix(k, prefix) {
|
if !strings.HasPrefix(k, prefix) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(k, prefix+"#") {
|
if strings.HasSuffix(k, "#") {
|
||||||
// Ignore the count field
|
// Ignore any count field
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,51 @@ func TestDiffFieldReader_impl(t *testing.T) {
|
||||||
var _ FieldReader = new(DiffFieldReader)
|
var _ FieldReader = new(DiffFieldReader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/hashicorp/terraform/issues/914
|
||||||
|
func TestDiffFieldReader_MapHandling(t *testing.T) {
|
||||||
|
schema := map[string]*Schema{
|
||||||
|
"tags": &Schema{
|
||||||
|
Type: TypeMap,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r := &DiffFieldReader{
|
||||||
|
Schema: schema,
|
||||||
|
Diff: &terraform.InstanceDiff{
|
||||||
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||||
|
"tags.#": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "1",
|
||||||
|
New: "2",
|
||||||
|
},
|
||||||
|
"tags.baz": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "qux",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Source: &MapFieldReader{
|
||||||
|
Schema: schema,
|
||||||
|
Map: BasicMapReader(map[string]string{
|
||||||
|
"tags.#": "1",
|
||||||
|
"tags.foo": "bar",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := r.ReadField([]string{"tags"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadField failed: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "qux",
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expected, result.Value) {
|
||||||
|
t.Fatalf("bad: DiffHandling\n\nexpected: %#v\n\ngot: %#v\n\n", expected, result.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDiffFieldReader_extra(t *testing.T) {
|
func TestDiffFieldReader_extra(t *testing.T) {
|
||||||
schema := map[string]*Schema{
|
schema := map[string]*Schema{
|
||||||
"stringComputed": &Schema{Type: TypeString},
|
"stringComputed": &Schema{Type: TypeString},
|
||||||
|
|
|
@ -11,8 +11,6 @@
|
||||||
// A good starting point is to view the Provider structure.
|
// A good starting point is to view the Provider structure.
|
||||||
package schema
|
package schema
|
||||||
|
|
||||||
//go:generate stringer -type=ValueType
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
@ -25,47 +23,6 @@ import (
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValueType is an enum of the type that can be represented by a schema.
|
|
||||||
type ValueType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
TypeInvalid ValueType = iota
|
|
||||||
TypeBool
|
|
||||||
TypeInt
|
|
||||||
TypeFloat
|
|
||||||
TypeString
|
|
||||||
TypeList
|
|
||||||
TypeMap
|
|
||||||
TypeSet
|
|
||||||
typeObject
|
|
||||||
)
|
|
||||||
|
|
||||||
// Zero returns the zero value for a type.
|
|
||||||
func (t ValueType) Zero() interface{} {
|
|
||||||
switch t {
|
|
||||||
case TypeInvalid:
|
|
||||||
return nil
|
|
||||||
case TypeBool:
|
|
||||||
return false
|
|
||||||
case TypeInt:
|
|
||||||
return 0
|
|
||||||
case TypeFloat:
|
|
||||||
return 0.0
|
|
||||||
case TypeString:
|
|
||||||
return ""
|
|
||||||
case TypeList:
|
|
||||||
return []interface{}{}
|
|
||||||
case TypeMap:
|
|
||||||
return map[string]interface{}{}
|
|
||||||
case TypeSet:
|
|
||||||
return nil
|
|
||||||
case typeObject:
|
|
||||||
return map[string]interface{}{}
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unknown type %s", t))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schema is used to describe the structure of a value.
|
// Schema is used to describe the structure of a value.
|
||||||
//
|
//
|
||||||
// Read the documentation of the struct elements for important details.
|
// Read the documentation of the struct elements for important details.
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
//go:generate stringer -type=ValueType valuetype.go
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// ValueType is an enum of the type that can be represented by a schema.
|
||||||
|
type ValueType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeInvalid ValueType = iota
|
||||||
|
TypeBool
|
||||||
|
TypeInt
|
||||||
|
TypeFloat
|
||||||
|
TypeString
|
||||||
|
TypeList
|
||||||
|
TypeMap
|
||||||
|
TypeSet
|
||||||
|
typeObject
|
||||||
|
)
|
||||||
|
|
||||||
|
// Zero returns the zero value for a type.
|
||||||
|
func (t ValueType) Zero() interface{} {
|
||||||
|
switch t {
|
||||||
|
case TypeInvalid:
|
||||||
|
return nil
|
||||||
|
case TypeBool:
|
||||||
|
return false
|
||||||
|
case TypeInt:
|
||||||
|
return 0
|
||||||
|
case TypeFloat:
|
||||||
|
return 0.0
|
||||||
|
case TypeString:
|
||||||
|
return ""
|
||||||
|
case TypeList:
|
||||||
|
return []interface{}{}
|
||||||
|
case TypeMap:
|
||||||
|
return map[string]interface{}{}
|
||||||
|
case TypeSet:
|
||||||
|
return nil
|
||||||
|
case typeObject:
|
||||||
|
return map[string]interface{}{}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown type %s", t))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
// generated by stringer -type=ValueType; DO NOT EDIT
|
// generated by stringer -type=ValueType valuetype.go; DO NOT EDIT
|
||||||
|
|
||||||
package schema
|
package schema
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,10 @@ Engine = Base.extend({
|
||||||
this.background.className += ' show';
|
this.background.className += ' show';
|
||||||
this.canvas.style.opacity = 1;
|
this.canvas.style.opacity = 1;
|
||||||
|
|
||||||
new Chainable()
|
// We have to pass the engine into Chainable to
|
||||||
|
// enable the timers to properly attach to the
|
||||||
|
// run/render loop
|
||||||
|
new Chainable(this)
|
||||||
.wait(1000)
|
.wait(1000)
|
||||||
.then(function(){
|
.then(function(){
|
||||||
this.starGeneratorRate = 200;
|
this.starGeneratorRate = 200;
|
||||||
|
@ -202,6 +205,13 @@ Engine = Base.extend({
|
||||||
this.now = Date.now() / 1000;
|
this.now = Date.now() / 1000;
|
||||||
this.tick = Math.min(this.now - this.last, 0.017);
|
this.tick = Math.min(this.now - this.last, 0.017);
|
||||||
|
|
||||||
|
// We must attach the chainable timer to the engine
|
||||||
|
// run/render loop or else things can get pretty
|
||||||
|
// out of wack
|
||||||
|
if (this.updateChainTimer) {
|
||||||
|
this.updateChainTimer(this.tick);
|
||||||
|
}
|
||||||
|
|
||||||
// Update all particles... may need to be optimized
|
// Update all particles... may need to be optimized
|
||||||
for (p = 0; p < this.particles.length; p++) {
|
for (p = 0; p < this.particles.length; p++) {
|
||||||
this.particles[p].update(this);
|
this.particles[p].update(this);
|
||||||
|
|
|
@ -1,12 +1,29 @@
|
||||||
(function(){
|
(function(){
|
||||||
|
|
||||||
var Chainable = function(){
|
var Chainable = function(engine){
|
||||||
|
this.engine = engine;
|
||||||
this._chain = [];
|
this._chain = [];
|
||||||
|
this._updateTimer = this._updateTimer.bind(this);
|
||||||
this._cycle = this._cycle.bind(this);
|
this._cycle = this._cycle.bind(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
Chainable.prototype._running = false;
|
Chainable.prototype._running = false;
|
||||||
|
|
||||||
|
Chainable.prototype._updateTimer = function(tick){
|
||||||
|
this._timer += tick;
|
||||||
|
if (this._timer >= this._timerMax) {
|
||||||
|
this.resetTimer();
|
||||||
|
this._cycle();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Chainable.prototype.resetTimer = function(){
|
||||||
|
this.engine.updateChainTimer = undefined;
|
||||||
|
this._timer = 0;
|
||||||
|
this._timerMax = 0;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
Chainable.prototype.start = function(){
|
Chainable.prototype.start = function(){
|
||||||
if (this._running || !this._chain.length) {
|
if (this._running || !this._chain.length) {
|
||||||
return this;
|
return this;
|
||||||
|
@ -19,9 +36,8 @@ Chainable.prototype.reset = function(){
|
||||||
if (!this._running) {
|
if (!this._running) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
clearTimeout(this._timer);
|
this.resetTimer();
|
||||||
this._timer = null;
|
this._timer = 0;
|
||||||
this._chain.length = 0;
|
|
||||||
this._running = false;
|
this._running = false;
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
@ -40,8 +56,10 @@ Chainable.prototype._cycle = function(){
|
||||||
return this._cycle();
|
return this._cycle();
|
||||||
}
|
}
|
||||||
if (current.type === 'wait') {
|
if (current.type === 'wait') {
|
||||||
clearTimeout(this._timer);
|
this.resetTimer();
|
||||||
this._timer = setTimeout(this._cycle, current.time || 0);
|
// Convert timer to seconds
|
||||||
|
this._timerMax = current.time / 1000;
|
||||||
|
this.engine.updateChainTimer = this._updateTimer;
|
||||||
current = null;
|
current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
---
|
||||||
|
layout: "aws"
|
||||||
|
page_title: "AWS: aws_main_route_table_association"
|
||||||
|
sidebar_current: "docs-aws-resource-main-route-table-assoc"
|
||||||
|
description: |-
|
||||||
|
Provides a resource for managing the main routing table of a VPC.
|
||||||
|
---
|
||||||
|
|
||||||
|
# aws\_main\_route\_table\_<wbr>association
|
||||||
|
|
||||||
|
Provides a resource for managing the main routing table of a VPC.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "aws_main_route_table_association" "a" {
|
||||||
|
vpc_id = "${aws_vpc.foo.id}"
|
||||||
|
route_table_id = "${aws_route_table.bar.id}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `vpc_id` - (Required) The ID of the VPC whose main route table should be set
|
||||||
|
* `route_table_id` - (Required) The ID of the Route Table to set as the new
|
||||||
|
main route table for the target VPC
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `id` - The ID of the Route Table Association
|
||||||
|
* `original_route_table_id` - Used internally, see __Notes__ below
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
On VPC creation, the AWS API always creates an initial Main Route Table. This
|
||||||
|
resource records the ID of that Route Table under `original_route_table_id`.
|
||||||
|
The "Delete" action for a `main_route_table_association` consists of resetting
|
||||||
|
this original table as the Main Route Table for the VPC. You'll see this
|
||||||
|
additional Route Table in the AWS console; it must remain intact in order for
|
||||||
|
the `main_route_table_association` delete to work properly.
|
|
@ -53,6 +53,7 @@ The following attributes are exported:
|
||||||
* `enable_dns_support` - Whether or not the VPC has DNS support
|
* `enable_dns_support` - Whether or not the VPC has DNS support
|
||||||
* `enable_dns_hostnames` - Whether or not the VPC has DNS hostname support
|
* `enable_dns_hostnames` - Whether or not the VPC has DNS hostname support
|
||||||
* `main_route_table_id` - The ID of the main route table associated with
|
* `main_route_table_id` - The ID of the main route table associated with
|
||||||
this VPC.
|
this VPC. Note that you can change a VPC's main route table by using an
|
||||||
|
[`aws_main_route_table_association`](/docs/providers/aws/r/main_route_table_assoc.html).
|
||||||
* `default_network_acl_id` - The ID of the network ACL created by default on VPC creation
|
* `default_network_acl_id` - The ID of the network ACL created by default on VPC creation
|
||||||
* `default_security_group_id` - The ID of the security group created by default on VPC creation
|
* `default_security_group_id` - The ID of the security group created by default on VPC creation
|
||||||
|
|
|
@ -8,7 +8,10 @@ description: |-
|
||||||
|
|
||||||
# google\_compute\_address
|
# google\_compute\_address
|
||||||
|
|
||||||
Creates a static IP address resource for Google Compute Engine.
|
Creates a static IP address resource for Google Compute Engine. For more information see
|
||||||
|
[the official documentation](https://cloud.google.com/compute/docs/instances-and-network) and
|
||||||
|
[API](https://cloud.google.com/compute/docs/reference/latest/addresses).
|
||||||
|
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
|
@ -31,3 +34,4 @@ The following attributes are exported:
|
||||||
|
|
||||||
* `name` - The name of the resource.
|
* `name` - The name of the resource.
|
||||||
* `address` - The IP address that was allocated.
|
* `address` - The IP address that was allocated.
|
||||||
|
* `self_link` - The URI of the created resource.
|
||||||
|
|
|
@ -37,6 +37,8 @@ The following arguments are supported:
|
||||||
* `name` - (Required) A unique name for the resource, required by GCE.
|
* `name` - (Required) A unique name for the resource, required by GCE.
|
||||||
Changing this forces a new resource to be created.
|
Changing this forces a new resource to be created.
|
||||||
|
|
||||||
|
* `description` - (Optional) Textual description field.
|
||||||
|
|
||||||
* `network` - (Required) The name of the network to attach this firewall to.
|
* `network` - (Required) The name of the network to attach this firewall to.
|
||||||
|
|
||||||
* `allow` - (Required) Can be specified multiple times for each allow
|
* `allow` - (Required) Can be specified multiple times for each allow
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
layout: "google"
|
||||||
|
page_title: "Google: google_compute_forwarding_rule"
|
||||||
|
sidebar_current: "docs-google-resource-forwarding_rule"
|
||||||
|
description: |-
|
||||||
|
Manages a Target Pool within GCE.
|
||||||
|
---
|
||||||
|
|
||||||
|
# google\_compute\_forwarding\_rule
|
||||||
|
|
||||||
|
Manages a Forwarding Rule within GCE. This binds an ip and port range to a target pool. For more
|
||||||
|
information see [the official
|
||||||
|
documentation](https://cloud.google.com/compute/docs/load-balancing/network/forwarding-rules) and
|
||||||
|
[API](https://cloud.google.com/compute/docs/reference/latest/forwardingRules).
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "google_compute_forwarding_rule" "default" {
|
||||||
|
name = "test"
|
||||||
|
target = "${google_compute_target_pool.default.self_link}"
|
||||||
|
port_range = "80"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `description` - (Optional) Textual description field.
|
||||||
|
|
||||||
|
* `ip_address` - (Optional) The static IP. (if not set, an ephemeral IP is
|
||||||
|
used).
|
||||||
|
|
||||||
|
* `ip_protocol` - (Optional) The IP protocol to route, one of "TCP" "UDP" "AH" "ESP" or "SCTP". (default "TCP").
|
||||||
|
|
||||||
|
* `name` - (Required) A unique name for the resource, required by GCE. Changing
|
||||||
|
this forces a new resource to be created.
|
||||||
|
|
||||||
|
* `port_range` - (Optional) A range e.g. "1024-2048" or a single port "1024"
|
||||||
|
(defaults to all ports!).
|
||||||
|
|
||||||
|
* `target` - URL of target pool.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `self_link` - The URL of the created resource.
|
||||||
|
|
||||||
|
* `ip_address` - The IP address that was chosen (or specified).
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
layout: "google"
|
||||||
|
page_title: "Google: google_compute_http_health_check"
|
||||||
|
sidebar_current: "docs-google-resource-http_health_check"
|
||||||
|
description: |-
|
||||||
|
Manages an HTTP Health Check within GCE.
|
||||||
|
---
|
||||||
|
|
||||||
|
# google\_compute\_http\_health\_check
|
||||||
|
|
||||||
|
Manages an HTTP health check within GCE. This is used to monitor instances
|
||||||
|
behind load balancers. Timeouts or HTTP errors cause the instance to be
|
||||||
|
removed from the pool. For more information, see [the official
|
||||||
|
documentation](https://cloud.google.com/compute/docs/load-balancing/health-checks)
|
||||||
|
and
|
||||||
|
[API](https://cloud.google.com/compute/docs/reference/latest/httpHealthChecks).
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "google_compute_http_health_check" "default" {
|
||||||
|
name = "test"
|
||||||
|
request_path = "/health_check"
|
||||||
|
check_interval_sec = 1
|
||||||
|
timeout_sec = 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `check_interval_sec` - (Optional) How often to poll each instance (default 5).
|
||||||
|
|
||||||
|
* `description` - (Optional) Textual description field.
|
||||||
|
|
||||||
|
* `healthy_threshold` - (Optional) Consecutive successes required (default 2).
|
||||||
|
|
||||||
|
* `host` - (Optional) HTTP host header field (default instance's public ip).
|
||||||
|
|
||||||
|
* `name` - (Required) A unique name for the resource, required by GCE.
|
||||||
|
Changing this forces a new resource to be created.
|
||||||
|
|
||||||
|
* `port` - (Optional) TCP port to connect to (default 80).
|
||||||
|
|
||||||
|
* `request_path` - (Optional) URL path to query (default /).
|
||||||
|
|
||||||
|
* `timeout_sec` - (Optional) How long before declaring failure (default 5).
|
||||||
|
|
||||||
|
* `unhealthy_threshold` - (Optional) Consecutive failures required (default 2).
|
||||||
|
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `self_link` - The URL of the created resource.
|
|
@ -8,7 +8,11 @@ description: |-
|
||||||
|
|
||||||
# google\_compute\_instance
|
# google\_compute\_instance
|
||||||
|
|
||||||
Manages a VM instance resource within GCE.
|
Manages a VM instance resource within GCE. For more information see
|
||||||
|
[the official documentation](https://cloud.google.com/compute/docs/instances)
|
||||||
|
and
|
||||||
|
[API](https://cloud.google.com/compute/docs/reference/latest/instances).
|
||||||
|
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
|
@ -23,8 +27,11 @@ resource "google_compute_instance" "default" {
|
||||||
image = "debian-7-wheezy-v20140814"
|
image = "debian-7-wheezy-v20140814"
|
||||||
}
|
}
|
||||||
|
|
||||||
network {
|
network_interface {
|
||||||
source = "default"
|
network = "default"
|
||||||
|
access_config {
|
||||||
|
// Ephemeral IP
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
|
@ -60,7 +67,11 @@ The following arguments are supported:
|
||||||
* `metadata` - (Optional) Metadata key/value pairs to make available from
|
* `metadata` - (Optional) Metadata key/value pairs to make available from
|
||||||
within the instance.
|
within the instance.
|
||||||
|
|
||||||
* `network` - (Required) Networks to attach to the instance. This can be
|
* `network_interface` - (Required) Networks to attach to the instance. This can be
|
||||||
|
specified multiple times for multiple networks. Structure is documented
|
||||||
|
below.
|
||||||
|
|
||||||
|
* `network` - (DEPRECATED, Required) Networks to attach to the instance. This can be
|
||||||
specified multiple times for multiple networks. Structure is documented
|
specified multiple times for multiple networks. Structure is documented
|
||||||
below.
|
below.
|
||||||
|
|
||||||
|
@ -81,7 +92,22 @@ The `disk` block supports:
|
||||||
|
|
||||||
* `type` - (Optional) The GCE disk type.
|
* `type` - (Optional) The GCE disk type.
|
||||||
|
|
||||||
The `network` block supports:
|
The `network_interface` block supports:
|
||||||
|
|
||||||
|
* `network` - (Required) The name of the network to attach this interface to.
|
||||||
|
|
||||||
|
* `access_config` - (Optional) Access configurations, i.e. IPs via which this instance can be
|
||||||
|
accessed via the Internet. Omit to ensure that the instance is not accessible from the Internet
|
||||||
|
(this means that ssh provisioners will not work unless you are running Terraform can send traffic to
|
||||||
|
the instance's network (e.g. via tunnel or because it is running on another cloud instance on that
|
||||||
|
network). This block can be repeated multiple times. Structure documented below.
|
||||||
|
|
||||||
|
The `access_config` block supports:
|
||||||
|
|
||||||
|
* `nat_ip` - (Optional) The IP address that will be 1:1 mapped to the instance's network ip. If not
|
||||||
|
given, one will be generated.
|
||||||
|
|
||||||
|
(DEPRECATED) The `network` block supports:
|
||||||
|
|
||||||
* `source` - (Required) The name of the network to attach this interface to.
|
* `source` - (Required) The name of the network to attach this interface to.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
---
|
||||||
|
layout: "google"
|
||||||
|
page_title: "Google: google_compute_target_pool"
|
||||||
|
sidebar_current: "docs-google-resource-target_pool"
|
||||||
|
description: |-
|
||||||
|
Manages a Target Pool within GCE.
|
||||||
|
---
|
||||||
|
|
||||||
|
# google\_compute\_target\_pool
|
||||||
|
|
||||||
|
Manages a Target Pool within GCE. This is a collection of instances used as
|
||||||
|
target of a network load balancer (Forwarding Rule). For more information see
|
||||||
|
[the official
|
||||||
|
documentation](https://cloud.google.com/compute/docs/load-balancing/network/target-pools)
|
||||||
|
and [API](https://cloud.google.com/compute/docs/reference/latest/targetPools).
|
||||||
|
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "google_compute_target_pool" "default" {
|
||||||
|
name = "test"
|
||||||
|
instances = [ "us-central1-a/myinstance1", "us-central1-b/myinstance2" ]
|
||||||
|
health_checks = [ "${google_compute_http_health_check.default.name}" ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `backup_pool` - (Optional) URL to the backup target pool. Must also set
|
||||||
|
failover\_ratio.
|
||||||
|
|
||||||
|
* `description` - (Optional) Textual description field.
|
||||||
|
|
||||||
|
* `failover_ratio` - (Optional) Ratio (0 to 1) of failed nodes before using the
|
||||||
|
backup pool (which must also be set).
|
||||||
|
|
||||||
|
* `health_checks` - (Optional) List of zero or one healthcheck names.
|
||||||
|
|
||||||
|
* `instances` - (Optional) List of instances in the pool. They can be given as
|
||||||
|
URLs, or in the form of "zone/name". Note that the instances need not exist
|
||||||
|
at the time of target pool creation, so there is no need to use the Terraform
|
||||||
|
interpolators to create a dependency on the instances from the target pool.
|
||||||
|
|
||||||
|
* `name` - (Required) A unique name for the resource, required by GCE. Changing
|
||||||
|
this forces a new resource to be created.
|
||||||
|
|
||||||
|
* `session_affinity` - (Optional) How to distribute load. Options are "NONE" (no affinity). "CLIENT\_IP" (hash of the source/dest addresses / ports), and "CLIENT\_IP\_PROTO" also includes the protocol (default "NONE").
|
||||||
|
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `self_link` - The URL of the created resource.
|
||||||
|
|
|
@ -53,6 +53,10 @@
|
||||||
<a href="/docs/providers/aws/r/launch_config.html">aws_launch_configuration</a>
|
<a href="/docs/providers/aws/r/launch_config.html">aws_launch_configuration</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-aws-resource-main-route-table-assoc") %>>
|
||||||
|
<a href="/docs/providers/aws/r/main_route_table_assoc.html">aws_main_route_table_association</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-aws-resource-network-acl") %>>
|
<li<%= sidebar_current("docs-aws-resource-network-acl") %>>
|
||||||
<a href="/docs/providers/aws/r/network_acl.html">aws_network_acl</a>
|
<a href="/docs/providers/aws/r/network_acl.html">aws_network_acl</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in New Issue