diff --git a/builtin/providers/google/import_compute_firewall_test.go b/builtin/providers/google/import_compute_firewall_test.go new file mode 100644 index 000000000..362391e1e --- /dev/null +++ b/builtin/providers/google/import_compute_firewall_test.go @@ -0,0 +1,32 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccComputeFirewall_importBasic(t *testing.T) { + resourceName := "google_compute_firewall.foobar" + networkName := fmt.Sprintf("firewall-test-%s", acctest.RandString(10)) + firewallName := fmt.Sprintf("firewall-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeFirewallDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeFirewall_basic(networkName, firewallName), + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/google/resource_compute_firewall.go b/builtin/providers/google/resource_compute_firewall.go index d5a8ef210..b2da01c76 100644 --- a/builtin/providers/google/resource_compute_firewall.go +++ b/builtin/providers/google/resource_compute_firewall.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "sort" + "strings" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" @@ -18,6 +19,10 @@ func resourceComputeFirewall() *schema.Resource { Read: resourceComputeFirewallRead, Update: resourceComputeFirewallUpdate, Delete: resourceComputeFirewallDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + SchemaVersion: 1, Schema: map[string]*schema.Schema{ "name": &schema.Schema{ @@ -43,10 +48,9 @@ func resourceComputeFirewall() *schema.Resource { }, "ports": &schema.Schema{ - Type: schema.TypeSet, + Type: schema.TypeList, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, }, }, }, @@ -62,6 +66,7 @@ func resourceComputeFirewall() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, + Computed: true, }, "self_link": &schema.Schema{ @@ -101,11 +106,7 @@ func resourceComputeFirewallAllowHash(v interface{}) int { // 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) - } + s := convertStringArr(v.([]interface{})) sort.Strings(s) for _, v := range s { @@ -146,6 +147,18 @@ func resourceComputeFirewallCreate(d *schema.ResourceData, meta interface{}) err return resourceComputeFirewallRead(d, meta) } +func flattenAllowed(allowed []*compute.FirewallAllowed) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(allowed)) + for _, allow := range allowed { + allowMap := make(map[string]interface{}) + allowMap["protocol"] = allow.IPProtocol + allowMap["ports"] = allow.Ports + + result = append(result, allowMap) + } + return result +} + func resourceComputeFirewallRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) @@ -168,8 +181,16 @@ func resourceComputeFirewallRead(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Error reading firewall: %s", err) } + networkUrl := strings.Split(firewall.Network, "/") d.Set("self_link", firewall.SelfLink) - + d.Set("name", firewall.Name) + d.Set("network", networkUrl[len(networkUrl)-1]) + d.Set("description", firewall.Description) + d.Set("project", project) + d.Set("source_ranges", firewall.SourceRanges) + d.Set("source_tags", firewall.SourceTags) + d.Set("target_tags", firewall.TargetTags) + d.Set("allow", flattenAllowed(firewall.Allowed)) return nil } @@ -250,10 +271,10 @@ func resourceFirewall( 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) + if v := convertStringArr(m["ports"].([]interface{})); len(v) > 0 { + ports = make([]string, len(v)) + for i, v := range v { + ports[i] = v } } diff --git a/builtin/providers/google/resource_compute_firewall_migrate.go b/builtin/providers/google/resource_compute_firewall_migrate.go new file mode 100644 index 000000000..3252e650e --- /dev/null +++ b/builtin/providers/google/resource_compute_firewall_migrate.go @@ -0,0 +1,93 @@ +package google + +import ( + "fmt" + "log" + "sort" + "strconv" + "strings" + + "github.com/hashicorp/terraform/terraform" +) + +func resourceComputeFirewallMigrateState( + v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { + if is.Empty() { + log.Println("[DEBUG] Empty FirewallState; nothing to migrate.") + return is, nil + } + + switch v { + case 0: + log.Println("[INFO] Found Compute Firewall State v0; migrating to v1") + is, err := migrateFirewallStateV0toV1(is) + if err != nil { + return is, err + } + return is, nil + default: + return is, fmt.Errorf("Unexpected schema version: %d", v) + } +} + +func migrateFirewallStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) { + log.Printf("[DEBUG] Attributes before migration: %#v", is.Attributes) + idx := 0 + portCount := 0 + newPorts := make(map[string]string) + keys := make([]string, len(is.Attributes)) + for k, _ := range is.Attributes { + keys[idx] = k + idx++ + + } + sort.Strings(keys) + for _, k := range keys { + if !strings.HasPrefix(k, "allow.") { + continue + } + + if k == "allow.#" { + continue + } + + if strings.HasSuffix(k, ".ports.#") { + continue + } + + if strings.HasSuffix(k, ".protocol") { + continue + } + + // We have a key that looks like "allow..ports.*" and we know it's not + // allow..ports.# because we deleted it above, so it must be allow..ports. + // from the Set of Ports. Just need to convert it to a list by + // replacing second hash with sequential numbers. + kParts := strings.Split(k, ".") + + // Sanity check: all four parts should be there and should be a number + badFormat := false + if len(kParts) != 4 { + badFormat = true + } else if _, err := strconv.Atoi(kParts[1]); err != nil { + badFormat = true + } + + if badFormat { + return is, fmt.Errorf( + "migration error: found port key in unexpected format: %s", k) + } + allowHash, _ := strconv.Atoi(kParts[1]) + newK := fmt.Sprintf("allow.%d.ports.%d", allowHash, portCount) + portCount++ + newPorts[newK] = is.Attributes[k] + delete(is.Attributes, k) + } + + for k, v := range newPorts { + is.Attributes[k] = v + } + + log.Printf("[DEBUG] Attributes after migration: %#v", is.Attributes) + return is, nil +} diff --git a/builtin/providers/google/resource_compute_firewall_migrate_test.go b/builtin/providers/google/resource_compute_firewall_migrate_test.go new file mode 100644 index 000000000..e28d607f3 --- /dev/null +++ b/builtin/providers/google/resource_compute_firewall_migrate_test.go @@ -0,0 +1,81 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestComputeFirewallMigrateState(t *testing.T) { + cases := map[string]struct { + StateVersion int + Attributes map[string]string + Expected map[string]string + Meta interface{} + }{ + "change scope from list to set": { + StateVersion: 0, + Attributes: map[string]string{ + "allow.#": "1", + "allow.0.protocol": "udp", + "allow.0.ports.#": "4", + "allow.0.ports.1693978638": "8080", + "allow.0.ports.172152165": "8081", + "allow.0.ports.299962681": "7072", + "allow.0.ports.3435931483": "4044", + }, + Expected: map[string]string{ + "allow.#": "1", + "allow.0.protocol": "udp", + "allow.0.ports.#": "4", + "allow.0.ports.0": "8080", + "allow.0.ports.1": "8081", + "allow.0.ports.2": "7072", + "allow.0.ports.3": "4044", + }, + }, + } + for tn, tc := range cases { + is := &terraform.InstanceState{ + ID: "i-abc123", + Attributes: tc.Attributes, + } + is, err := resourceComputeFirewallMigrateState( + tc.StateVersion, is, tc.Meta) + + if err != nil { + t.Fatalf("bad: %s, err: %#v", tn, err) + } + + for k, v := range tc.Expected { + if is.Attributes[k] != v { + t.Fatalf( + "bad: %s\n\n expected: %#v -> %#v\n got: %#v -> %#v\n in: %#v", + tn, k, v, k, is.Attributes[k], is.Attributes) + } + } + } +} + +func TestComputeFirewallMigrateState_empty(t *testing.T) { + var is *terraform.InstanceState + var meta interface{} + + // should handle nil + is, err := resourceComputeFirewallMigrateState(0, is, meta) + + if err != nil { + t.Fatalf("err: %#v", err) + } + if is != nil { + t.Fatalf("expected nil instancestate, got: %#v", is) + } + + // should handle non-nil but empty + is = &terraform.InstanceState{} + is, err = resourceComputeFirewallMigrateState(0, is, meta) + + if err != nil { + t.Fatalf("err: %#v", err) + } +} diff --git a/builtin/providers/google/resource_compute_firewall_test.go b/builtin/providers/google/resource_compute_firewall_test.go index 3fa6b305b..8b077314b 100644 --- a/builtin/providers/google/resource_compute_firewall_test.go +++ b/builtin/providers/google/resource_compute_firewall_test.go @@ -126,7 +126,7 @@ func testAccCheckComputeFirewallPorts( func testAccComputeFirewall_basic(network, firewall string) string { return fmt.Sprintf(` resource "google_compute_network" "foobar" { - name = "firewall-test-%s" + name = "%s" ipv4_range = "10.0.0.0/16" } @@ -145,7 +145,7 @@ func testAccComputeFirewall_basic(network, firewall string) string { func testAccComputeFirewall_update(network, firewall string) string { return fmt.Sprintf(` resource "google_compute_network" "foobar" { - name = "firewall-test-%s" + name = "%s" ipv4_range = "10.0.0.0/16" }