diff --git a/builtin/providers/google/provider.go b/builtin/providers/google/provider.go index 011f7f29c..e96b7892f 100644 --- a/builtin/providers/google/provider.go +++ b/builtin/providers/google/provider.go @@ -32,6 +32,7 @@ func Provider() *schema.Provider { ResourcesMap: map[string]*schema.Resource{ "google_compute_address": resourceComputeAddress(), "google_compute_disk": resourceComputeDisk(), + "google_compute_firewall": resourceComputeFirewall(), "google_compute_instance": resourceComputeInstance(), "google_compute_network": resourceComputeNetwork(), }, diff --git a/builtin/providers/google/resource_compute_firewall.go b/builtin/providers/google/resource_compute_firewall.go new file mode 100644 index 000000000..130b5fbc1 --- /dev/null +++ b/builtin/providers/google/resource_compute_firewall.go @@ -0,0 +1,219 @@ +package google + +import ( + "bytes" + "fmt" + "sort" + "time" + + "code.google.com/p/google-api-go-client/compute/v1" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceComputeFirewall() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeFirewallCreate, + Read: resourceComputeFirewallRead, + Delete: resourceComputeFirewallDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "network": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "allow": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "ports": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + }, + }, + Set: resourceComputeFirewallAllowHash, + }, + + "source_ranges": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + + "source_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 resourceComputeFirewallAllowHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) + + // We need to make sure to sort the strings below so that we always + // generate the same hash code no matter what is in the set. + if v, ok := m["ports"]; ok { + vs := v.(*schema.Set).List() + s := make([]string, len(vs)) + for i, raw := range vs { + s[i] = raw.(string) + } + sort.Strings(s) + + for _, v := range s { + buf.WriteString(fmt.Sprintf("%s-", v)) + } + } + + return hashcode.String(buf.String()) +} + +func resourceComputeFirewallCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + // Look up the network to attach the firewall 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) + } + + // Build up the list of allowed entries + var allowed []*compute.FirewallAllowed + if v := d.Get("allow").(*schema.Set); v.Len() > 0 { + allowed = make([]*compute.FirewallAllowed, 0, v.Len()) + for _, v := range v.List() { + m := v.(map[string]interface{}) + + var ports []string + if v := m["ports"].(*schema.Set); v.Len() > 0 { + ports = make([]string, v.Len()) + for i, v := range v.List() { + ports[i] = v.(string) + } + } + + allowed = append(allowed, &compute.FirewallAllowed{ + IPProtocol: m["protocol"].(string), + Ports: ports, + }) + } + } + + // Build up the list of sources + var sourceRanges, sourceTags []string + if v := d.Get("source_ranges").(*schema.Set); v.Len() > 0 { + sourceRanges = make([]string, v.Len()) + for i, v := range v.List() { + sourceRanges[i] = v.(string) + } + } + if v := d.Get("source_tags").(*schema.Set); v.Len() > 0 { + sourceTags = make([]string, v.Len()) + for i, v := range v.List() { + sourceTags[i] = v.(string) + } + } + + // Build the firewall parameter + firewall := &compute.Firewall{ + Name: d.Get("name").(string), + Network: network.SelfLink, + Allowed: allowed, + SourceRanges: sourceRanges, + SourceTags: sourceTags, + } + op, err := config.clientCompute.Firewalls.Insert( + config.Project, firewall).Do() + if err != nil { + return fmt.Errorf("Error creating firewall: %s", err) + } + + // It probably maybe worked, so store the ID now + d.SetId(firewall.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 + if _, err := state.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for firewall to create: %s", err) + } + + return resourceComputeFirewallRead(d, meta) +} + +func resourceComputeFirewallRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + _, err := config.clientCompute.Firewalls.Get( + config.Project, d.Id()).Do() + if err != nil { + return fmt.Errorf("Error reading firewall: %s", err) + } + + return nil +} + +func resourceComputeFirewallDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + // Delete the firewall + op, err := config.clientCompute.Firewalls.Delete( + config.Project, d.Id()).Do() + if err != nil { + return fmt.Errorf("Error deleting firewall: %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 firewall to delete: %s", err) + } + + d.SetId("") + return nil +} diff --git a/builtin/providers/google/resource_compute_firewall_test.go b/builtin/providers/google/resource_compute_firewall_test.go new file mode 100644 index 000000000..467867f61 --- /dev/null +++ b/builtin/providers/google/resource_compute_firewall_test.go @@ -0,0 +1,92 @@ +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 TestAccComputeFirewall_basic(t *testing.T) { + var firewall compute.Firewall + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeFirewallDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeFirewall_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeFirewallExists( + "google_compute_firewall.foobar", &firewall), + ), + }, + }, + }) +} + +func testAccCheckComputeFirewallDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.Resources { + if rs.Type != "google_compute_firewall" { + continue + } + + _, err := config.clientCompute.Firewalls.Get( + config.Project, rs.ID).Do() + if err == nil { + return fmt.Errorf("Firewall still exists") + } + } + + return nil +} + +func testAccCheckComputeFirewallExists(n string, firewall *compute.Firewall) 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.Firewalls.Get( + config.Project, rs.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.ID { + return fmt.Errorf("Firewall not found") + } + + *firewall = *found + + return nil + } +} + +const testAccComputeFirewall_basic = ` +resource "google_compute_network" "foobar" { + name = "terraform-test" + ipv4_range = "10.0.0.0/16" +} + +resource "google_compute_firewall" "foobar" { + name = "terraform-test" + network = "${google_compute_network.foobar.name}" + source_tags = ["foo"] + + allow { + protocol = "icmp" + } +}` diff --git a/builtin/providers/google/resource_compute_network.go b/builtin/providers/google/resource_compute_network.go index 829bd9004..99c2a12ed 100644 --- a/builtin/providers/google/resource_compute_network.go +++ b/builtin/providers/google/resource_compute_network.go @@ -77,7 +77,7 @@ func resourceComputeNetworkRead(d *schema.ResourceData, meta interface{}) error network, err := config.clientCompute.Networks.Get( config.Project, d.Id()).Do() if err != nil { - return fmt.Errorf("Error reading address: %s", err) + return fmt.Errorf("Error reading network: %s", err) } d.Set("gateway_ipv4", network.GatewayIPv4)