Merge branch 'keinohguchi-master'

This commit is contained in:
stack72 2017-06-16 22:24:40 +03:00
commit 60aae80567
5 changed files with 1045 additions and 0 deletions

View File

@ -25,6 +25,7 @@ func Provider() terraform.ResourceProvider {
"digitalocean_certificate": resourceDigitalOceanCertificate(),
"digitalocean_domain": resourceDigitalOceanDomain(),
"digitalocean_droplet": resourceDigitalOceanDroplet(),
"digitalocean_firewall": resourceDigitalOceanFirewall(),
"digitalocean_floating_ip": resourceDigitalOceanFloatingIp(),
"digitalocean_loadbalancer": resourceDigitalOceanLoadbalancer(),
"digitalocean_record": resourceDigitalOceanRecord(),

View File

@ -0,0 +1,578 @@
package digitalocean
import (
"bytes"
"context"
"fmt"
"log"
"strconv"
"strings"
"github.com/digitalocean/godo"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceDigitalOceanFirewall() *schema.Resource {
return &schema.Resource{
Create: resourceDigitalOceanFirewallCreate,
Read: resourceDigitalOceanFirewallRead,
Update: resourceDigitalOceanFirewallUpdate,
Delete: resourceDigitalOceanFirewallDelete,
Exists: resourceDigitalOceanFirewallExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"status": {
Type: schema.TypeString,
Computed: true,
},
"created_at": {
Type: schema.TypeString,
Computed: true,
},
"pending_changes": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"droplet_id": {
Type: schema.TypeInt,
Optional: true,
},
"removing": {
Type: schema.TypeBool,
Optional: true,
},
"status": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
"name": {
Type: schema.TypeString,
Required: true,
},
"droplet_ids": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"tags": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"inbound_rule": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"protocol": {
Type: schema.TypeString,
Optional: true,
},
"port_range": {
Type: schema.TypeString,
Optional: true,
},
"source_addresses": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"source_tags": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"source_droplet_ids": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeInt},
Optional: true,
},
"source_load_balancer_uids": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
},
},
},
"outbound_rule": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"protocol": {
Type: schema.TypeString,
Optional: true,
},
"port_range": {
Type: schema.TypeString,
Optional: true,
},
"destination_addresses": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"destination_tags": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"destination_droplet_ids": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeInt},
Optional: true,
},
"destination_load_balancer_uids": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
},
},
},
},
}
}
func resourceDigitalOceanFirewallCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)
opts, err := firewallRequest(d, client)
if err != nil {
return fmt.Errorf("Error in firewall request: %s", err)
}
log.Printf("[DEBUG] Firewall create configuration: %#v", opts)
firewall, _, err := client.Firewalls.Create(context.Background(), opts)
if err != nil {
return fmt.Errorf("Error creating firewall: %s", err)
}
// Assign the firewall id
d.SetId(firewall.ID)
log.Printf("[INFO] Firewall ID: %s", d.Id())
return resourceDigitalOceanFirewallRead(d, meta)
}
func resourceDigitalOceanFirewallRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)
// Retrieve the firewall properties for updating the state
firewall, resp, err := client.Firewalls.Get(context.Background(), d.Id())
if err != nil {
// check if the firewall no longer exists.
if resp != nil && resp.StatusCode == 404 {
log.Printf("[WARN] DigitalOcean Firewall (%s) not found", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("Error retrieving firewall: %s", err)
}
d.Set("status", firewall.Status)
d.Set("create_at", firewall.Created)
d.Set("pending_changes", firewallPendingChanges(d, firewall))
d.Set("name", firewall.Name)
d.Set("droplet_ids", firewall.DropletIDs)
d.Set("tags", firewall.Tags)
if err := d.Set("inbound_rule", flattenFirewallInboundRules(d, firewall.InboundRules)); err != nil {
return fmt.Errorf("[DEBUG] Error setting Firewall inbound_rule error: %#v", err)
}
if err := d.Set("outbound_rule", flattenFirewallOutboundRules(d, firewall.OutboundRules)); err != nil {
return fmt.Errorf("[DEBUG] Error setting Firewall outbound_rule error: %#v", err)
}
return nil
}
func resourceDigitalOceanFirewallUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)
opts, err := firewallRequest(d, client)
if err != nil {
return fmt.Errorf("Error in firewall request: %s", err)
}
log.Printf("[DEBUG] Firewall update configuration: %#v", opts)
_, _, err = client.Firewalls.Update(context.Background(), d.Id(), opts)
if err != nil {
return fmt.Errorf("Error updating firewall: %s", err)
}
return resourceDigitalOceanFirewallRead(d, meta)
}
func resourceDigitalOceanFirewallDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)
log.Printf("[INFO] Deleting firewall: %s", d.Id())
// Destroy the droplet
_, err := client.Firewalls.Delete(context.Background(), d.Id())
// Handle remotely destroyed droplets
if err != nil && strings.Contains(err.Error(), "404 Not Found") {
return nil
}
if err != nil {
return fmt.Errorf("Error deleting firewall: %s", err)
}
return nil
}
func resourceDigitalOceanFirewallExists(d *schema.ResourceData, meta interface{}) (bool, error) {
client := meta.(*godo.Client)
log.Printf("[INFO] Exists firewall: %s", d.Id())
// Retrieve the firewall properties for updating the state
_, resp, err := client.Firewalls.Get(context.Background(), d.Id())
if err != nil {
// check if the firewall no longer exists.
if resp != nil && resp.StatusCode == 404 {
log.Printf("[WARN] DigitalOcean Firewall (%s) not found", d.Id())
d.SetId("")
return false, nil
}
return false, fmt.Errorf("Error retrieving firewall: %s", err)
}
return true, nil
}
func firewallRequest(d *schema.ResourceData, client *godo.Client) (*godo.FirewallRequest, error) {
// Build up our firewall request
opts := &godo.FirewallRequest{
Name: d.Get("name").(string),
}
if v, ok := d.GetOk("droplet_ids"); ok {
var droplets []int
for _, id := range v.([]interface{}) {
i, err := strconv.Atoi(id.(string))
if err != nil {
return nil, err
}
droplets = append(droplets, i)
}
opts.DropletIDs = droplets
}
if v, ok := d.GetOk("tags"); ok {
var tags []string
for _, tag := range v.([]interface{}) {
tags = append(tags, tag.(string))
}
opts.Tags = tags
}
// Get inbound_rules
opts.InboundRules = expandFirewallInboundRules(d)
// Get outbound_rules
opts.OutboundRules = expandFirewallOutboundRules(d)
return opts, nil
}
func expandFirewallInboundRules(d *schema.ResourceData) []godo.InboundRule {
rules := make([]godo.InboundRule, 0, len(d.Get("inbound_rule").([]interface{})))
for _, rawRule := range d.Get("inbound_rule").([]interface{}) {
var src godo.Sources
rule := rawRule.(map[string]interface{})
sourceAddresses := rule["source_addresses"].([]interface{})
for _, address := range sourceAddresses {
src.Addresses = append(src.Addresses, address.(string))
}
sourceTags := rule["source_tags"].([]interface{})
for _, tag := range sourceTags {
src.Tags = append(src.Tags, tag.(string))
}
dropletIds := rule["source_droplet_ids"].([]interface{})
for _, dropletId := range dropletIds {
src.DropletIDs = append(src.DropletIDs, dropletId.(int))
}
lbIds := rule["source_load_balancer_uids"].([]interface{})
for _, lbId := range lbIds {
src.LoadBalancerUIDs = append(src.LoadBalancerUIDs, lbId.(string))
}
r := godo.InboundRule{
Protocol: rule["protocol"].(string),
PortRange: rule["port_range"].(string),
Sources: &src,
}
rules = append(rules, r)
}
return rules
}
func expandFirewallOutboundRules(d *schema.ResourceData) []godo.OutboundRule {
rules := make([]godo.OutboundRule, 0, len(d.Get("outbound_rule").([]interface{})))
for _, rawRule := range d.Get("outbound_rule").([]interface{}) {
var dest godo.Destinations
rule := rawRule.(map[string]interface{})
destinationAddresses := rule["destination_addresses"].([]interface{})
for _, address := range destinationAddresses {
dest.Addresses = append(dest.Addresses, address.(string))
}
destinationTags := rule["destination_tags"].([]interface{})
for _, tag := range destinationTags {
dest.Addresses = append(dest.Tags, tag.(string))
}
dropletIds := rule["destination_droplet_ids"].([]interface{})
for _, dropletId := range dropletIds {
dest.DropletIDs = append(dest.DropletIDs, dropletId.(int))
}
lbIds := rule["destination_load_balancer_uids"].([]interface{})
for _, lbId := range lbIds {
dest.LoadBalancerUIDs = append(dest.LoadBalancerUIDs, lbId.(string))
}
r := godo.OutboundRule{
Protocol: rule["protocol"].(string),
PortRange: rule["port_range"].(string),
Destinations: &dest,
}
rules = append(rules, r)
}
return rules
}
func firewallPendingChanges(d *schema.ResourceData, firewall *godo.Firewall) []interface{} {
remote := make([]interface{}, 0, len(firewall.PendingChanges))
for _, change := range firewall.PendingChanges {
rawChange := map[string]interface{}{
"droplet_id": change.DropletID,
"removing": change.Removing,
"status": change.Status,
}
remote = append(remote, rawChange)
}
return remote
}
func flattenFirewallInboundRules(d *schema.ResourceData, rules []godo.InboundRule) []interface{} {
if rules == nil {
return nil
}
// Prepare the data.
local := d.Get("inbound_rule").([]interface{})
remote := make([]interface{}, 0, len(rules))
remoteMap := make(map[int]map[string]interface{})
for _, rule := range rules {
rawRule := map[string]interface{}{
"protocol": rule.Protocol,
"port_range": rule.PortRange,
"source_droplet_ids": rule.Sources.DropletIDs,
"source_tags": rule.Sources.Tags,
"source_addresses": rule.Sources.Addresses,
"source_load_balancer_uids": rule.Sources.LoadBalancerUIDs,
}
remote = append(remote, rawRule)
hash := hashFirewallRule(rule.Protocol, rule.PortRange)
remoteMap[hash] = rawRule
}
// Handle special cases, both using the remote rules.
if len(remote) == 0 || len(local) == 0 {
return remote
}
// Update the local rules to only contains rules match
// to the remote rules.
match := make([]interface{}, 0, len(rules))
for _, rawRule := range local {
local := rawRule.(map[string]interface{})
protocol := local["protocol"].(string)
portRange := local["port_range"].(string)
hash := hashFirewallRule(protocol, portRange)
remote, ok := remoteMap[hash]
if !ok {
// No entry in the remote, remove it.
continue
}
// matches source lists.
key := "source_droplet_ids"
local[key] = matchFirewallIntLists(key, local, remote)
keys := []string{
"source_tags",
"source_addresses",
"source_load_balancer_uids",
}
for _, key := range keys {
local[key] = matchFirewallStringLists(key, local, remote)
}
match = append(match, local)
delete(remoteMap, hash)
}
// Append the remaining remote rules.
for _, rawRule := range remoteMap {
match = append(match, rawRule)
}
return match
}
func flattenFirewallOutboundRules(d *schema.ResourceData, rules []godo.OutboundRule) []interface{} {
// Prepare the data.
local := d.Get("outbound_rule").([]interface{})
remote := make([]interface{}, 0, len(rules))
remoteMap := make(map[int]map[string]interface{})
for _, rule := range rules {
rawRule := map[string]interface{}{
"protocol": rule.Protocol,
"port_range": rule.PortRange,
"destination_droplet_ids": rule.Destinations.DropletIDs,
"destination_tags": rule.Destinations.Tags,
"destination_addresses": rule.Destinations.Addresses,
"destination_load_balancer_uids": rule.Destinations.LoadBalancerUIDs,
}
remote = append(remote, rawRule)
hash := hashFirewallRule(rule.Protocol, rule.PortRange)
remoteMap[hash] = rawRule
}
// Handle special cases, both using the remote rules.
if len(remote) == 0 || len(local) == 0 {
return remote
}
// Update the local rules to only contains rules match
// to the remote rules.
match := make([]interface{}, 0, len(rules))
for _, rawRule := range local {
local := rawRule.(map[string]interface{})
protocol := local["protocol"].(string)
portRange := local["port_range"].(string)
hash := hashFirewallRule(protocol, portRange)
remote, ok := remoteMap[hash]
if !ok {
// No entry in the remote, remove it.
continue
}
// matches destination lists.
key := "destination_droplet_ids"
local[key] = matchFirewallIntLists(key, local, remote)
keys := []string{
"destination_tags",
"destination_addresses",
"destination_load_balancer_uids",
}
for _, key := range keys {
local[key] = matchFirewallStringLists(key, local, remote)
}
match = append(match, local)
delete(remoteMap, hash)
}
// Append the remaining remote rules.
for _, rawRule := range remoteMap {
match = append(match, rawRule)
}
return match
}
func matchFirewallIntLists(key string, local, remote map[string]interface{}) []interface{} {
remoteSize := len(remote[key].([]int))
remoteSet := make(map[int]bool)
matchedList := make([]interface{}, 0, remoteSize)
// Create a remote set out of the list for the quick comparison.
for _, i := range remote[key].([]int) {
remoteSet[i] = true
}
// Add only the item which exists in the remote list.
for _, i := range local[key].([]interface{}) {
if _, ok := remoteSet[i.(int)]; !ok {
continue
}
matchedList = append(matchedList, i)
delete(remoteSet, i.(int))
}
// Append items only exists in the remote list.
for i := range remoteSet {
matchedList = append(matchedList, i)
}
return matchedList
}
func matchFirewallStringLists(key string, local, remote map[string]interface{}) []interface{} {
remoteSize := len(remote[key].([]string))
remoteList := make([]interface{}, 0, remoteSize)
matchedList := make([]interface{}, 0, remoteSize)
// Create a remote set out of the list for the quick comparison.
for _, s := range remote[key].([]string) {
remoteList = append(remoteList, s)
}
remoteSet := schema.NewSet(schema.HashString, remoteList)
// Add only the item which exists in the remote list.
for _, s := range local[key].([]interface{}) {
if !remoteSet.Contains(s.(string)) {
continue
}
matchedList = append(matchedList, s)
remoteSet.Remove(s)
}
// Append items only exists in the remote list.
for _, s := range remoteSet.List() {
matchedList = append(matchedList, s)
}
return matchedList
}
func hashFirewallRule(protocol, portRange string) int {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("%s-%s", protocol, portRange))
return hashcode.String(buf.String())
}

View File

@ -0,0 +1,328 @@
package digitalocean
import (
"context"
"fmt"
"testing"
"github.com/digitalocean/godo"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccDigitalOceanFirewall_AllowOnlyInbound(t *testing.T) {
rName := acctest.RandString(10)
var firewall godo.Firewall
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanFirewallDestroy,
Steps: []resource.TestStep{
{
Config: testAccDigitalOceanFirewallConfig_OnlyInbound(rName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckDigitalOceanFirewallExists("digitalocean_firewall.foobar", &firewall),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.#", "1"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.port_range", "22"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.source_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.source_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.source_addresses.1", "::/0"),
),
},
},
})
}
func TestAccDigitalOceanFirewall_AllowMultipleInbound(t *testing.T) {
rName := acctest.RandString(10)
var firewall godo.Firewall
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanFirewallDestroy,
Steps: []resource.TestStep{
{
Config: testAccDigitalOceanFirewallConfig_OnlyMultipleInbound(rName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckDigitalOceanFirewallExists("digitalocean_firewall.foobar", &firewall),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.port_range", "22"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.source_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.source_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.source_addresses.1", "::/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.1.port_range", "80"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.1.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.1.source_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.1.source_addresses.0", "1.2.3.0/24"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.1.source_addresses.1", "2002::/16"),
),
},
},
})
}
func TestAccDigitalOceanFirewall_AllowOnlyOutbound(t *testing.T) {
rName := acctest.RandString(10)
var firewall godo.Firewall
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanFirewallDestroy,
Steps: []resource.TestStep{
{
Config: testAccDigitalOceanFirewallConfig_OnlyOutbound(rName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckDigitalOceanFirewallExists("digitalocean_firewall.foobar", &firewall),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.#", "1"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.port_range", "22"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.destination_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.destination_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.destination_addresses.1", "::/0"),
),
},
},
})
}
func TestAccDigitalOceanFirewall_AllowMultipleOutbound(t *testing.T) {
rName := acctest.RandString(10)
var firewall godo.Firewall
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanFirewallDestroy,
Steps: []resource.TestStep{
{
Config: testAccDigitalOceanFirewallConfig_OnlyMultipleOutbound(rName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckDigitalOceanFirewallExists("digitalocean_firewall.foobar", &firewall),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.port_range", "22"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.destination_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.destination_addresses.0", "192.168.1.0/24"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.destination_addresses.1", "2002:1001::/48"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.1.port_range", "53"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.1.protocol", "udp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.1.destination_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.1.destination_addresses.0", "1.2.3.0/24"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.1.destination_addresses.1", "2002::/16"),
),
},
},
})
}
func TestAccDigitalOceanFirewall_MultipleInboundAndOutbound(t *testing.T) {
rName := acctest.RandString(10)
var firewall godo.Firewall
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanFirewallDestroy,
Steps: []resource.TestStep{
{
Config: testAccDigitalOceanFirewallConfig_MultipleInboundAndOutbound(rName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckDigitalOceanFirewallExists("digitalocean_firewall.foobar", &firewall),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.1.port_range", "443"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.1.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.1.source_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.1.source_addresses.0", "192.168.1.0/24"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.1.source_addresses.1", "2002:1001:1:2::/64"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.port_range", "22"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.source_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.source_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rule.0.source_addresses.1", "::/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.port_range", "443"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.destination_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.destination_addresses.0", "192.168.1.0/24"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.0.destination_addresses.1", "2002:1001:1:2::/64"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.1.port_range", "53"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.1.protocol", "udp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.1.destination_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.1.destination_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rule.1.destination_addresses.1", "::/0"),
),
},
},
})
}
func TestAccDigitalOceanFirewall_ImportMultipleRules(t *testing.T) {
resourceName := "digitalocean_firewall.foobar"
rName := acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanFirewallDestroy,
Steps: []resource.TestStep{
{
Config: testAccDigitalOceanFirewallConfig_MultipleInboundAndOutbound(rName),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func testAccDigitalOceanFirewallConfig_OnlyInbound(rName string) string {
return fmt.Sprintf(`
resource "digitalocean_firewall" "foobar" {
name = "%s"
inbound_rule {
protocol = "tcp"
port_range = "22"
source_addresses = ["0.0.0.0/0", "::/0"]
}
}
`, rName)
}
func testAccDigitalOceanFirewallConfig_OnlyOutbound(rName string) string {
return fmt.Sprintf(`
resource "digitalocean_firewall" "foobar" {
name = "%s"
outbound_rule {
protocol = "tcp"
port_range = "22"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
}
`, rName)
}
func testAccDigitalOceanFirewallConfig_OnlyMultipleInbound(rName string) string {
return fmt.Sprintf(`
resource "digitalocean_firewall" "foobar" {
name = "%s"
inbound_rule {
protocol = "tcp"
port_range = "22"
source_addresses = ["0.0.0.0/0", "::/0"]
}
inbound_rule {
protocol = "tcp"
port_range = "80"
source_addresses = ["1.2.3.0/24", "2002::/16"]
}
}
`, rName)
}
func testAccDigitalOceanFirewallConfig_OnlyMultipleOutbound(rName string) string {
return fmt.Sprintf(`
resource "digitalocean_firewall" "foobar" {
name = "%s"
outbound_rule {
protocol = "tcp"
port_range = "22"
destination_addresses = ["192.168.1.0/24", "2002:1001::/48"]
}
outbound_rule {
protocol = "udp"
port_range = "53"
destination_addresses = ["1.2.3.0/24", "2002::/16"]
}
}
`, rName)
}
func testAccDigitalOceanFirewallConfig_MultipleInboundAndOutbound(rName string) string {
return fmt.Sprintf(`
resource "digitalocean_firewall" "foobar" {
name = "%s"
inbound_rule {
protocol = "tcp"
port_range = "22"
source_addresses = ["0.0.0.0/0", "::/0"]
}
inbound_rule {
protocol = "tcp"
port_range = "443"
source_addresses = ["192.168.1.0/24", "2002:1001:1:2::/64"]
}
outbound_rule {
protocol = "tcp"
port_range = "443"
destination_addresses = ["192.168.1.0/24", "2002:1001:1:2::/64"]
}
outbound_rule {
protocol = "udp"
port_range = "53"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
}
`, rName)
}
func testAccCheckDigitalOceanFirewallDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*godo.Client)
for _, rs := range s.RootModule().Resources {
if rs.Type != "digitalocean_firewall" {
continue
}
// Try to find the firewall
_, _, err := client.Firewalls.Get(context.Background(), rs.Primary.ID)
if err == nil {
return fmt.Errorf("Firewall still exists")
}
}
return nil
}
func testAccCheckDigitalOceanFirewallExists(n string, firewall *godo.Firewall) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No Record ID is set")
}
client := testAccProvider.Meta().(*godo.Client)
foundFirewall, _, err := client.Firewalls.Get(context.Background(), rs.Primary.ID)
if err != nil {
return err
}
if foundFirewall.ID != rs.Primary.ID {
return fmt.Errorf("Record not found")
}
*firewall = *foundFirewall
return nil
}
}

View File

@ -33,6 +33,10 @@
<a href="/docs/providers/do/r/droplet.html">digitalocean_droplet</a>
</li>
<li<%= sidebar_current("docs-do-resource-firewall") %>>
<a href="/docs/providers/do/r/firewall.html">digitalocean_firewall</a>
</li>
<li<%= sidebar_current("docs-do-resource-floating-ip") %>>
<a href="/docs/providers/do/r/floating_ip.html">digitalocean_floating_ip</a>
</li>

View File

@ -0,0 +1,134 @@
---
layout: "digitalocean"
page_title: "DigitalOcean: digitalocean_firewall"
sidebar_current: "docs-do-resource-firewall"
description: |-
Provides a DigitalOcean Cloud Firewall resource. This can be used to create, modify, and delete Firewalls.
---
# digitalocean\_firewall
Provides a DigitalOcean Cloud Firewall resource. This can be used to create,
modify, and delete Firewalls.
## Example Usage
```hcl
resource "digitalocean_droplet" "web" {
name = "web-1"
size = "512mb"
image = "centos-7-x64"
region = "nyc3"
}
resource "digitalocean_firewall" "web" {
name = "only-22-80-and-443"
droplet_ids = ["${digitalocean_droplet.web.id}"]
inbound_rules = [
{
protocol = "tcp"
port_range = "22"
source_addresses = ["192.168.1.0/24", "2002:1:2::/48"]
},
{
protocol = "tcp"
port_range = "80"
source_addresses = ["0.0.0.0/0", "::/0"]
},
{
protocol = "tcp"
port_range = "443"
source_addresses = ["0.0.0.0/0", "::/0"]
},
]
outbound_rules = [
{
protocol = "udp"
port_range = "53"
destination_addresses = ["0.0.0.0/0", "::/0"]
},
]
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The Firewall name
* `droplet_ids` (Optional) - The list of the IDs of the Droplets assigned
to the Firewall.
* `tags` (Optional) - The names of the Tags assigned to the Firewall.
* `inbound_rules` - (Optional) The inbound access rule block for the Firewall.
The `inbound_rules` block is documented below.
* `outbound_rules` - (Optional) The outbound access rule block for the Firewall.
The `outbound_rules` block is documented below.
`inbound_rule` supports the following:
* `protocol` - (Optional) The type of traffic to be allowed.
This may be one of "tcp", "udp", or "icmp".
* `port_range` - (Optional) The ports on which traffic will be allowed
specified as a string containing a single port, a range (e.g. "8000-9000"),
or "all" to open all ports for a protocol.
* `source_addresses` - (Optional) An array of strings containing the IPv4
addresses, IPv6 addresses, IPv4 CIDRs, and/or IPv6 CIDRs from which the
inbound traffic will be accepted.
* `source_droplet_ids` - (Optional) An array containing the IDs of
the Droplets from which the inbound traffic will be accepted.
* `source_tags` - (Optional) An array containing the names of Tags
corresponding to groups of Droplets from which the inbound traffic
will be accepted.
* `source_load_balancer_uids` - (Optional) An array containing the IDs
of the Load Balancers from which the inbound traffic will be accepted.
`outbound_rule` supports the following:
* `protocol` - (Optional) The type of traffic to be allowed.
This may be one of "tcp", "udp", or "icmp".
* `port_range` - (Optional) The ports on which traffic will be allowed
specified as a string containing a single port, a range (e.g. "8000-9000"),
or "all" to open all ports for a protocol.
* `destination_addresses` - (Optional) An array of strings containing the IPv4
addresses, IPv6 addresses, IPv4 CIDRs, and/or IPv6 CIDRs to which the
outbound traffic will be allowed.
* `destination_droplet_ids` - (Optional) An array containing the IDs of
the Droplets to which the outbound traffic will be allowed.
* `destination_tags` - (Optional) An array containing the names of Tags
corresponding to groups of Droplets to which the outbound traffic will
be allowed.
traffic.
* `destination_load_balancer_uids` - (Optional) An array containing the IDs
of the Load Balancers to which the outbound traffic will be allowed.
## Attributes Reference
The following attributes are exported:
* `id` - A unique ID that can be used to identify and reference a Firewall.
* `status` - A status string indicating the current state of the Firewall.
This can be "waiting", "succeeded", or "failed".
* `created_at` - A time value given in ISO8601 combined date and time format
that represents when the Firewall was created.
* `pending_changes` - An list of object containing the fields, "droplet_id",
"removing", and "status". It is provided to detail exactly which Droplets
are having their security policies updated. When empty, all changes
have been successfully applied.
* `name` - The name of the Firewall.
* `droplet_ids` - The list of the IDs of the Droplets assigned to
the Firewall.
* `tags` - The names of the Tags assigned to the Firewall.
* `inbound_rules` - The inbound access rule block for the Firewall.
* `outbound_rules` - The outbound access rule block for the Firewall.
## Import
Firewalls can be imported using the firewall `id`, e.g.
```
terraform import digitalocean_firewall.myfirewall b8ecd2ab-2267-4a5e-8692-cbf1d32583e3
```