mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-09-06 17:16:44 +00:00
![Vincent Roseberry](/assets/img/avatar_default.png)
* Upgrade compute v1 client * Upgrade gensupport * Use v1 API now that all beta features are in GA for `google_compute_firewall`
498 lines
13 KiB
Go
498 lines
13 KiB
Go
package google
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/hashicorp/terraform/helper/hashcode"
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
"github.com/hashicorp/terraform/helper/validation"
|
|
|
|
computeBeta "google.golang.org/api/compute/v0.beta"
|
|
"google.golang.org/api/compute/v1"
|
|
)
|
|
|
|
var FirewallBaseApiVersion = v1
|
|
var FirewallVersionedFeatures = []Feature{}
|
|
|
|
func resourceComputeFirewall() *schema.Resource {
|
|
return &schema.Resource{
|
|
Create: resourceComputeFirewallCreate,
|
|
Read: resourceComputeFirewallRead,
|
|
Update: resourceComputeFirewallUpdate,
|
|
Delete: resourceComputeFirewallDelete,
|
|
Importer: &schema.ResourceImporter{
|
|
State: schema.ImportStatePassthrough,
|
|
},
|
|
SchemaVersion: 1,
|
|
MigrateState: resourceComputeFirewallMigrateState,
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
"name": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"network": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
DiffSuppressFunc: compareSelfLinkOrResourceName,
|
|
},
|
|
|
|
"priority": {
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Default: 1000,
|
|
ValidateFunc: validation.IntBetween(0, 65535),
|
|
},
|
|
|
|
"allow": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ConflictsWith: []string{"deny"},
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"protocol": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
|
|
"ports": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
},
|
|
},
|
|
Set: resourceComputeFirewallRuleHash,
|
|
},
|
|
|
|
"deny": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ConflictsWith: []string{"allow"},
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"protocol": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"ports": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
ForceNew: true,
|
|
},
|
|
},
|
|
},
|
|
Set: resourceComputeFirewallRuleHash,
|
|
|
|
// Unlike allow, deny can't be updated upstream
|
|
ForceNew: true,
|
|
},
|
|
|
|
"description": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
},
|
|
|
|
"direction": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ValidateFunc: validation.StringInSlice([]string{"INGRESS", "EGRESS"}, false),
|
|
ForceNew: true,
|
|
},
|
|
|
|
"project": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Computed: true,
|
|
},
|
|
|
|
"self_link": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
|
|
"source_ranges": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Computed: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Set: schema.HashString,
|
|
},
|
|
|
|
"source_tags": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Set: schema.HashString,
|
|
},
|
|
|
|
"destination_ranges": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Computed: true,
|
|
ConflictsWith: []string{"source_ranges", "source_tags"},
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Set: schema.HashString,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"target_tags": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Set: schema.HashString,
|
|
},
|
|
|
|
"source_service_accounts": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
ForceNew: true,
|
|
ConflictsWith: []string{"source_tags", "target_tags"},
|
|
},
|
|
|
|
"target_service_accounts": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
ForceNew: true,
|
|
ConflictsWith: []string{"source_tags", "target_tags"},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func resourceComputeFirewallRuleHash(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 {
|
|
s := convertStringArr(v.([]interface{}))
|
|
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 {
|
|
computeApiVersion := getComputeApiVersion(d, FirewallBaseApiVersion, FirewallVersionedFeatures)
|
|
config := meta.(*Config)
|
|
|
|
project, err := getProject(d, config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
firewall, err := resourceFirewall(d, meta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var op interface{}
|
|
switch computeApiVersion {
|
|
case v1:
|
|
firewallV1 := &compute.Firewall{}
|
|
err = Convert(firewall, firewallV1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
op, err = config.clientCompute.Firewalls.Insert(project, firewallV1).Do()
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating firewall: %s", err)
|
|
}
|
|
case v0beta:
|
|
firewallV0Beta := &computeBeta.Firewall{}
|
|
err = Convert(firewall, firewallV0Beta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
op, err = config.clientComputeBeta.Firewalls.Insert(project, firewallV0Beta).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)
|
|
|
|
err = computeSharedOperationWait(config.clientCompute, op, project, "Creating Firewall")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return resourceComputeFirewallRead(d, meta)
|
|
}
|
|
|
|
func flattenFirewallAllowed(allowed []*computeBeta.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 flattenFirewallDenied(denied []*computeBeta.FirewallDenied) []map[string]interface{} {
|
|
result := make([]map[string]interface{}, 0, len(denied))
|
|
for _, deny := range denied {
|
|
denyMap := make(map[string]interface{})
|
|
denyMap["protocol"] = deny.IPProtocol
|
|
denyMap["ports"] = deny.Ports
|
|
|
|
result = append(result, denyMap)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func resourceComputeFirewallRead(d *schema.ResourceData, meta interface{}) error {
|
|
computeApiVersion := getComputeApiVersion(d, FirewallBaseApiVersion, FirewallVersionedFeatures)
|
|
config := meta.(*Config)
|
|
|
|
project, err := getProject(d, config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
firewall := &computeBeta.Firewall{}
|
|
switch computeApiVersion {
|
|
case v1:
|
|
firewallV1, err := config.clientCompute.Firewalls.Get(project, d.Id()).Do()
|
|
if err != nil {
|
|
return handleNotFoundError(err, d, fmt.Sprintf("Firewall %q", d.Get("name").(string)))
|
|
}
|
|
|
|
err = Convert(firewallV1, firewall)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case v0beta:
|
|
firewallV0Beta, err := config.clientComputeBeta.Firewalls.Get(project, d.Id()).Do()
|
|
if err != nil {
|
|
return handleNotFoundError(err, d, fmt.Sprintf("Firewall %q", d.Get("name").(string)))
|
|
}
|
|
|
|
err = Convert(firewallV0Beta, firewall)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
d.Set("self_link", ConvertSelfLinkToV1(firewall.SelfLink))
|
|
d.Set("name", firewall.Name)
|
|
d.Set("network", ConvertSelfLinkToV1(firewall.Network))
|
|
|
|
// Unlike most other Beta properties, direction will always have a value even when
|
|
// a zero is sent by the client. We'll never revert back to v1 without conditionally reading it.
|
|
// This if statement blocks Beta import for this resource.
|
|
if _, ok := d.GetOk("direction"); ok {
|
|
d.Set("direction", firewall.Direction)
|
|
}
|
|
|
|
d.Set("description", firewall.Description)
|
|
d.Set("project", project)
|
|
d.Set("source_ranges", firewall.SourceRanges)
|
|
d.Set("source_tags", firewall.SourceTags)
|
|
d.Set("destination_ranges", firewall.DestinationRanges)
|
|
d.Set("target_tags", firewall.TargetTags)
|
|
d.Set("allow", flattenFirewallAllowed(firewall.Allowed))
|
|
d.Set("deny", flattenFirewallDenied(firewall.Denied))
|
|
d.Set("priority", int(firewall.Priority))
|
|
d.Set("source_service_accounts", firewall.SourceServiceAccounts)
|
|
d.Set("target_service_accounts", firewall.TargetServiceAccounts)
|
|
return nil
|
|
}
|
|
|
|
func resourceComputeFirewallUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
computeApiVersion := getComputeApiVersionUpdate(d, FirewallBaseApiVersion, FirewallVersionedFeatures, []Feature{})
|
|
config := meta.(*Config)
|
|
|
|
project, err := getProject(d, config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.Partial(true)
|
|
|
|
firewall, err := resourceFirewall(d, meta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var op interface{}
|
|
switch computeApiVersion {
|
|
case v1:
|
|
firewallV1 := &compute.Firewall{}
|
|
err = Convert(firewall, firewallV1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
op, err = config.clientCompute.Firewalls.Update(project, d.Id(), firewallV1).Do()
|
|
if err != nil {
|
|
return fmt.Errorf("Error updating firewall: %s", err)
|
|
}
|
|
case v0beta:
|
|
firewallV0Beta := &computeBeta.Firewall{}
|
|
err = Convert(firewall, firewallV0Beta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
op, err = config.clientComputeBeta.Firewalls.Update(project, d.Id(), firewallV0Beta).Do()
|
|
if err != nil {
|
|
return fmt.Errorf("Error updating firewall: %s", err)
|
|
}
|
|
}
|
|
|
|
err = computeSharedOperationWait(config.clientCompute, op, project, "Updating Firewall")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.Partial(false)
|
|
|
|
return resourceComputeFirewallRead(d, meta)
|
|
}
|
|
|
|
func resourceComputeFirewallDelete(d *schema.ResourceData, meta interface{}) error {
|
|
computeApiVersion := getComputeApiVersion(d, FirewallBaseApiVersion, FirewallVersionedFeatures)
|
|
config := meta.(*Config)
|
|
|
|
project, err := getProject(d, config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Delete the firewall
|
|
var op interface{}
|
|
switch computeApiVersion {
|
|
case v1:
|
|
op, err = config.clientCompute.Firewalls.Delete(project, d.Id()).Do()
|
|
if err != nil {
|
|
return fmt.Errorf("Error deleting firewall: %s", err)
|
|
}
|
|
case v0beta:
|
|
op, err = config.clientComputeBeta.Firewalls.Delete(project, d.Id()).Do()
|
|
if err != nil {
|
|
return fmt.Errorf("Error deleting firewall: %s", err)
|
|
}
|
|
}
|
|
|
|
err = computeSharedOperationWait(config.clientCompute, op, project, "Deleting Firewall")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.SetId("")
|
|
return nil
|
|
}
|
|
|
|
func resourceFirewall(d *schema.ResourceData, meta interface{}) (*computeBeta.Firewall, error) {
|
|
config := meta.(*Config)
|
|
|
|
network, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Build up the list of allowed entries
|
|
var allowed []*computeBeta.FirewallAllowed
|
|
if v := d.Get("allow").(*schema.Set); v.Len() > 0 {
|
|
allowed = make([]*computeBeta.FirewallAllowed, 0, v.Len())
|
|
for _, v := range v.List() {
|
|
m := v.(map[string]interface{})
|
|
|
|
allowed = append(allowed, &computeBeta.FirewallAllowed{
|
|
IPProtocol: m["protocol"].(string),
|
|
Ports: convertStringArr(m["ports"].([]interface{})),
|
|
})
|
|
}
|
|
}
|
|
|
|
// Build up the list of denied entries
|
|
var denied []*computeBeta.FirewallDenied
|
|
if v := d.Get("deny").(*schema.Set); v.Len() > 0 {
|
|
denied = make([]*computeBeta.FirewallDenied, 0, v.Len())
|
|
for _, v := range v.List() {
|
|
m := v.(map[string]interface{})
|
|
|
|
denied = append(denied, &computeBeta.FirewallDenied{
|
|
IPProtocol: m["protocol"].(string),
|
|
Ports: convertStringArr(m["ports"].([]interface{})),
|
|
})
|
|
}
|
|
}
|
|
|
|
// 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 up the list of destinations
|
|
var destinationRanges []string
|
|
if v := d.Get("destination_ranges").(*schema.Set); v.Len() > 0 {
|
|
destinationRanges = make([]string, v.Len())
|
|
for i, v := range v.List() {
|
|
destinationRanges[i] = v.(string)
|
|
}
|
|
}
|
|
|
|
// Build up the list of targets
|
|
var targetTags []string
|
|
if v := d.Get("target_tags").(*schema.Set); v.Len() > 0 {
|
|
targetTags = make([]string, v.Len())
|
|
for i, v := range v.List() {
|
|
targetTags[i] = v.(string)
|
|
}
|
|
}
|
|
|
|
// Build the firewall parameter
|
|
return &computeBeta.Firewall{
|
|
Name: d.Get("name").(string),
|
|
Description: d.Get("description").(string),
|
|
Direction: d.Get("direction").(string),
|
|
Network: network.RelativeLink(),
|
|
Allowed: allowed,
|
|
Denied: denied,
|
|
SourceRanges: sourceRanges,
|
|
SourceTags: sourceTags,
|
|
DestinationRanges: destinationRanges,
|
|
TargetTags: targetTags,
|
|
Priority: int64(d.Get("priority").(int)),
|
|
SourceServiceAccounts: convertStringSet(d.Get("source_service_accounts").(*schema.Set)),
|
|
TargetServiceAccounts: convertStringSet(d.Get("target_service_accounts").(*schema.Set)),
|
|
}, nil
|
|
}
|