provider/google: Support Import of 'google_compute_firewall'

This commit is contained in:
Noah Webb 2016-08-04 16:51:29 -04:00
parent 782b24833a
commit 09df0efd1c
5 changed files with 241 additions and 14 deletions

View File

@ -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,
},
},
})
}

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"log" "log"
"sort" "sort"
"strings"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
@ -18,6 +19,10 @@ func resourceComputeFirewall() *schema.Resource {
Read: resourceComputeFirewallRead, Read: resourceComputeFirewallRead,
Update: resourceComputeFirewallUpdate, Update: resourceComputeFirewallUpdate,
Delete: resourceComputeFirewallDelete, Delete: resourceComputeFirewallDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
SchemaVersion: 1,
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"name": &schema.Schema{ "name": &schema.Schema{
@ -43,10 +48,9 @@ func resourceComputeFirewall() *schema.Resource {
}, },
"ports": &schema.Schema{ "ports": &schema.Schema{
Type: schema.TypeSet, Type: schema.TypeList,
Optional: true, Optional: true,
Elem: &schema.Schema{Type: schema.TypeString}, Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
}, },
}, },
}, },
@ -62,6 +66,7 @@ func resourceComputeFirewall() *schema.Resource {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
Computed: true,
}, },
"self_link": &schema.Schema{ "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 // 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. // generate the same hash code no matter what is in the set.
if v, ok := m["ports"]; ok { if v, ok := m["ports"]; ok {
vs := v.(*schema.Set).List() s := convertStringArr(v.([]interface{}))
s := make([]string, len(vs))
for i, raw := range vs {
s[i] = raw.(string)
}
sort.Strings(s) sort.Strings(s)
for _, v := range s { for _, v := range s {
@ -146,6 +147,18 @@ func resourceComputeFirewallCreate(d *schema.ResourceData, meta interface{}) err
return resourceComputeFirewallRead(d, meta) 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 { func resourceComputeFirewallRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config) config := meta.(*Config)
@ -168,8 +181,16 @@ func resourceComputeFirewallRead(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("Error reading firewall: %s", err) return fmt.Errorf("Error reading firewall: %s", err)
} }
networkUrl := strings.Split(firewall.Network, "/")
d.Set("self_link", firewall.SelfLink) 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 return nil
} }
@ -250,10 +271,10 @@ func resourceFirewall(
m := v.(map[string]interface{}) m := v.(map[string]interface{})
var ports []string var ports []string
if v := m["ports"].(*schema.Set); v.Len() > 0 { if v := convertStringArr(m["ports"].([]interface{})); len(v) > 0 {
ports = make([]string, v.Len()) ports = make([]string, len(v))
for i, v := range v.List() { for i, v := range v {
ports[i] = v.(string) ports[i] = v
} }
} }

View File

@ -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.<hash>.ports.*" and we know it's not
// allow.<hash>.ports.# because we deleted it above, so it must be allow.<hash1>.ports.<hash2>
// 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 <hash> 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
}

View File

@ -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)
}
}

View File

@ -126,7 +126,7 @@ func testAccCheckComputeFirewallPorts(
func testAccComputeFirewall_basic(network, firewall string) string { func testAccComputeFirewall_basic(network, firewall string) string {
return fmt.Sprintf(` return fmt.Sprintf(`
resource "google_compute_network" "foobar" { resource "google_compute_network" "foobar" {
name = "firewall-test-%s" name = "%s"
ipv4_range = "10.0.0.0/16" 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 { func testAccComputeFirewall_update(network, firewall string) string {
return fmt.Sprintf(` return fmt.Sprintf(`
resource "google_compute_network" "foobar" { resource "google_compute_network" "foobar" {
name = "firewall-test-%s" name = "%s"
ipv4_range = "10.0.0.0/16" ipv4_range = "10.0.0.0/16"
} }