diff --git a/builtin/providers/google/operation.go b/builtin/providers/google/operation.go index 59c6839ab..32bf79a5e 100644 --- a/builtin/providers/google/operation.go +++ b/builtin/providers/google/operation.go @@ -1,6 +1,7 @@ package google import ( + "bytes" "fmt" "code.google.com/p/google-api-go-client/compute/v1" @@ -62,3 +63,17 @@ func (w *OperationWaiter) Conf() *resource.StateChangeConf { Refresh: w.RefreshFunc(), } } + +// OperationError wraps compute.OperationError and implements the +// error interface so it can be returned. +type OperationError compute.OperationError + +func (e OperationError) Error() string { + var buf bytes.Buffer + + for _, err := range e.Errors { + buf.WriteString(err.Message + "\n") + } + + return buf.String() +} diff --git a/builtin/providers/google/provider.go b/builtin/providers/google/provider.go index e96b7892f..5fbba686a 100644 --- a/builtin/providers/google/provider.go +++ b/builtin/providers/google/provider.go @@ -35,6 +35,7 @@ func Provider() *schema.Provider { "google_compute_firewall": resourceComputeFirewall(), "google_compute_instance": resourceComputeInstance(), "google_compute_network": resourceComputeNetwork(), + "google_compute_route": resourceComputeRoute(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/google/resource_compute_route.go b/builtin/providers/google/resource_compute_route.go new file mode 100644 index 000000000..2c155cbce --- /dev/null +++ b/builtin/providers/google/resource_compute_route.go @@ -0,0 +1,220 @@ +package google + +import ( + "fmt" + "log" + "time" + + "code.google.com/p/google-api-go-client/compute/v1" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceComputeRoute() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeRouteCreate, + Read: resourceComputeRouteRead, + Delete: resourceComputeRouteDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "dest_range": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "network": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "next_hop_ip": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "next_hop_instance": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "next_hop_instance_zone": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "next_hop_gateway": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "next_hop_network": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "priority": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "tags": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + }, + } +} + +func resourceComputeRouteCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + // Look up the network to attach the route to + network, err := config.clientCompute.Networks.Get( + config.Project, d.Get("network").(string)).Do() + if err != nil { + return fmt.Errorf("Error reading network: %s", err) + } + + // Next hop data + var nextHopInstance, nextHopIp, nextHopNetwork, nextHopGateway string + if v, ok := d.GetOk("next_hop_ip"); ok { + nextHopIp = v.(string) + } + if v, ok := d.GetOk("next_hop_gateway"); ok { + nextHopGateway = v.(string) + } + if v, ok := d.GetOk("next_hop_instance"); ok { + nextInstance, err := config.clientCompute.Instances.Get( + config.Project, + d.Get("next_hop_instance_zone").(string), + v.(string)).Do() + if err != nil { + return fmt.Errorf("Error reading instance: %s", err) + } + + nextHopInstance = nextInstance.SelfLink + } + if v, ok := d.GetOk("next_hop_network"); ok { + nextNetwork, err := config.clientCompute.Networks.Get( + config.Project, v.(string)).Do() + if err != nil { + return fmt.Errorf("Error reading network: %s", err) + } + + nextHopNetwork = nextNetwork.SelfLink + } + + // Tags + var tags []string + if v := d.Get("tags").(*schema.Set); v.Len() > 0 { + tags = make([]string, v.Len()) + for i, v := range v.List() { + tags[i] = v.(string) + } + } + + // Build the route parameter + route := &compute.Route{ + Name: d.Get("name").(string), + DestRange: d.Get("dest_range").(string), + Network: network.SelfLink, + NextHopInstance: nextHopInstance, + NextHopIp: nextHopIp, + NextHopNetwork: nextHopNetwork, + NextHopGateway: nextHopGateway, + Priority: int64(d.Get("priority").(int)), + Tags: tags, + } + log.Printf("[DEBUG] Route insert request: %#v", route) + op, err := config.clientCompute.Routes.Insert( + config.Project, route).Do() + if err != nil { + return fmt.Errorf("Error creating route: %s", err) + } + + // It probably maybe worked, so store the ID now + d.SetId(route.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 route 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 resourceComputeRouteRead(d, meta) +} + +func resourceComputeRouteRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + _, err := config.clientCompute.Routes.Get( + config.Project, d.Id()).Do() + if err != nil { + return fmt.Errorf("Error reading route: %#v", err) + } + + return nil +} + +func resourceComputeRouteDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + // Delete the route + op, err := config.clientCompute.Routes.Delete( + config.Project, d.Id()).Do() + if err != nil { + return fmt.Errorf("Error deleting route: %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 + if _, err := state.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for route to delete: %s", err) + } + + d.SetId("") + return nil +} diff --git a/builtin/providers/google/resource_compute_route_test.go b/builtin/providers/google/resource_compute_route_test.go new file mode 100644 index 000000000..eb0721d96 --- /dev/null +++ b/builtin/providers/google/resource_compute_route_test.go @@ -0,0 +1,90 @@ +package google + +import ( + "fmt" + "testing" + + "code.google.com/p/google-api-go-client/compute/v1" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeRoute_basic(t *testing.T) { + var route compute.Route + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRouteDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRoute_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRouteExists( + "google_compute_route.foobar", &route), + ), + }, + }, + }) +} + +func testAccCheckComputeRouteDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.Resources { + if rs.Type != "google_compute_route" { + continue + } + + _, err := config.clientCompute.Routes.Get( + config.Project, rs.ID).Do() + if err == nil { + return fmt.Errorf("Route still exists") + } + } + + return nil +} + +func testAccCheckComputeRouteExists(n string, route *compute.Route) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + + found, err := config.clientCompute.Routes.Get( + config.Project, rs.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.ID { + return fmt.Errorf("Route not found") + } + + *route = *found + + return nil + } +} + +const testAccComputeRoute_basic = ` +resource "google_compute_network" "foobar" { + name = "terraform-test" + ipv4_range = "10.0.0.0/16" +} + +resource "google_compute_route" "foobar" { + name = "terraform-test" + dest_range = "15.0.0.0/24" + network = "${google_compute_network.foobar.name}" + next_hop_ip = "10.0.1.5" + priority = 100 +}`