Merge branch 'master' into rmenn-aws-go-vpc

* master: (69 commits)
  upgrade tests and remove ICMPTypeCode for now
  helper/ssh: update import location
  clean  up
  provider/aws: Convert AWS Network ACL to aws-sdk-go
  Update website docs on AWS RDS encryption field
  more test updates
  provider/aws update Network ACL tests
  code cleanup on subnet check
  restore IOPS positioning
  Code cleanup
  Update CHANGELOG.md
  Bugfix: Add tags on AWS IG creation, not just on update
  fix nit-pick from go vet
  remove duplicated function
  provider/aws: Convert AWS Route Table Association to aws-sdk-go
  Cleansup: Restore expandIPPerms, remove flattenIPPerms
  clean up debug output to make go vet happy
  providers/aws: Convert AWS VPC Peering to aws-sdk-go
  provider/aws: Add env default for AWS_ACCOUNT_ID in VPC Peering connection
  convert route table tests to aws-sdk-go
  ...
This commit is contained in:
Clint Shryock 2015-03-12 15:07:28 -05:00
commit ddc2d8de2e
115 changed files with 4423 additions and 1614 deletions

View File

@ -10,6 +10,7 @@ install: make updatedeps
script: script:
- go test ./... - go test ./...
- make vet
#- go test -race ./... #- go test -race ./...
branches: branches:

View File

@ -1,5 +1,12 @@
## 0.4.0 (unreleased) ## 0.4.0 (unreleased)
BACKWARDS INCOMPATIBILITIES:
* Commands `terraform push` and `terraform pull` are now nested under
the `remote` command: `terraform remote push` and `terraform remote pull`.
The old `remote` functionality is now at `terraform remote config`. This
consolidates all remote state management under one command.
FEATURES: FEATURES:
* **New provider: `dme` (DNSMadeEasy)** [GH-855] * **New provider: `dme` (DNSMadeEasy)** [GH-855]
@ -16,11 +23,14 @@ FEATURES:
IMPROVEMENTS: IMPROVEMENTS:
* **New config function: `format`** - Format a string using `sprintf`
format. [GH-1096]
* **New config function: `replace`** - Search and replace string values. * **New config function: `replace`** - Search and replace string values.
Search can be a regular expression. See documentation for more Search can be a regular expression. See documentation for more
info. [GH-1029] info. [GH-1029]
* **New config function: `split`** - Split a value based on a delimiter. * **New config function: `split`** - Split a value based on a delimiter.
This is useful for faking lists as parameters to modules. This is useful for faking lists as parameters to modules.
* **New resource: `digitalocean_ssh_key`** [GH-1074]
* core: The serial of the state is only updated if there is an actual * core: The serial of the state is only updated if there is an actual
change. This will lower the amount of state changing on things change. This will lower the amount of state changing on things
like refresh. like refresh.
@ -39,9 +49,16 @@ BUG FIXES:
"resource.0" would ignore the latter completely. [GH-1086] "resource.0" would ignore the latter completely. [GH-1086]
* providers/aws: manually deleted VPC removes it from the state * providers/aws: manually deleted VPC removes it from the state
* providers/aws: `source_dest_check` regression fixed (now works). [GH-1020] * providers/aws: `source_dest_check` regression fixed (now works). [GH-1020]
* providers/aws: Longer wait times for DB instances.
* providers/aws: Longer wait times for route53 records (30 mins). [GH-1164]
* providers/digitalocean: Waits until droplet is ready to be destroyed [GH-1057] * providers/digitalocean: Waits until droplet is ready to be destroyed [GH-1057]
* providers/digitalocean: More lenient about 404's while waiting [GH-1062] * providers/digitalocean: More lenient about 404's while waiting [GH-1062]
* providers/aws: Longer wait times for DB instances * providers/google: Network data in state was not being stored. [GH-1095]
PLUGIN CHANGES:
* New `helper/schema` fields for resources: `Deprecated` and `Removed` allow
plugins to generate warning or error messages when a given attribute is used.
## 0.3.7 (February 19, 2015) ## 0.3.7 (February 19, 2015)

View File

@ -12,6 +12,9 @@ bin: generate
dev: generate dev: generate
@TF_DEV=1 sh -c "'$(CURDIR)/scripts/build.sh'" @TF_DEV=1 sh -c "'$(CURDIR)/scripts/build.sh'"
quickdev: generate
@TF_QUICKDEV=1 TF_DEV=1 sh -c "'$(CURDIR)/scripts/build.sh'"
# test runs the unit tests and vets the code # test runs the unit tests and vets the code
test: generate test: generate
TF_ACC= go test $(TEST) $(TESTARGS) -timeout=30s -parallel=4 TF_ACC= go test $(TEST) $(TESTARGS) -timeout=30s -parallel=4

View File

@ -2,11 +2,14 @@ package aws
import ( import (
"fmt" "fmt"
"github.com/mitchellh/goamz/ec2" "strconv"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
) )
func expandNetworkAclEntries(configured []interface{}, entryType string) ([]ec2.NetworkAclEntry, error) { func expandNetworkAclEntries(configured []interface{}, entryType string) ([]ec2.NetworkACLEntry, error) {
entries := make([]ec2.NetworkAclEntry, 0, len(configured)) entries := make([]ec2.NetworkACLEntry, 0, len(configured))
for _, eRaw := range configured { for _, eRaw := range configured {
data := eRaw.(map[string]interface{}) data := eRaw.(map[string]interface{})
protocol := data["protocol"].(string) protocol := data["protocol"].(string)
@ -15,16 +18,16 @@ func expandNetworkAclEntries(configured []interface{}, entryType string) ([]ec2.
return nil, fmt.Errorf("Invalid Protocol %s for rule %#v", protocol, data) return nil, fmt.Errorf("Invalid Protocol %s for rule %#v", protocol, data)
} }
p := extractProtocolInteger(data["protocol"].(string)) p := extractProtocolInteger(data["protocol"].(string))
e := ec2.NetworkAclEntry{ e := ec2.NetworkACLEntry{
Protocol: p, Protocol: aws.String(strconv.Itoa(p)),
PortRange: ec2.PortRange{ PortRange: &ec2.PortRange{
From: data["from_port"].(int), From: aws.Integer(data["from_port"].(int)),
To: data["to_port"].(int), To: aws.Integer(data["to_port"].(int)),
}, },
Egress: (entryType == "egress"), Egress: aws.Boolean((entryType == "egress")),
RuleAction: data["action"].(string), RuleAction: aws.String(data["action"].(string)),
RuleNumber: data["rule_no"].(int), RuleNumber: aws.Integer(data["rule_no"].(int)),
CidrBlock: data["cidr_block"].(string), CIDRBlock: aws.String(data["cidr_block"].(string)),
} }
entries = append(entries, e) entries = append(entries, e)
} }
@ -33,17 +36,17 @@ func expandNetworkAclEntries(configured []interface{}, entryType string) ([]ec2.
} }
func flattenNetworkAclEntries(list []ec2.NetworkAclEntry) []map[string]interface{} { func flattenNetworkAclEntries(list []ec2.NetworkACLEntry) []map[string]interface{} {
entries := make([]map[string]interface{}, 0, len(list)) entries := make([]map[string]interface{}, 0, len(list))
for _, entry := range list { for _, entry := range list {
entries = append(entries, map[string]interface{}{ entries = append(entries, map[string]interface{}{
"from_port": entry.PortRange.From, "from_port": *entry.PortRange.From,
"to_port": entry.PortRange.To, "to_port": *entry.PortRange.To,
"action": entry.RuleAction, "action": *entry.RuleAction,
"rule_no": entry.RuleNumber, "rule_no": *entry.RuleNumber,
"protocol": extractProtocolString(entry.Protocol), "protocol": *entry.Protocol,
"cidr_block": entry.CidrBlock, "cidr_block": *entry.CIDRBlock,
}) })
} }
return entries return entries

View File

@ -4,10 +4,11 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/mitchellh/goamz/ec2" "github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
) )
func Test_expandNetworkAclEntry(t *testing.T) { func Test_expandNetworkACLEntry(t *testing.T) {
input := []interface{}{ input := []interface{}{
map[string]interface{}{ map[string]interface{}{
"protocol": "tcp", "protocol": "tcp",
@ -28,30 +29,28 @@ func Test_expandNetworkAclEntry(t *testing.T) {
} }
expanded, _ := expandNetworkAclEntries(input, "egress") expanded, _ := expandNetworkAclEntries(input, "egress")
expected := []ec2.NetworkAclEntry{ expected := []ec2.NetworkACLEntry{
ec2.NetworkAclEntry{ ec2.NetworkACLEntry{
Protocol: 6, Protocol: aws.String("6"),
PortRange: ec2.PortRange{ PortRange: &ec2.PortRange{
From: 22, From: aws.Integer(22),
To: 22, To: aws.Integer(22),
}, },
RuleAction: "deny", RuleAction: aws.String("deny"),
RuleNumber: 1, RuleNumber: aws.Integer(1),
CidrBlock: "0.0.0.0/0", CIDRBlock: aws.String("0.0.0.0/0"),
Egress: true, Egress: aws.Boolean(true),
IcmpCode: ec2.IcmpCode{Code: 0, Type: 0},
}, },
ec2.NetworkAclEntry{ ec2.NetworkACLEntry{
Protocol: 6, Protocol: aws.String("6"),
PortRange: ec2.PortRange{ PortRange: &ec2.PortRange{
From: 443, From: aws.Integer(443),
To: 443, To: aws.Integer(443),
}, },
RuleAction: "deny", RuleAction: aws.String("deny"),
RuleNumber: 2, RuleNumber: aws.Integer(2),
CidrBlock: "0.0.0.0/0", CIDRBlock: aws.String("0.0.0.0/0"),
Egress: true, Egress: aws.Boolean(true),
IcmpCode: ec2.IcmpCode{Code: 0, Type: 0},
}, },
} }
@ -64,28 +63,28 @@ func Test_expandNetworkAclEntry(t *testing.T) {
} }
func Test_flattenNetworkAclEntry(t *testing.T) { func Test_flattenNetworkACLEntry(t *testing.T) {
apiInput := []ec2.NetworkAclEntry{ apiInput := []ec2.NetworkACLEntry{
ec2.NetworkAclEntry{ ec2.NetworkACLEntry{
Protocol: 6, Protocol: aws.String("tcp"),
PortRange: ec2.PortRange{ PortRange: &ec2.PortRange{
From: 22, From: aws.Integer(22),
To: 22, To: aws.Integer(22),
}, },
RuleAction: "deny", RuleAction: aws.String("deny"),
RuleNumber: 1, RuleNumber: aws.Integer(1),
CidrBlock: "0.0.0.0/0", CIDRBlock: aws.String("0.0.0.0/0"),
}, },
ec2.NetworkAclEntry{ ec2.NetworkACLEntry{
Protocol: 6, Protocol: aws.String("tcp"),
PortRange: ec2.PortRange{ PortRange: &ec2.PortRange{
From: 443, From: aws.Integer(443),
To: 443, To: aws.Integer(443),
}, },
RuleAction: "deny", RuleAction: aws.String("deny"),
RuleNumber: 2, RuleNumber: aws.Integer(2),
CidrBlock: "0.0.0.0/0", CIDRBlock: aws.String("0.0.0.0/0"),
}, },
} }
flattened := flattenNetworkAclEntries(apiInput) flattened := flattenNetworkAclEntries(apiInput)

View File

@ -156,7 +156,6 @@ func resourceAwsDbInstance() *schema.Resource {
"final_snapshot_identifier": &schema.Schema{ "final_snapshot_identifier": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true,
}, },
"db_subnet_group_name": &schema.Schema{ "db_subnet_group_name": &schema.Schema{
@ -324,7 +323,7 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error {
d.Set("parameter_group_name", *v.DBParameterGroups[0].DBParameterGroupName) d.Set("parameter_group_name", *v.DBParameterGroups[0].DBParameterGroupName)
} }
d.Set("address", *v.Endpoint.Port) d.Set("address", *v.Endpoint.Address)
d.Set("endpoint", fmt.Sprintf("%s:%d", *v.Endpoint.Address, *v.Endpoint.Port)) d.Set("endpoint", fmt.Sprintf("%s:%d", *v.Endpoint.Address, *v.Endpoint.Port))
d.Set("status", *v.DBInstanceStatus) d.Set("status", *v.DBInstanceStatus)
d.Set("storage_encrypted", *v.StorageEncrypted) d.Set("storage_encrypted", *v.StorageEncrypted)

View File

@ -6,9 +6,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
) )
func resourceAwsEip() *schema.Resource { func resourceAwsEip() *schema.Resource {
@ -59,7 +60,7 @@ func resourceAwsEip() *schema.Resource {
} }
func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
// By default, we're not in a VPC // By default, we're not in a VPC
domainOpt := "" domainOpt := ""
@ -67,12 +68,12 @@ func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error {
domainOpt = "vpc" domainOpt = "vpc"
} }
allocOpts := ec2.AllocateAddress{ allocOpts := &ec2.AllocateAddressRequest{
Domain: domainOpt, Domain: aws.String(domainOpt),
} }
log.Printf("[DEBUG] EIP create configuration: %#v", allocOpts) log.Printf("[DEBUG] EIP create configuration: %#v", allocOpts)
allocResp, err := ec2conn.AllocateAddress(&allocOpts) allocResp, err := ec2conn.AllocateAddress(allocOpts)
if err != nil { if err != nil {
return fmt.Errorf("Error creating EIP: %s", err) return fmt.Errorf("Error creating EIP: %s", err)
} }
@ -86,17 +87,17 @@ func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error {
// it defaults to using the public IP // it defaults to using the public IP
log.Printf("[DEBUG] EIP Allocate: %#v", allocResp) log.Printf("[DEBUG] EIP Allocate: %#v", allocResp)
if d.Get("domain").(string) == "vpc" { if d.Get("domain").(string) == "vpc" {
d.SetId(allocResp.AllocationId) d.SetId(*allocResp.AllocationID)
} else { } else {
d.SetId(allocResp.PublicIp) d.SetId(*allocResp.PublicIP)
} }
log.Printf("[INFO] EIP ID: %s (domain: %v)", d.Id(), allocResp.Domain) log.Printf("[INFO] EIP ID: %s (domain: %v)", d.Id(), *allocResp.Domain)
return resourceAwsEipUpdate(d, meta) return resourceAwsEipUpdate(d, meta)
} }
func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
domain := resourceAwsEipDomain(d) domain := resourceAwsEipDomain(d)
id := d.Id() id := d.Id()
@ -113,9 +114,13 @@ func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
"[DEBUG] EIP describe configuration: %#v, %#v (domain: %s)", "[DEBUG] EIP describe configuration: %#v, %#v (domain: %s)",
assocIds, publicIps, domain) assocIds, publicIps, domain)
describeAddresses, err := ec2conn.Addresses(publicIps, assocIds, nil) req := &ec2.DescribeAddressesRequest{
AllocationIDs: assocIds,
PublicIPs: publicIps,
}
describeAddresses, err := ec2conn.DescribeAddresses(req)
if err != nil { if err != nil {
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAllocationID.NotFound" { if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidAllocationID.NotFound" {
d.SetId("") d.SetId("")
return nil return nil
} }
@ -125,8 +130,8 @@ func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
// Verify AWS returned our EIP // Verify AWS returned our EIP
if len(describeAddresses.Addresses) != 1 || if len(describeAddresses.Addresses) != 1 ||
describeAddresses.Addresses[0].AllocationId != id || *describeAddresses.Addresses[0].AllocationID != id ||
describeAddresses.Addresses[0].PublicIp != id { *describeAddresses.Addresses[0].PublicIP != id {
if err != nil { if err != nil {
return fmt.Errorf("Unable to find EIP: %#v", describeAddresses.Addresses) return fmt.Errorf("Unable to find EIP: %#v", describeAddresses.Addresses)
} }
@ -134,16 +139,16 @@ func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
address := describeAddresses.Addresses[0] address := describeAddresses.Addresses[0]
d.Set("association_id", address.AssociationId) d.Set("association_id", address.AssociationID)
d.Set("instance", address.InstanceId) d.Set("instance", address.InstanceID)
d.Set("public_ip", address.PublicIp) d.Set("private_ip", address.PrivateIPAddress)
d.Set("private_ip", address.PrivateIpAddress) d.Set("public_ip", address.PublicIP)
return nil return nil
} }
func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
domain := resourceAwsEipDomain(d) domain := resourceAwsEipDomain(d)
@ -151,22 +156,22 @@ func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
if v, ok := d.GetOk("instance"); ok { if v, ok := d.GetOk("instance"); ok {
instanceId := v.(string) instanceId := v.(string)
assocOpts := ec2.AssociateAddress{ assocOpts := &ec2.AssociateAddressRequest{
InstanceId: instanceId, InstanceID: aws.String(instanceId),
PublicIp: d.Id(), PublicIP: aws.String(d.Id()),
} }
// more unique ID conditionals // more unique ID conditionals
if domain == "vpc" { if domain == "vpc" {
assocOpts = ec2.AssociateAddress{ assocOpts = &ec2.AssociateAddressRequest{
InstanceId: instanceId, InstanceID: aws.String(instanceId),
AllocationId: d.Id(), AllocationID: aws.String(d.Id()),
PublicIp: "", PublicIP: aws.String(""),
} }
} }
log.Printf("[DEBUG] EIP associate configuration: %#v (domain: %v)", assocOpts, domain) log.Printf("[DEBUG] EIP associate configuration: %#v (domain: %v)", assocOpts, domain)
_, err := ec2conn.AssociateAddress(&assocOpts) _, err := ec2conn.AssociateAddress(assocOpts)
if err != nil { if err != nil {
return fmt.Errorf("Failure associating instances: %s", err) return fmt.Errorf("Failure associating instances: %s", err)
} }
@ -176,7 +181,7 @@ func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
} }
func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error { func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
if err := resourceAwsEipRead(d, meta); err != nil { if err := resourceAwsEipRead(d, meta); err != nil {
return err return err
@ -192,9 +197,13 @@ func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error {
var err error var err error
switch resourceAwsEipDomain(d) { switch resourceAwsEipDomain(d) {
case "vpc": case "vpc":
_, err = ec2conn.DisassociateAddress(d.Get("association_id").(string)) err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressRequest{
AssociationID: aws.String(d.Get("association_id").(string)),
})
case "standard": case "standard":
_, err = ec2conn.DisassociateAddressClassic(d.Get("public_ip").(string)) err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressRequest{
PublicIP: aws.String(d.Get("public_ip").(string)),
})
} }
if err != nil { if err != nil {
return err return err
@ -209,16 +218,20 @@ func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error {
log.Printf( log.Printf(
"[DEBUG] EIP release (destroy) address allocation: %v", "[DEBUG] EIP release (destroy) address allocation: %v",
d.Id()) d.Id())
_, err = ec2conn.ReleaseAddress(d.Id()) err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressRequest{
AllocationID: aws.String(d.Id()),
})
case "standard": case "standard":
log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id()) log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id())
_, err = ec2conn.ReleasePublicAddress(d.Id()) err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressRequest{
PublicIP: aws.String(d.Id()),
})
} }
if err == nil { if err == nil {
return nil return nil
} }
if _, ok := err.(*ec2.Error); !ok { if _, ok := err.(aws.APIError); !ok {
return resource.RetryError{Err: err} return resource.RetryError{Err: err}
} }

View File

@ -5,9 +5,10 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/goamz/ec2"
) )
func TestAccAWSEIP_normal(t *testing.T) { func TestAccAWSEIP_normal(t *testing.T) {
@ -57,24 +58,28 @@ func TestAccAWSEIP_instance(t *testing.T) {
} }
func testAccCheckAWSEIPDestroy(s *terraform.State) error { func testAccCheckAWSEIPDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_eip" { if rs.Type != "aws_eip" {
continue continue
} }
describe, err := conn.Addresses([]string{rs.Primary.ID}, []string{}, nil) req := &ec2.DescribeAddressesRequest{
AllocationIDs: []string{},
PublicIPs: []string{rs.Primary.ID},
}
describe, err := conn.DescribeAddresses(req)
if err == nil { if err == nil {
if len(describe.Addresses) != 0 && if len(describe.Addresses) != 0 &&
describe.Addresses[0].PublicIp == rs.Primary.ID { *describe.Addresses[0].PublicIP == rs.Primary.ID {
return fmt.Errorf("EIP still exists") return fmt.Errorf("EIP still exists")
} }
} }
// Verify the error // Verify the error
providerErr, ok := err.(*ec2.Error) providerErr, ok := err.(aws.APIError)
if !ok { if !ok {
return err return err
} }
@ -89,7 +94,7 @@ func testAccCheckAWSEIPDestroy(s *terraform.State) error {
func testAccCheckAWSEIPAttributes(conf *ec2.Address) resource.TestCheckFunc { func testAccCheckAWSEIPAttributes(conf *ec2.Address) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
if conf.PublicIp == "" { if *conf.PublicIP == "" {
return fmt.Errorf("empty public_ip") return fmt.Errorf("empty public_ip")
} }
@ -108,28 +113,36 @@ func testAccCheckAWSEIPExists(n string, res *ec2.Address) resource.TestCheckFunc
return fmt.Errorf("No EIP ID is set") return fmt.Errorf("No EIP ID is set")
} }
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
if strings.Contains(rs.Primary.ID, "eipalloc") { if strings.Contains(rs.Primary.ID, "eipalloc") {
describe, err := conn.Addresses([]string{}, []string{rs.Primary.ID}, nil) req := &ec2.DescribeAddressesRequest{
AllocationIDs: []string{rs.Primary.ID},
PublicIPs: []string{},
}
describe, err := conn.DescribeAddresses(req)
if err != nil { if err != nil {
return err return err
} }
if len(describe.Addresses) != 1 || if len(describe.Addresses) != 1 ||
describe.Addresses[0].AllocationId != rs.Primary.ID { *describe.Addresses[0].AllocationID != rs.Primary.ID {
return fmt.Errorf("EIP not found") return fmt.Errorf("EIP not found")
} }
*res = describe.Addresses[0] *res = describe.Addresses[0]
} else { } else {
describe, err := conn.Addresses([]string{rs.Primary.ID}, []string{}, nil) req := &ec2.DescribeAddressesRequest{
AllocationIDs: []string{},
PublicIPs: []string{rs.Primary.ID},
}
describe, err := conn.DescribeAddresses(req)
if err != nil { if err != nil {
return err return err
} }
if len(describe.Addresses) != 1 || if len(describe.Addresses) != 1 ||
describe.Addresses[0].PublicIp != rs.Primary.ID { *describe.Addresses[0].PublicIP != rs.Primary.ID {
return fmt.Errorf("EIP not found") return fmt.Errorf("EIP not found")
} }
*res = describe.Addresses[0] *res = describe.Addresses[0]

View File

@ -6,14 +6,14 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"log" "log"
"strconv"
"strings" "strings"
"time" "time"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
) )
func resourceAwsInstance() *schema.Resource { func resourceAwsInstance() *schema.Resource {
@ -186,6 +186,13 @@ func resourceAwsInstance() *schema.Resource {
Computed: true, Computed: true,
ForceNew: true, ForceNew: true,
}, },
"iops": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
},
}, },
}, },
Set: resourceAwsInstanceBlockDevicesHash, Set: resourceAwsInstanceBlockDevicesHash,
@ -231,6 +238,13 @@ func resourceAwsInstance() *schema.Resource {
Computed: true, Computed: true,
ForceNew: true, ForceNew: true,
}, },
"iops": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
},
}, },
}, },
}, },
@ -239,7 +253,7 @@ func resourceAwsInstance() *schema.Resource {
} }
func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
// Figure out user data // Figure out user data
userData := "" userData := ""
@ -247,38 +261,84 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
userData = v.(string) userData = v.(string)
} }
placement := &ec2.Placement{
AvailabilityZone: aws.String(d.Get("availability_zone").(string)),
Tenancy: aws.String(d.Get("tenancy").(string)),
}
iam := &ec2.IAMInstanceProfileSpecification{
Name: aws.String(d.Get("iam_instance_profile").(string)),
}
// Build the creation struct
runOpts := &ec2.RunInstancesRequest{
ImageID: aws.String(d.Get("ami").(string)),
Placement: placement,
InstanceType: aws.String(d.Get("instance_type").(string)),
MaxCount: aws.Integer(1),
MinCount: aws.Integer(1),
UserData: aws.String(userData),
EBSOptimized: aws.Boolean(d.Get("ebs_optimized").(bool)),
IAMInstanceProfile: iam,
}
associatePublicIPAddress := false associatePublicIPAddress := false
if v := d.Get("associate_public_ip_address"); v != nil { if v := d.Get("associate_public_ip_address"); v != nil {
associatePublicIPAddress = v.(bool) associatePublicIPAddress = v.(bool)
} }
// Build the creation struct // check for non-default Subnet, and cast it to a String
runOpts := &ec2.RunInstances{ var hasSubnet bool
ImageId: d.Get("ami").(string), subnet, hasSubnet := d.GetOk("subnet_id")
AvailZone: d.Get("availability_zone").(string), subnetID := subnet.(string)
InstanceType: d.Get("instance_type").(string),
KeyName: d.Get("key_name").(string), if hasSubnet && associatePublicIPAddress {
SubnetId: d.Get("subnet_id").(string), // If we have a non-default VPC / Subnet specified, we can flag
PrivateIPAddress: d.Get("private_ip").(string), // AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided.
AssociatePublicIpAddress: associatePublicIPAddress, // You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise
UserData: []byte(userData), // you get: Network interfaces and an instance-level subnet ID may not be specified on the same request
EbsOptimized: d.Get("ebs_optimized").(bool), // You also need to attach Security Groups to the NetworkInterface instead of the instance,
IamInstanceProfile: d.Get("iam_instance_profile").(string), // to avoid: Network interfaces and an instance-level security groups may not be specified on
Tenancy: d.Get("tenancy").(string), // the same request
ni := ec2.InstanceNetworkInterfaceSpecification{
AssociatePublicIPAddress: aws.Boolean(associatePublicIPAddress),
DeviceIndex: aws.Integer(0),
SubnetID: aws.String(subnetID),
}
if v, ok := d.GetOk("private_ip"); ok {
ni.PrivateIPAddress = aws.String(v.(string))
}
runOpts.NetworkInterfaces = []ec2.InstanceNetworkInterfaceSpecification{ni}
} else {
if subnetID != "" {
runOpts.SubnetID = aws.String(subnetID)
}
if v, ok := d.GetOk("private_ip"); ok {
runOpts.PrivateIPAddress = aws.String(v.(string))
}
}
if v, ok := d.GetOk("key_name"); ok {
runOpts.KeyName = aws.String(v.(string))
} }
if v := d.Get("security_groups"); v != nil { if v := d.Get("security_groups"); v != nil {
// Security group names.
// For a nondefault VPC, you must use security group IDs instead.
// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html
var groups []string
for _, v := range v.(*schema.Set).List() { for _, v := range v.(*schema.Set).List() {
str := v.(string) str := v.(string)
groups = append(groups, str)
var g ec2.SecurityGroup }
if runOpts.SubnetId != "" { if runOpts.SubnetID != nil &&
g.Id = str *runOpts.SubnetID != "" {
} else { runOpts.SecurityGroupIDs = groups
g.Name = str } else {
} runOpts.SecurityGroups = groups
runOpts.SecurityGroups = append(runOpts.SecurityGroups, g)
} }
} }
@ -297,21 +357,27 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
} }
if len(blockDevices) > 0 { if len(blockDevices) > 0 {
runOpts.BlockDevices = make([]ec2.BlockDeviceMapping, len(blockDevices)) runOpts.BlockDeviceMappings = make([]ec2.BlockDeviceMapping, len(blockDevices))
for i, v := range blockDevices { for i, v := range blockDevices {
bd := v.(map[string]interface{}) bd := v.(map[string]interface{})
runOpts.BlockDevices[i].DeviceName = bd["device_name"].(string) runOpts.BlockDeviceMappings[i].DeviceName = aws.String(bd["device_name"].(string))
runOpts.BlockDevices[i].VolumeType = bd["volume_type"].(string) runOpts.BlockDeviceMappings[i].EBS = &ec2.EBSBlockDevice{
runOpts.BlockDevices[i].VolumeSize = int64(bd["volume_size"].(int)) VolumeType: aws.String(bd["volume_type"].(string)),
runOpts.BlockDevices[i].DeleteOnTermination = bd["delete_on_termination"].(bool) VolumeSize: aws.Integer(bd["volume_size"].(int)),
if v, ok := bd["virtual_name"].(string); ok { DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
runOpts.BlockDevices[i].VirtualName = v
} }
if v, ok := bd["snapshot_id"].(string); ok {
runOpts.BlockDevices[i].SnapshotId = v if v, ok := bd["virtual_name"].(string); ok {
runOpts.BlockDeviceMappings[i].VirtualName = aws.String(v)
}
if v, ok := bd["snapshot_id"].(string); ok && v != "" {
runOpts.BlockDeviceMappings[i].EBS.SnapshotID = aws.String(v)
} }
if v, ok := bd["encrypted"].(bool); ok { if v, ok := bd["encrypted"].(bool); ok {
runOpts.BlockDevices[i].Encrypted = v runOpts.BlockDeviceMappings[i].EBS.Encrypted = aws.Boolean(v)
}
if v, ok := bd["iops"].(int); ok && v > 0 {
runOpts.BlockDeviceMappings[i].EBS.IOPS = aws.Integer(v)
} }
} }
} }
@ -324,21 +390,21 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
} }
instance := &runResp.Instances[0] instance := &runResp.Instances[0]
log.Printf("[INFO] Instance ID: %s", instance.InstanceId) log.Printf("[INFO] Instance ID: %s", *instance.InstanceID)
// Store the resulting ID so we can look this up later // Store the resulting ID so we can look this up later
d.SetId(instance.InstanceId) d.SetId(*instance.InstanceID)
// Wait for the instance to become running so we can get some attributes // Wait for the instance to become running so we can get some attributes
// that aren't available until later. // that aren't available until later.
log.Printf( log.Printf(
"[DEBUG] Waiting for instance (%s) to become running", "[DEBUG] Waiting for instance (%s) to become running",
instance.InstanceId) *instance.InstanceID)
stateConf := &resource.StateChangeConf{ stateConf := &resource.StateChangeConf{
Pending: []string{"pending"}, Pending: []string{"pending"},
Target: "running", Target: "running",
Refresh: InstanceStateRefreshFunc(ec2conn, instance.InstanceId), Refresh: InstanceStateRefreshFunc(ec2conn, *instance.InstanceID),
Timeout: 10 * time.Minute, Timeout: 10 * time.Minute,
Delay: 10 * time.Second, Delay: 10 * time.Second,
MinTimeout: 3 * time.Second, MinTimeout: 3 * time.Second,
@ -348,16 +414,18 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
if err != nil { if err != nil {
return fmt.Errorf( return fmt.Errorf(
"Error waiting for instance (%s) to become ready: %s", "Error waiting for instance (%s) to become ready: %s",
instance.InstanceId, err) *instance.InstanceID, err)
} }
instance = instanceRaw.(*ec2.Instance) instance = instanceRaw.(*ec2.Instance)
// Initialize the connection info // Initialize the connection info
d.SetConnInfo(map[string]string{ if instance.PublicIPAddress != nil {
"type": "ssh", d.SetConnInfo(map[string]string{
"host": instance.PublicIpAddress, "type": "ssh",
}) "host": *instance.PublicIPAddress,
})
}
// Set our attributes // Set our attributes
if err := resourceAwsInstanceRead(d, meta); err != nil { if err := resourceAwsInstanceRead(d, meta); err != nil {
@ -369,13 +437,15 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
} }
func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
resp, err := ec2conn.Instances([]string{d.Id()}, ec2.NewFilter()) resp, err := ec2conn.DescribeInstances(&ec2.DescribeInstancesRequest{
InstanceIDs: []string{d.Id()},
})
if err != nil { if err != nil {
// If the instance was not found, return nil so that we can show // If the instance was not found, return nil so that we can show
// that the instance is gone. // that the instance is gone.
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" { if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
d.SetId("") d.SetId("")
return nil return nil
} }
@ -393,28 +463,33 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
instance := &resp.Reservations[0].Instances[0] instance := &resp.Reservations[0].Instances[0]
// If the instance is terminated, then it is gone // If the instance is terminated, then it is gone
if instance.State.Name == "terminated" { if *instance.State.Name == "terminated" {
d.SetId("") d.SetId("")
return nil return nil
} }
d.Set("availability_zone", instance.AvailZone) d.Set("availability_zone", instance.Placement.AvailabilityZone)
d.Set("key_name", instance.KeyName) d.Set("key_name", instance.KeyName)
d.Set("public_dns", instance.DNSName) d.Set("public_dns", instance.PublicDNSName)
d.Set("public_ip", instance.PublicIpAddress) d.Set("public_ip", instance.PublicIPAddress)
d.Set("private_dns", instance.PrivateDNSName) d.Set("private_dns", instance.PrivateDNSName)
d.Set("private_ip", instance.PrivateIpAddress) d.Set("private_ip", instance.PrivateIPAddress)
d.Set("subnet_id", instance.SubnetId) d.Set("subnet_id", instance.SubnetID)
d.Set("ebs_optimized", instance.EbsOptimized) if len(instance.NetworkInterfaces) > 0 {
d.Set("tags", tagsToMap(instance.Tags)) d.Set("subnet_id", instance.NetworkInterfaces[0].SubnetID)
d.Set("tenancy", instance.Tenancy) } else {
d.Set("subnet_id", instance.SubnetID)
}
d.Set("ebs_optimized", instance.EBSOptimized)
d.Set("tags", tagsToMapSDK(instance.Tags))
d.Set("tenancy", instance.Placement.Tenancy)
// Determine whether we're referring to security groups with // Determine whether we're referring to security groups with
// IDs or names. We use a heuristic to figure this out. By default, // IDs or names. We use a heuristic to figure this out. By default,
// we use IDs if we're in a VPC. However, if we previously had an // we use IDs if we're in a VPC. However, if we previously had an
// all-name list of security groups, we use names. Or, if we had any // all-name list of security groups, we use names. Or, if we had any
// IDs, we use IDs. // IDs, we use IDs.
useID := instance.SubnetId != "" useID := *instance.SubnetID != ""
if v := d.Get("security_groups"); v != nil { if v := d.Get("security_groups"); v != nil {
match := false match := false
for _, v := range v.(*schema.Set).List() { for _, v := range v.(*schema.Set).List() {
@ -431,24 +506,26 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
sgs := make([]string, len(instance.SecurityGroups)) sgs := make([]string, len(instance.SecurityGroups))
for i, sg := range instance.SecurityGroups { for i, sg := range instance.SecurityGroups {
if useID { if useID {
sgs[i] = sg.Id sgs[i] = *sg.GroupID
} else { } else {
sgs[i] = sg.Name sgs[i] = *sg.GroupName
} }
} }
d.Set("security_groups", sgs) d.Set("security_groups", sgs)
blockDevices := make(map[string]ec2.BlockDevice) blockDevices := make(map[string]ec2.InstanceBlockDeviceMapping)
for _, bd := range instance.BlockDevices { for _, bd := range instance.BlockDeviceMappings {
blockDevices[bd.VolumeId] = bd blockDevices[*bd.EBS.VolumeID] = bd
} }
volIDs := make([]string, 0, len(blockDevices)) volIDs := make([]string, 0, len(blockDevices))
for volID := range blockDevices { for _, vol := range blockDevices {
volIDs = append(volIDs, volID) volIDs = append(volIDs, *vol.EBS.VolumeID)
} }
volResp, err := ec2conn.Volumes(volIDs, ec2.NewFilter()) volResp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesRequest{
VolumeIDs: volIDs,
})
if err != nil { if err != nil {
return err return err
} }
@ -456,27 +533,25 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
nonRootBlockDevices := make([]map[string]interface{}, 0) nonRootBlockDevices := make([]map[string]interface{}, 0)
rootBlockDevice := make([]interface{}, 0, 1) rootBlockDevice := make([]interface{}, 0, 1)
for _, vol := range volResp.Volumes { for _, vol := range volResp.Volumes {
volSize, err := strconv.Atoi(vol.Size)
if err != nil {
return err
}
blockDevice := make(map[string]interface{}) blockDevice := make(map[string]interface{})
blockDevice["device_name"] = blockDevices[vol.VolumeId].DeviceName blockDevice["device_name"] = *blockDevices[*vol.VolumeID].DeviceName
blockDevice["volume_type"] = vol.VolumeType blockDevice["volume_type"] = *vol.VolumeType
blockDevice["volume_size"] = volSize blockDevice["volume_size"] = *vol.Size
if vol.IOPS != nil {
blockDevice["iops"] = *vol.IOPS
}
blockDevice["delete_on_termination"] = blockDevice["delete_on_termination"] =
blockDevices[vol.VolumeId].DeleteOnTermination *blockDevices[*vol.VolumeID].EBS.DeleteOnTermination
// If this is the root device, save it. We stop here since we // If this is the root device, save it. We stop here since we
// can't put invalid keys into this map. // can't put invalid keys into this map.
if blockDevice["device_name"] == instance.RootDeviceName { if blockDevice["device_name"] == *instance.RootDeviceName {
rootBlockDevice = []interface{}{blockDevice} rootBlockDevice = []interface{}{blockDevice}
continue continue
} }
blockDevice["snapshot_id"] = vol.SnapshotId blockDevice["snapshot_id"] = *vol.SnapshotID
blockDevice["encrypted"] = vol.Encrypted blockDevice["encrypted"] = *vol.Encrypted
nonRootBlockDevices = append(nonRootBlockDevices, blockDevice) nonRootBlockDevices = append(nonRootBlockDevices, blockDevice)
} }
d.Set("block_device", nonRootBlockDevices) d.Set("block_device", nonRootBlockDevices)
@ -486,21 +561,25 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
} }
func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
opts := new(ec2.ModifyInstance) opts := new(ec2.ModifyInstanceAttributeRequest)
opts.SetSourceDestCheck = true
opts.SourceDestCheck = d.Get("source_dest_check").(bool)
log.Printf("[INFO] Modifying instance %s: %#v", d.Id(), opts) log.Printf("[INFO] Modifying instance %s: %#v", d.Id(), opts)
if _, err := ec2conn.ModifyInstance(d.Id(), opts); err != nil { err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeRequest{
InstanceID: aws.String(d.Id()),
SourceDestCheck: &ec2.AttributeBooleanValue{
Value: aws.Boolean(d.Get("source_dest_check").(bool)),
},
})
if err != nil {
return err return err
} }
// TODO(mitchellh): wait for the attributes we modified to // TODO(mitchellh): wait for the attributes we modified to
// persist the change... // persist the change...
if err := setTags(ec2conn, d); err != nil { if err := setTagsSDK(ec2conn, d); err != nil {
return err return err
} else { } else {
d.SetPartial("tags") d.SetPartial("tags")
@ -510,10 +589,13 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
} }
func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error { func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
log.Printf("[INFO] Terminating instance: %s", d.Id()) log.Printf("[INFO] Terminating instance: %s", d.Id())
if _, err := ec2conn.TerminateInstances([]string{d.Id()}); err != nil { req := &ec2.TerminateInstancesRequest{
InstanceIDs: []string{d.Id()},
}
if _, err := ec2conn.TerminateInstances(req); err != nil {
return fmt.Errorf("Error terminating instance: %s", err) return fmt.Errorf("Error terminating instance: %s", err)
} }
@ -545,9 +627,11 @@ func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error {
// an EC2 instance. // an EC2 instance.
func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc { func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
resp, err := conn.Instances([]string{instanceID}, ec2.NewFilter()) resp, err := conn.DescribeInstances(&ec2.DescribeInstancesRequest{
InstanceIDs: []string{instanceID},
})
if err != nil { if err != nil {
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" { if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
// Set this to nil as if we didn't find anything. // Set this to nil as if we didn't find anything.
resp = nil resp = nil
} else { } else {
@ -563,7 +647,7 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRe
} }
i := &resp.Reservations[0].Instances[0] i := &resp.Reservations[0].Instances[0]
return i, i.State.Name, nil return i, *i.State.Name, nil
} }
} }

View File

@ -5,24 +5,25 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/goamz/ec2"
) )
func TestAccAWSInstance_normal(t *testing.T) { func TestAccAWSInstance_normal(t *testing.T) {
var v ec2.Instance var v ec2.Instance
testCheck := func(*terraform.State) error { testCheck := func(*terraform.State) error {
if v.AvailZone != "us-west-2a" { if *v.Placement.AvailabilityZone != "us-west-2a" {
return fmt.Errorf("bad availability zone: %#v", v.AvailZone) return fmt.Errorf("bad availability zone: %#v", *v.Placement.AvailabilityZone)
} }
if len(v.SecurityGroups) == 0 { if len(v.SecurityGroups) == 0 {
return fmt.Errorf("no security groups: %#v", v.SecurityGroups) return fmt.Errorf("no security groups: %#v", v.SecurityGroups)
} }
if v.SecurityGroups[0].Name != "tf_test_foo" { if *v.SecurityGroups[0].GroupName != "tf_test_foo" {
return fmt.Errorf("no security groups: %#v", v.SecurityGroups) return fmt.Errorf("no security groups: %#v", v.SecurityGroups)
} }
@ -73,9 +74,9 @@ func TestAccAWSInstance_blockDevices(t *testing.T) {
return func(*terraform.State) error { return func(*terraform.State) error {
// Map out the block devices by name, which should be unique. // Map out the block devices by name, which should be unique.
blockDevices := make(map[string]ec2.BlockDevice) blockDevices := make(map[string]ec2.InstanceBlockDeviceMapping)
for _, blockDevice := range v.BlockDevices { for _, blockDevice := range v.BlockDeviceMappings {
blockDevices[blockDevice.DeviceName] = blockDevice blockDevices[*blockDevice.DeviceName] = blockDevice
} }
// Check if the root block device exists. // Check if the root block device exists.
@ -88,6 +89,11 @@ func TestAccAWSInstance_blockDevices(t *testing.T) {
fmt.Errorf("block device doesn't exist: /dev/sdb") fmt.Errorf("block device doesn't exist: /dev/sdb")
} }
// Check if the third block device exists.
if _, ok := blockDevices["/dev/sdc"]; !ok {
fmt.Errorf("block device doesn't exist: /dev/sdc")
}
return nil return nil
} }
} }
@ -114,11 +120,22 @@ func TestAccAWSInstance_blockDevices(t *testing.T) {
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_instance.foo", "root_block_device.0.volume_type", "gp2"), "aws_instance.foo", "root_block_device.0.volume_type", "gp2"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_instance.foo", "block_device.#", "1"), "aws_instance.foo", "block_device.#", "2"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_instance.foo", "block_device.172787947.device_name", "/dev/sdb"), "aws_instance.foo", "block_device.172787947.device_name", "/dev/sdb"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_instance.foo", "block_device.172787947.volume_size", "9"), "aws_instance.foo", "block_device.172787947.volume_size", "9"),
resource.TestCheckResourceAttr(
"aws_instance.foo", "block_device.172787947.iops", "0"),
// Check provisioned SSD device
resource.TestCheckResourceAttr(
"aws_instance.foo", "block_device.3336996981.volume_type", "io1"),
resource.TestCheckResourceAttr(
"aws_instance.foo", "block_device.3336996981.device_name", "/dev/sdc"),
resource.TestCheckResourceAttr(
"aws_instance.foo", "block_device.3336996981.volume_size", "10"),
resource.TestCheckResourceAttr(
"aws_instance.foo", "block_device.3336996981.iops", "100"),
testCheck(), testCheck(),
), ),
}, },
@ -131,8 +148,8 @@ func TestAccAWSInstance_sourceDestCheck(t *testing.T) {
testCheck := func(enabled bool) resource.TestCheckFunc { testCheck := func(enabled bool) resource.TestCheckFunc {
return func(*terraform.State) error { return func(*terraform.State) error {
if v.SourceDestCheck != enabled { if *v.SourceDestCheck != enabled {
return fmt.Errorf("bad source_dest_check: %#v", v.SourceDestCheck) return fmt.Errorf("bad source_dest_check: %#v", *v.SourceDestCheck)
} }
return nil return nil
@ -190,7 +207,7 @@ func TestAccAWSInstance_vpc(t *testing.T) {
}) })
} }
func TestAccInstance_tags(t *testing.T) { func TestAccAWSInstance_tags(t *testing.T) {
var v ec2.Instance var v ec2.Instance
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
@ -202,9 +219,9 @@ func TestAccInstance_tags(t *testing.T) {
Config: testAccCheckInstanceConfigTags, Config: testAccCheckInstanceConfigTags,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v), testAccCheckInstanceExists("aws_instance.foo", &v),
testAccCheckTags(&v.Tags, "foo", "bar"), testAccCheckTagsSDK(&v.Tags, "foo", "bar"),
// Guard against regression of https://github.com/hashicorp/terraform/issues/914 // Guard against regression of https://github.com/hashicorp/terraform/issues/914
testAccCheckTags(&v.Tags, "#", ""), testAccCheckTagsSDK(&v.Tags, "#", ""),
), ),
}, },
@ -212,21 +229,21 @@ func TestAccInstance_tags(t *testing.T) {
Config: testAccCheckInstanceConfigTagsUpdate, Config: testAccCheckInstanceConfigTagsUpdate,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v), testAccCheckInstanceExists("aws_instance.foo", &v),
testAccCheckTags(&v.Tags, "foo", ""), testAccCheckTagsSDK(&v.Tags, "foo", ""),
testAccCheckTags(&v.Tags, "bar", "baz"), testAccCheckTagsSDK(&v.Tags, "bar", "baz"),
), ),
}, },
}, },
}) })
} }
func TestAccInstance_privateIP(t *testing.T) { func TestAccAWSInstance_privateIP(t *testing.T) {
var v ec2.Instance var v ec2.Instance
testCheckPrivateIP := func() resource.TestCheckFunc { testCheckPrivateIP := func() resource.TestCheckFunc {
return func(*terraform.State) error { return func(*terraform.State) error {
if v.PrivateIpAddress != "10.1.1.42" { if *v.PrivateIPAddress != "10.1.1.42" {
return fmt.Errorf("bad private IP: %s", v.PrivateIpAddress) return fmt.Errorf("bad private IP: %s", *v.PrivateIPAddress)
} }
return nil return nil
@ -249,13 +266,13 @@ func TestAccInstance_privateIP(t *testing.T) {
}) })
} }
func TestAccInstance_associatePublicIPAndPrivateIP(t *testing.T) { func TestAccAWSInstance_associatePublicIPAndPrivateIP(t *testing.T) {
var v ec2.Instance var v ec2.Instance
testCheckPrivateIP := func() resource.TestCheckFunc { testCheckPrivateIP := func() resource.TestCheckFunc {
return func(*terraform.State) error { return func(*terraform.State) error {
if v.PrivateIpAddress != "10.1.1.42" { if *v.PrivateIPAddress != "10.1.1.42" {
return fmt.Errorf("bad private IP: %s", v.PrivateIpAddress) return fmt.Errorf("bad private IP: %s", *v.PrivateIPAddress)
} }
return nil return nil
@ -279,7 +296,7 @@ func TestAccInstance_associatePublicIPAndPrivateIP(t *testing.T) {
} }
func testAccCheckInstanceDestroy(s *terraform.State) error { func testAccCheckInstanceDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_instance" { if rs.Type != "aws_instance" {
@ -287,8 +304,9 @@ func testAccCheckInstanceDestroy(s *terraform.State) error {
} }
// Try to find the resource // Try to find the resource
resp, err := conn.Instances( resp, err := conn.DescribeInstances(&ec2.DescribeInstancesRequest{
[]string{rs.Primary.ID}, ec2.NewFilter()) InstanceIDs: []string{rs.Primary.ID},
})
if err == nil { if err == nil {
if len(resp.Reservations) > 0 { if len(resp.Reservations) > 0 {
return fmt.Errorf("still exist.") return fmt.Errorf("still exist.")
@ -298,7 +316,7 @@ func testAccCheckInstanceDestroy(s *terraform.State) error {
} }
// Verify the error is what we want // Verify the error is what we want
ec2err, ok := err.(*ec2.Error) ec2err, ok := err.(aws.APIError)
if !ok { if !ok {
return err return err
} }
@ -321,9 +339,10 @@ func testAccCheckInstanceExists(n string, i *ec2.Instance) resource.TestCheckFun
return fmt.Errorf("No ID is set") return fmt.Errorf("No ID is set")
} }
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
resp, err := conn.Instances( resp, err := conn.DescribeInstances(&ec2.DescribeInstancesRequest{
[]string{rs.Primary.ID}, ec2.NewFilter()) InstanceIDs: []string{rs.Primary.ID},
})
if err != nil { if err != nil {
return err return err
} }
@ -391,6 +410,12 @@ resource "aws_instance" "foo" {
device_name = "/dev/sdb" device_name = "/dev/sdb"
volume_size = 9 volume_size = 9
} }
block_device {
device_name = "/dev/sdc"
volume_size = 10
volume_type = "io1"
iops = 100
}
} }
` `

View File

@ -5,9 +5,10 @@ import (
"log" "log"
"time" "time"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
) )
func resourceAwsInternetGateway() *schema.Resource { func resourceAwsInternetGateway() *schema.Resource {
@ -28,7 +29,7 @@ func resourceAwsInternetGateway() *schema.Resource {
} }
func resourceAwsInternetGatewayCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsInternetGatewayCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
// Create the gateway // Create the gateway
log.Printf("[DEBUG] Creating internet gateway") log.Printf("[DEBUG] Creating internet gateway")
@ -38,16 +39,21 @@ func resourceAwsInternetGatewayCreate(d *schema.ResourceData, meta interface{})
} }
// Get the ID and store it // Get the ID and store it
ig := &resp.InternetGateway ig := resp.InternetGateway
d.SetId(ig.InternetGatewayId) d.SetId(*ig.InternetGatewayID)
log.Printf("[INFO] InternetGateway ID: %s", d.Id()) log.Printf("[INFO] InternetGateway ID: %s", d.Id())
err = setTagsSDK(ec2conn, d)
if err != nil {
return err
}
// Attach the new gateway to the correct vpc // Attach the new gateway to the correct vpc
return resourceAwsInternetGatewayAttach(d, meta) return resourceAwsInternetGatewayAttach(d, meta)
} }
func resourceAwsInternetGatewayRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsInternetGatewayRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
igRaw, _, err := IGStateRefreshFunc(ec2conn, d.Id())() igRaw, _, err := IGStateRefreshFunc(ec2conn, d.Id())()
if err != nil { if err != nil {
@ -64,10 +70,10 @@ func resourceAwsInternetGatewayRead(d *schema.ResourceData, meta interface{}) er
// Gateway exists but not attached to the VPC // Gateway exists but not attached to the VPC
d.Set("vpc_id", "") d.Set("vpc_id", "")
} else { } else {
d.Set("vpc_id", ig.Attachments[0].VpcId) d.Set("vpc_id", ig.Attachments[0].VPCID)
} }
d.Set("tags", tagsToMap(ig.Tags)) d.Set("tags", tagsToMapSDK(ig.Tags))
return nil return nil
} }
@ -85,9 +91,9 @@ func resourceAwsInternetGatewayUpdate(d *schema.ResourceData, meta interface{})
} }
} }
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
if err := setTags(ec2conn, d); err != nil { if err := setTagsSDK(ec2conn, d); err != nil {
return err return err
} }
@ -97,7 +103,7 @@ func resourceAwsInternetGatewayUpdate(d *schema.ResourceData, meta interface{})
} }
func resourceAwsInternetGatewayDelete(d *schema.ResourceData, meta interface{}) error { func resourceAwsInternetGatewayDelete(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
// Detach if it is attached // Detach if it is attached
if err := resourceAwsInternetGatewayDetach(d, meta); err != nil { if err := resourceAwsInternetGatewayDetach(d, meta); err != nil {
@ -107,12 +113,14 @@ func resourceAwsInternetGatewayDelete(d *schema.ResourceData, meta interface{})
log.Printf("[INFO] Deleting Internet Gateway: %s", d.Id()) log.Printf("[INFO] Deleting Internet Gateway: %s", d.Id())
return resource.Retry(5*time.Minute, func() error { return resource.Retry(5*time.Minute, func() error {
_, err := ec2conn.DeleteInternetGateway(d.Id()) err := ec2conn.DeleteInternetGateway(&ec2.DeleteInternetGatewayRequest{
InternetGatewayID: aws.String(d.Id()),
})
if err == nil { if err == nil {
return nil return nil
} }
ec2err, ok := err.(*ec2.Error) ec2err, ok := err.(aws.APIError)
if !ok { if !ok {
return err return err
} }
@ -129,7 +137,7 @@ func resourceAwsInternetGatewayDelete(d *schema.ResourceData, meta interface{})
} }
func resourceAwsInternetGatewayAttach(d *schema.ResourceData, meta interface{}) error { func resourceAwsInternetGatewayAttach(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
if d.Get("vpc_id").(string) == "" { if d.Get("vpc_id").(string) == "" {
log.Printf( log.Printf(
@ -143,7 +151,10 @@ func resourceAwsInternetGatewayAttach(d *schema.ResourceData, meta interface{})
d.Id(), d.Id(),
d.Get("vpc_id").(string)) d.Get("vpc_id").(string))
_, err := ec2conn.AttachInternetGateway(d.Id(), d.Get("vpc_id").(string)) err := ec2conn.AttachInternetGateway(&ec2.AttachInternetGatewayRequest{
InternetGatewayID: aws.String(d.Id()),
VPCID: aws.String(d.Get("vpc_id").(string)),
})
if err != nil { if err != nil {
return err return err
} }
@ -171,7 +182,7 @@ func resourceAwsInternetGatewayAttach(d *schema.ResourceData, meta interface{})
} }
func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{}) error { func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
// Get the old VPC ID to detach from // Get the old VPC ID to detach from
vpcID, _ := d.GetChange("vpc_id") vpcID, _ := d.GetChange("vpc_id")
@ -189,9 +200,12 @@ func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{})
vpcID.(string)) vpcID.(string))
wait := true wait := true
_, err := ec2conn.DetachInternetGateway(d.Id(), vpcID.(string)) err := ec2conn.DetachInternetGateway(&ec2.DetachInternetGatewayRequest{
InternetGatewayID: aws.String(d.Id()),
VPCID: aws.String(vpcID.(string)),
})
if err != nil { if err != nil {
ec2err, ok := err.(*ec2.Error) ec2err, ok := err.(aws.APIError)
if ok { if ok {
if ec2err.Code == "InvalidInternetGatewayID.NotFound" { if ec2err.Code == "InvalidInternetGatewayID.NotFound" {
err = nil err = nil
@ -232,9 +246,11 @@ func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{})
// an internet gateway. // an internet gateway.
func IGStateRefreshFunc(ec2conn *ec2.EC2, id string) resource.StateRefreshFunc { func IGStateRefreshFunc(ec2conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
resp, err := ec2conn.DescribeInternetGateways([]string{id}, ec2.NewFilter()) resp, err := ec2conn.DescribeInternetGateways(&ec2.DescribeInternetGatewaysRequest{
InternetGatewayIDs: []string{id},
})
if err != nil { if err != nil {
ec2err, ok := err.(*ec2.Error) ec2err, ok := err.(aws.APIError)
if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" { if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
resp = nil resp = nil
} else { } else {
@ -256,16 +272,18 @@ func IGStateRefreshFunc(ec2conn *ec2.EC2, id string) resource.StateRefreshFunc {
// IGAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used // IGAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used
// watch the state of an internet gateway's attachment. // watch the state of an internet gateway's attachment.
func IGAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string) resource.StateRefreshFunc { func IGAttachStateRefreshFunc(ec2conn *ec2.EC2, id string, expected string) resource.StateRefreshFunc {
var start time.Time var start time.Time
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
if start.IsZero() { if start.IsZero() {
start = time.Now() start = time.Now()
} }
resp, err := conn.DescribeInternetGateways([]string{id}, ec2.NewFilter()) resp, err := ec2conn.DescribeInternetGateways(&ec2.DescribeInternetGatewaysRequest{
InternetGatewayIDs: []string{id},
})
if err != nil { if err != nil {
ec2err, ok := err.(*ec2.Error) ec2err, ok := err.(aws.APIError)
if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" { if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
resp = nil resp = nil
} else { } else {
@ -291,6 +309,6 @@ func IGAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string) resourc
return ig, "detached", nil return ig, "detached", nil
} }
return ig, ig.Attachments[0].State, nil return ig, *ig.Attachments[0].State, nil
} }
} }

View File

@ -4,9 +4,10 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/goamz/ec2"
) )
func TestAccAWSInternetGateway(t *testing.T) { func TestAccAWSInternetGateway(t *testing.T) {
@ -20,8 +21,8 @@ func TestAccAWSInternetGateway(t *testing.T) {
return fmt.Errorf("IG B is not attached") return fmt.Errorf("IG B is not attached")
} }
id1 := v.Attachments[0].VpcId id1 := v.Attachments[0].VPCID
id2 := v2.Attachments[0].VpcId id2 := v2.Attachments[0].VPCID
if id1 == id2 { if id1 == id2 {
return fmt.Errorf("Both attachment IDs are the same") return fmt.Errorf("Both attachment IDs are the same")
} }
@ -97,6 +98,7 @@ func TestAccInternetGateway_tags(t *testing.T) {
Config: testAccCheckInternetGatewayConfigTags, Config: testAccCheckInternetGatewayConfigTags,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckInternetGatewayExists("aws_internet_gateway.foo", &v), testAccCheckInternetGatewayExists("aws_internet_gateway.foo", &v),
testAccCheckTagsSDK(&v.Tags, "foo", "bar"),
), ),
}, },
@ -104,8 +106,8 @@ func TestAccInternetGateway_tags(t *testing.T) {
Config: testAccCheckInternetGatewayConfigTagsUpdate, Config: testAccCheckInternetGatewayConfigTagsUpdate,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckInternetGatewayExists("aws_internet_gateway.foo", &v), testAccCheckInternetGatewayExists("aws_internet_gateway.foo", &v),
testAccCheckTags(&v.Tags, "foo", ""), testAccCheckTagsSDK(&v.Tags, "foo", ""),
testAccCheckTags(&v.Tags, "bar", "baz"), testAccCheckTagsSDK(&v.Tags, "bar", "baz"),
), ),
}, },
}, },
@ -113,7 +115,7 @@ func TestAccInternetGateway_tags(t *testing.T) {
} }
func testAccCheckInternetGatewayDestroy(s *terraform.State) error { func testAccCheckInternetGatewayDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_internet_gateway" { if rs.Type != "aws_internet_gateway" {
@ -121,8 +123,9 @@ func testAccCheckInternetGatewayDestroy(s *terraform.State) error {
} }
// Try to find the resource // Try to find the resource
resp, err := conn.DescribeInternetGateways( resp, err := ec2conn.DescribeInternetGateways(&ec2.DescribeInternetGatewaysRequest{
[]string{rs.Primary.ID}, ec2.NewFilter()) InternetGatewayIDs: []string{rs.Primary.ID},
})
if err == nil { if err == nil {
if len(resp.InternetGateways) > 0 { if len(resp.InternetGateways) > 0 {
return fmt.Errorf("still exists") return fmt.Errorf("still exists")
@ -132,7 +135,7 @@ func testAccCheckInternetGatewayDestroy(s *terraform.State) error {
} }
// Verify the error is what we want // Verify the error is what we want
ec2err, ok := err.(*ec2.Error) ec2err, ok := err.(aws.APIError)
if !ok { if !ok {
return err return err
} }
@ -155,9 +158,10 @@ func testAccCheckInternetGatewayExists(n string, ig *ec2.InternetGateway) resour
return fmt.Errorf("No ID is set") return fmt.Errorf("No ID is set")
} }
conn := testAccProvider.Meta().(*AWSClient).ec2conn ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
resp, err := conn.DescribeInternetGateways( resp, err := ec2conn.DescribeInternetGateways(&ec2.DescribeInternetGatewaysRequest{
[]string{rs.Primary.ID}, ec2.NewFilter()) InternetGatewayIDs: []string{rs.Primary.ID},
})
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,9 +1,13 @@
package aws package aws
import ( import (
"encoding/base64"
"fmt" "fmt"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
) )
func resourceAwsKeyPair() *schema.Resource { func resourceAwsKeyPair() *schema.Resource {
@ -33,42 +37,50 @@ func resourceAwsKeyPair() *schema.Resource {
} }
func resourceAwsKeyPairCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsKeyPairCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
keyName := d.Get("key_name").(string) keyName := d.Get("key_name").(string)
publicKey := d.Get("public_key").(string) publicKey := d.Get("public_key").(string)
resp, err := ec2conn.ImportKeyPair(keyName, publicKey) req := &ec2.ImportKeyPairRequest{
KeyName: aws.String(keyName),
PublicKeyMaterial: []byte(base64.StdEncoding.EncodeToString([]byte(publicKey))),
}
resp, err := ec2conn.ImportKeyPair(req)
if err != nil { if err != nil {
return fmt.Errorf("Error import KeyPair: %s", err) return fmt.Errorf("Error import KeyPair: %s", err)
} }
d.SetId(resp.KeyName) d.SetId(*resp.KeyName)
return nil return nil
} }
func resourceAwsKeyPairRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsKeyPairRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
resp, err := ec2conn.KeyPairs([]string{d.Id()}, nil) req := &ec2.DescribeKeyPairsRequest{
KeyNames: []string{d.Id()},
}
resp, err := ec2conn.DescribeKeyPairs(req)
if err != nil { if err != nil {
return fmt.Errorf("Error retrieving KeyPair: %s", err) return fmt.Errorf("Error retrieving KeyPair: %s", err)
} }
for _, keyPair := range resp.Keys { for _, keyPair := range resp.KeyPairs {
if keyPair.Name == d.Id() { if *keyPair.KeyName == d.Id() {
d.Set("key_name", keyPair.Name) d.Set("key_name", keyPair.KeyName)
d.Set("fingerprint", keyPair.Fingerprint) d.Set("fingerprint", keyPair.KeyFingerprint)
return nil return nil
} }
} }
return fmt.Errorf("Unable to find key pair within: %#v", resp.Keys) return fmt.Errorf("Unable to find key pair within: %#v", resp.KeyPairs)
} }
func resourceAwsKeyPairDelete(d *schema.ResourceData, meta interface{}) error { func resourceAwsKeyPairDelete(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
_, err := ec2conn.DeleteKeyPair(d.Id()) err := ec2conn.DeleteKeyPair(&ec2.DeleteKeyPairRequest{
KeyName: aws.String(d.Id()),
})
return err return err
} }

View File

@ -4,13 +4,14 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/goamz/ec2"
) )
func TestAccAWSKeyPair_normal(t *testing.T) { func TestAccAWSKeyPair_normal(t *testing.T) {
var conf ec2.KeyPair var conf ec2.KeyPairInfo
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -29,7 +30,7 @@ func TestAccAWSKeyPair_normal(t *testing.T) {
} }
func testAccCheckAWSKeyPairDestroy(s *terraform.State) error { func testAccCheckAWSKeyPairDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_key_pair" { if rs.Type != "aws_key_pair" {
@ -37,17 +38,18 @@ func testAccCheckAWSKeyPairDestroy(s *terraform.State) error {
} }
// Try to find key pair // Try to find key pair
resp, err := conn.KeyPairs( resp, err := ec2conn.DescribeKeyPairs(&ec2.DescribeKeyPairsRequest{
[]string{rs.Primary.ID}, nil) KeyNames: []string{rs.Primary.ID},
})
if err == nil { if err == nil {
if len(resp.Keys) > 0 { if len(resp.KeyPairs) > 0 {
return fmt.Errorf("still exist.") return fmt.Errorf("still exist.")
} }
return nil return nil
} }
// Verify the error is what we want // Verify the error is what we want
ec2err, ok := err.(*ec2.Error) ec2err, ok := err.(aws.APIError)
if !ok { if !ok {
return err return err
} }
@ -59,16 +61,16 @@ func testAccCheckAWSKeyPairDestroy(s *terraform.State) error {
return nil return nil
} }
func testAccCheckAWSKeyPairFingerprint(expectedFingerprint string, conf *ec2.KeyPair) resource.TestCheckFunc { func testAccCheckAWSKeyPairFingerprint(expectedFingerprint string, conf *ec2.KeyPairInfo) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
if conf.Fingerprint != expectedFingerprint { if *conf.KeyFingerprint != expectedFingerprint {
return fmt.Errorf("incorrect fingerprint. expected %s, got %s", expectedFingerprint, conf.Fingerprint) return fmt.Errorf("incorrect fingerprint. expected %s, got %s", expectedFingerprint, *conf.KeyFingerprint)
} }
return nil return nil
} }
} }
func testAccCheckAWSKeyPairExists(n string, res *ec2.KeyPair) resource.TestCheckFunc { func testAccCheckAWSKeyPairExists(n string, res *ec2.KeyPairInfo) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n] rs, ok := s.RootModule().Resources[n]
if !ok { if !ok {
@ -79,18 +81,20 @@ func testAccCheckAWSKeyPairExists(n string, res *ec2.KeyPair) resource.TestCheck
return fmt.Errorf("No KeyPair name is set") return fmt.Errorf("No KeyPair name is set")
} }
conn := testAccProvider.Meta().(*AWSClient).ec2conn ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
resp, err := conn.KeyPairs( resp, err := ec2conn.DescribeKeyPairs(&ec2.DescribeKeyPairsRequest{
[]string{rs.Primary.ID}, nil) KeyNames: []string{rs.Primary.ID},
})
if err != nil { if err != nil {
return err return err
} }
if len(resp.Keys) != 1 || if len(resp.KeyPairs) != 1 ||
resp.Keys[0].Name != rs.Primary.ID { *resp.KeyPairs[0].KeyName != rs.Primary.ID {
return fmt.Errorf("KeyPair not found") return fmt.Errorf("KeyPair not found")
} }
*res = resp.Keys[0]
*res = resp.KeyPairs[0]
return nil return nil
} }

View File

@ -4,8 +4,9 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
) )
func resourceAwsMainRouteTableAssociation() *schema.Resource { func resourceAwsMainRouteTableAssociation() *schema.Resource {
@ -39,7 +40,7 @@ func resourceAwsMainRouteTableAssociation() *schema.Resource {
} }
func resourceAwsMainRouteTableAssociationCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsMainRouteTableAssociationCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
vpcId := d.Get("vpc_id").(string) vpcId := d.Get("vpc_id").(string)
routeTableId := d.Get("route_table_id").(string) routeTableId := d.Get("route_table_id").(string)
@ -50,23 +51,23 @@ func resourceAwsMainRouteTableAssociationCreate(d *schema.ResourceData, meta int
return err return err
} }
resp, err := ec2conn.ReassociateRouteTable( resp, err := ec2conn.ReplaceRouteTableAssociation(&ec2.ReplaceRouteTableAssociationRequest{
mainAssociation.AssociationId, AssociationID: mainAssociation.RouteTableAssociationID,
routeTableId, RouteTableID: aws.String(routeTableId),
) })
if err != nil { if err != nil {
return err return err
} }
d.Set("original_route_table_id", mainAssociation.RouteTableId) d.Set("original_route_table_id", mainAssociation.RouteTableID)
d.SetId(resp.AssociationId) d.SetId(*resp.NewAssociationID)
log.Printf("[INFO] New main route table association ID: %s", d.Id()) log.Printf("[INFO] New main route table association ID: %s", d.Id())
return nil return nil
} }
func resourceAwsMainRouteTableAssociationRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsMainRouteTableAssociationRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
mainAssociation, err := findMainRouteTableAssociation( mainAssociation, err := findMainRouteTableAssociation(
ec2conn, ec2conn,
@ -75,7 +76,7 @@ func resourceAwsMainRouteTableAssociationRead(d *schema.ResourceData, meta inter
return err return err
} }
if mainAssociation.AssociationId != d.Id() { if *mainAssociation.RouteTableAssociationID != d.Id() {
// It seems it doesn't exist anymore, so clear the ID // It seems it doesn't exist anymore, so clear the ID
d.SetId("") d.SetId("")
} }
@ -87,25 +88,28 @@ func resourceAwsMainRouteTableAssociationRead(d *schema.ResourceData, meta inter
// original_route_table_id - this needs to stay recorded as the AWS-created // original_route_table_id - this needs to stay recorded as the AWS-created
// table from VPC creation. // table from VPC creation.
func resourceAwsMainRouteTableAssociationUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAwsMainRouteTableAssociationUpdate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
vpcId := d.Get("vpc_id").(string) vpcId := d.Get("vpc_id").(string)
routeTableId := d.Get("route_table_id").(string) routeTableId := d.Get("route_table_id").(string)
log.Printf("[INFO] Updating main route table association: %s => %s", vpcId, routeTableId) log.Printf("[INFO] Updating main route table association: %s => %s", vpcId, routeTableId)
resp, err := ec2conn.ReassociateRouteTable(d.Id(), routeTableId) resp, err := ec2conn.ReplaceRouteTableAssociation(&ec2.ReplaceRouteTableAssociationRequest{
AssociationID: aws.String(d.Id()),
RouteTableID: aws.String(routeTableId),
})
if err != nil { if err != nil {
return err return err
} }
d.SetId(resp.AssociationId) d.SetId(*resp.NewAssociationID)
log.Printf("[INFO] New main route table association ID: %s", d.Id()) log.Printf("[INFO] New main route table association ID: %s", d.Id())
return nil return nil
} }
func resourceAwsMainRouteTableAssociationDelete(d *schema.ResourceData, meta interface{}) error { func resourceAwsMainRouteTableAssociationDelete(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
vpcId := d.Get("vpc_id").(string) vpcId := d.Get("vpc_id").(string)
originalRouteTableId := d.Get("original_route_table_id").(string) originalRouteTableId := d.Get("original_route_table_id").(string)
@ -113,12 +117,15 @@ func resourceAwsMainRouteTableAssociationDelete(d *schema.ResourceData, meta int
vpcId, vpcId,
originalRouteTableId) originalRouteTableId)
resp, err := ec2conn.ReassociateRouteTable(d.Id(), originalRouteTableId) resp, err := ec2conn.ReplaceRouteTableAssociation(&ec2.ReplaceRouteTableAssociationRequest{
AssociationID: aws.String(d.Id()),
RouteTableID: aws.String(originalRouteTableId),
})
if err != nil { if err != nil {
return err return err
} }
log.Printf("[INFO] Resulting Association ID: %s", resp.AssociationId) log.Printf("[INFO] Resulting Association ID: %s", *resp.NewAssociationID)
return nil return nil
} }
@ -130,7 +137,7 @@ func findMainRouteTableAssociation(ec2conn *ec2.EC2, vpcId string) (*ec2.RouteTa
} }
for _, a := range mainRouteTable.Associations { for _, a := range mainRouteTable.Associations {
if a.Main { if *a.Main {
return &a, nil return &a, nil
} }
} }
@ -138,10 +145,17 @@ func findMainRouteTableAssociation(ec2conn *ec2.EC2, vpcId string) (*ec2.RouteTa
} }
func findMainRouteTable(ec2conn *ec2.EC2, vpcId string) (*ec2.RouteTable, error) { func findMainRouteTable(ec2conn *ec2.EC2, vpcId string) (*ec2.RouteTable, error) {
filter := ec2.NewFilter() mainFilter := ec2.Filter{
filter.Add("association.main", "true") aws.String("association.main"),
filter.Add("vpc-id", vpcId) []string{"true"},
routeResp, err := ec2conn.DescribeRouteTables(nil, filter) }
vpcFilter := ec2.Filter{
aws.String("vpc-id"),
[]string{vpcId},
}
routeResp, err := ec2conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
Filters: []ec2.Filter{mainFilter, vpcFilter},
})
if err != nil { if err != nil {
return nil, err return nil, err
} else if len(routeResp.RouteTables) != 1 { } else if len(routeResp.RouteTables) != 1 {

View File

@ -65,15 +65,15 @@ func testAccCheckMainRouteTableAssociation(
return fmt.Errorf("Not found: %s", vpcResource) return fmt.Errorf("Not found: %s", vpcResource)
} }
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
mainAssociation, err := findMainRouteTableAssociation(conn, vpc.Primary.ID) mainAssociation, err := findMainRouteTableAssociation(conn, vpc.Primary.ID)
if err != nil { if err != nil {
return err return err
} }
if mainAssociation.AssociationId != rs.Primary.ID { if *mainAssociation.RouteTableAssociationID != rs.Primary.ID {
return fmt.Errorf("Found wrong main association: %s", return fmt.Errorf("Found wrong main association: %s",
mainAssociation.AssociationId) *mainAssociation.RouteTableAssociationID)
} }
return nil return nil

View File

@ -6,10 +6,11 @@ import (
"log" "log"
"time" "time"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
) )
func resourceAwsNetworkAcl() *schema.Resource { func resourceAwsNetworkAcl() *schema.Resource {
@ -108,32 +109,34 @@ func resourceAwsNetworkAcl() *schema.Resource {
func resourceAwsNetworkAclCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsNetworkAclCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
// Create the Network Acl // Create the Network Acl
createOpts := &ec2.CreateNetworkAcl{ createOpts := &ec2.CreateNetworkACLRequest{
VpcId: d.Get("vpc_id").(string), VPCID: aws.String(d.Get("vpc_id").(string)),
} }
log.Printf("[DEBUG] Network Acl create config: %#v", createOpts) log.Printf("[DEBUG] Network Acl create config: %#v", createOpts)
resp, err := ec2conn.CreateNetworkAcl(createOpts) resp, err := ec2conn.CreateNetworkACL(createOpts)
if err != nil { if err != nil {
return fmt.Errorf("Error creating network acl: %s", err) return fmt.Errorf("Error creating network acl: %s", err)
} }
// Get the ID and store it // Get the ID and store it
networkAcl := &resp.NetworkAcl networkAcl := resp.NetworkACL
d.SetId(networkAcl.NetworkAclId) d.SetId(*networkAcl.NetworkACLID)
log.Printf("[INFO] Network Acl ID: %s", networkAcl.NetworkAclId) log.Printf("[INFO] Network Acl ID: %s", *networkAcl.NetworkACLID)
// Update rules and subnet association once acl is created // Update rules and subnet association once acl is created
return resourceAwsNetworkAclUpdate(d, meta) return resourceAwsNetworkAclUpdate(d, meta)
} }
func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
resp, err := ec2conn.NetworkAcls([]string{d.Id()}, ec2.NewFilter()) resp, err := ec2conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
NetworkACLIDs: []string{d.Id()},
})
if err != nil { if err != nil {
return err return err
@ -142,29 +145,29 @@ func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error {
return nil return nil
} }
networkAcl := &resp.NetworkAcls[0] networkAcl := &resp.NetworkACLs[0]
var ingressEntries []ec2.NetworkAclEntry var ingressEntries []ec2.NetworkACLEntry
var egressEntries []ec2.NetworkAclEntry var egressEntries []ec2.NetworkACLEntry
// separate the ingress and egress rules // separate the ingress and egress rules
for _, e := range networkAcl.EntrySet { for _, e := range networkAcl.Entries {
if e.Egress == true { if *e.Egress == true {
egressEntries = append(egressEntries, e) egressEntries = append(egressEntries, e)
} else { } else {
ingressEntries = append(ingressEntries, e) ingressEntries = append(ingressEntries, e)
} }
} }
d.Set("vpc_id", networkAcl.VpcId) d.Set("vpc_id", networkAcl.VPCID)
d.Set("ingress", ingressEntries) d.Set("ingress", ingressEntries)
d.Set("egress", egressEntries) d.Set("egress", egressEntries)
d.Set("tags", tagsToMap(networkAcl.Tags)) d.Set("tags", tagsToMapSDK(networkAcl.Tags))
return nil return nil
} }
func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
d.Partial(true) d.Partial(true)
if d.HasChange("ingress") { if d.HasChange("ingress") {
@ -190,13 +193,16 @@ func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error
if err != nil { if err != nil {
return fmt.Errorf("Failed to update acl %s with subnet %s: %s", d.Id(), newSubnet, err) return fmt.Errorf("Failed to update acl %s with subnet %s: %s", d.Id(), newSubnet, err)
} }
_, err = ec2conn.ReplaceNetworkAclAssociation(association.NetworkAclAssociationId, d.Id()) _, err = ec2conn.ReplaceNetworkACLAssociation(&ec2.ReplaceNetworkACLAssociationRequest{
AssociationID: association.NetworkACLAssociationID,
NetworkACLID: aws.String(d.Id()),
})
if err != nil { if err != nil {
return err return err
} }
} }
if err := setTags(ec2conn, d); err != nil { if err := setTagsSDK(ec2conn, d); err != nil {
return err return err
} else { } else {
d.SetPartial("tags") d.SetPartial("tags")
@ -226,7 +232,11 @@ func updateNetworkAclEntries(d *schema.ResourceData, entryType string, ec2conn *
} }
for _, remove := range toBeDeleted { for _, remove := range toBeDeleted {
// Delete old Acl // Delete old Acl
_, err := ec2conn.DeleteNetworkAclEntry(d.Id(), remove.RuleNumber, remove.Egress) err := ec2conn.DeleteNetworkACLEntry(&ec2.DeleteNetworkACLEntryRequest{
NetworkACLID: aws.String(d.Id()),
RuleNumber: remove.RuleNumber,
Egress: remove.Egress,
})
if err != nil { if err != nil {
return fmt.Errorf("Error deleting %s entry: %s", entryType, err) return fmt.Errorf("Error deleting %s entry: %s", entryType, err)
} }
@ -238,7 +248,15 @@ func updateNetworkAclEntries(d *schema.ResourceData, entryType string, ec2conn *
} }
for _, add := range toBeCreated { for _, add := range toBeCreated {
// Add new Acl entry // Add new Acl entry
_, err := ec2conn.CreateNetworkAclEntry(d.Id(), &add) err := ec2conn.CreateNetworkACLEntry(&ec2.CreateNetworkACLEntryRequest{
NetworkACLID: aws.String(d.Id()),
CIDRBlock: add.CIDRBlock,
Egress: add.Egress,
PortRange: add.PortRange,
Protocol: add.Protocol,
RuleAction: add.RuleAction,
RuleNumber: add.RuleNumber,
})
if err != nil { if err != nil {
return fmt.Errorf("Error creating %s entry: %s", entryType, err) return fmt.Errorf("Error creating %s entry: %s", entryType, err)
} }
@ -247,12 +265,15 @@ func updateNetworkAclEntries(d *schema.ResourceData, entryType string, ec2conn *
} }
func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error { func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
log.Printf("[INFO] Deleting Network Acl: %s", d.Id()) log.Printf("[INFO] Deleting Network Acl: %s", d.Id())
return resource.Retry(5*time.Minute, func() error { return resource.Retry(5*time.Minute, func() error {
if _, err := ec2conn.DeleteNetworkAcl(d.Id()); err != nil { err := ec2conn.DeleteNetworkACL(&ec2.DeleteNetworkACLRequest{
ec2err := err.(*ec2.Error) NetworkACLID: aws.String(d.Id()),
})
if err != nil {
ec2err := err.(aws.APIError)
switch ec2err.Code { switch ec2err.Code {
case "InvalidNetworkAclID.NotFound": case "InvalidNetworkAclID.NotFound":
return nil return nil
@ -267,7 +288,10 @@ func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error
if err != nil { if err != nil {
return fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err) return fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err)
} }
_, err = ec2conn.ReplaceNetworkAclAssociation(association.NetworkAclAssociationId, defaultAcl.NetworkAclId) _, err = ec2conn.ReplaceNetworkACLAssociation(&ec2.ReplaceNetworkACLAssociationRequest{
AssociationID: association.NetworkACLAssociationID,
NetworkACLID: defaultAcl.NetworkACLID,
})
return resource.RetryError{Err: err} return resource.RetryError{Err: err}
default: default:
// Any other error, we want to quit the retry loop immediately // Any other error, we want to quit the retry loop immediately
@ -296,30 +320,43 @@ func resourceAwsNetworkAclEntryHash(v interface{}) int {
return hashcode.String(buf.String()) return hashcode.String(buf.String())
} }
func getDefaultNetworkAcl(vpc_id string, ec2conn *ec2.EC2) (defaultAcl *ec2.NetworkAcl, err error) { func getDefaultNetworkAcl(vpc_id string, ec2conn *ec2.EC2) (defaultAcl *ec2.NetworkACL, err error) {
filter := ec2.NewFilter() resp, err := ec2conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
filter.Add("default", "true") NetworkACLIDs: []string{},
filter.Add("vpc-id", vpc_id) Filters: []ec2.Filter{
ec2.Filter{
resp, err := ec2conn.NetworkAcls([]string{}, filter) Name: aws.String("default"),
Values: []string{"true"},
},
ec2.Filter{
Name: aws.String("vpc-id"),
Values: []string{vpc_id},
},
},
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &resp.NetworkAcls[0], nil return &resp.NetworkACLs[0], nil
} }
func findNetworkAclAssociation(subnetId string, ec2conn *ec2.EC2) (networkAclAssociation *ec2.NetworkAclAssociation, err error) { func findNetworkAclAssociation(subnetId string, ec2conn *ec2.EC2) (networkAclAssociation *ec2.NetworkACLAssociation, err error) {
filter := ec2.NewFilter() resp, err := ec2conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
filter.Add("association.subnet-id", subnetId) NetworkACLIDs: []string{},
Filters: []ec2.Filter{
resp, err := ec2conn.NetworkAcls([]string{}, filter) ec2.Filter{
Name: aws.String("association.subnet-id"),
Values: []string{subnetId},
},
},
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, association := range resp.NetworkAcls[0].AssociationSet { for _, association := range resp.NetworkACLs[0].Associations {
if association.SubnetId == subnetId { if *association.SubnetID == subnetId {
return &association, nil return &association, nil
} }
} }

View File

@ -4,15 +4,16 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/goamz/ec2"
// "github.com/hashicorp/terraform/helper/hashcode" // "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
// "github.com/hashicorp/terraform/helper/schema" // "github.com/hashicorp/terraform/helper/schema"
) )
func TestAccAWSNetworkAclsWithEgressAndIngressRules(t *testing.T) { func TestAccAWSNetworkAclsWithEgressAndIngressRules(t *testing.T) {
var networkAcl ec2.NetworkAcl var networkAcl ec2.NetworkACL
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -24,29 +25,29 @@ func TestAccAWSNetworkAclsWithEgressAndIngressRules(t *testing.T) {
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAWSNetworkAclExists("aws_network_acl.bar", &networkAcl), testAccCheckAWSNetworkAclExists("aws_network_acl.bar", &networkAcl),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.bar", "ingress.580214135.protocol", "tcp"), "aws_network_acl.bar", "ingress.3409203205.protocol", "tcp"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.bar", "ingress.580214135.rule_no", "1"), "aws_network_acl.bar", "ingress.3409203205.rule_no", "1"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.bar", "ingress.580214135.from_port", "80"), "aws_network_acl.bar", "ingress.3409203205.from_port", "80"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.bar", "ingress.580214135.to_port", "80"), "aws_network_acl.bar", "ingress.3409203205.to_port", "80"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.bar", "ingress.580214135.action", "allow"), "aws_network_acl.bar", "ingress.3409203205.action", "allow"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.bar", "ingress.580214135.cidr_block", "10.3.10.3/18"), "aws_network_acl.bar", "ingress.3409203205.cidr_block", "10.3.10.3/18"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.bar", "egress.1730430240.protocol", "tcp"), "aws_network_acl.bar", "egress.2579689292.protocol", "tcp"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.bar", "egress.1730430240.rule_no", "2"), "aws_network_acl.bar", "egress.2579689292.rule_no", "2"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.bar", "egress.1730430240.from_port", "443"), "aws_network_acl.bar", "egress.2579689292.from_port", "443"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.bar", "egress.1730430240.to_port", "443"), "aws_network_acl.bar", "egress.2579689292.to_port", "443"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.bar", "egress.1730430240.cidr_block", "10.3.2.3/18"), "aws_network_acl.bar", "egress.2579689292.cidr_block", "10.3.2.3/18"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.bar", "egress.1730430240.action", "allow"), "aws_network_acl.bar", "egress.2579689292.action", "allow"),
), ),
}, },
}, },
@ -54,7 +55,7 @@ func TestAccAWSNetworkAclsWithEgressAndIngressRules(t *testing.T) {
} }
func TestAccAWSNetworkAclsOnlyIngressRules(t *testing.T) { func TestAccAWSNetworkAclsOnlyIngressRules(t *testing.T) {
var networkAcl ec2.NetworkAcl var networkAcl ec2.NetworkACL
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -67,17 +68,17 @@ func TestAccAWSNetworkAclsOnlyIngressRules(t *testing.T) {
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl), testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
// testAccCheckSubnetAssociation("aws_network_acl.foos", "aws_subnet.blob"), // testAccCheckSubnetAssociation("aws_network_acl.foos", "aws_subnet.blob"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.protocol", "tcp"), "aws_network_acl.foos", "ingress.2750166237.protocol", "tcp"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.rule_no", "1"), "aws_network_acl.foos", "ingress.2750166237.rule_no", "1"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.from_port", "0"), "aws_network_acl.foos", "ingress.2750166237.from_port", "0"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.to_port", "22"), "aws_network_acl.foos", "ingress.2750166237.to_port", "22"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.action", "deny"), "aws_network_acl.foos", "ingress.2750166237.action", "deny"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.cidr_block", "10.2.2.3/18"), "aws_network_acl.foos", "ingress.2750166237.cidr_block", "10.2.2.3/18"),
), ),
}, },
}, },
@ -85,7 +86,7 @@ func TestAccAWSNetworkAclsOnlyIngressRules(t *testing.T) {
} }
func TestAccAWSNetworkAclsOnlyIngressRulesChange(t *testing.T) { func TestAccAWSNetworkAclsOnlyIngressRulesChange(t *testing.T) {
var networkAcl ec2.NetworkAcl var networkAcl ec2.NetworkACL
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -98,21 +99,21 @@ func TestAccAWSNetworkAclsOnlyIngressRulesChange(t *testing.T) {
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl), testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
testIngressRuleLength(&networkAcl, 2), testIngressRuleLength(&networkAcl, 2),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.protocol", "tcp"), "aws_network_acl.foos", "ingress.37211640.protocol", "tcp"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.rule_no", "1"), "aws_network_acl.foos", "ingress.37211640.rule_no", "1"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.from_port", "0"), "aws_network_acl.foos", "ingress.37211640.from_port", "0"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.to_port", "22"), "aws_network_acl.foos", "ingress.37211640.to_port", "22"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.action", "deny"), "aws_network_acl.foos", "ingress.37211640.action", "deny"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.cidr_block", "10.2.2.3/18"), "aws_network_acl.foos", "ingress.37211640.cidr_block", "10.2.2.3/18"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.2438803013.from_port", "443"), "aws_network_acl.foos", "ingress.2750166237.from_port", "443"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.2438803013.rule_no", "2"), "aws_network_acl.foos", "ingress.2750166237.rule_no", "2"),
), ),
}, },
resource.TestStep{ resource.TestStep{
@ -121,17 +122,17 @@ func TestAccAWSNetworkAclsOnlyIngressRulesChange(t *testing.T) {
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl), testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
testIngressRuleLength(&networkAcl, 1), testIngressRuleLength(&networkAcl, 1),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.protocol", "tcp"), "aws_network_acl.foos", "ingress.37211640.protocol", "tcp"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.rule_no", "1"), "aws_network_acl.foos", "ingress.37211640.rule_no", "1"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.from_port", "0"), "aws_network_acl.foos", "ingress.37211640.from_port", "0"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.to_port", "22"), "aws_network_acl.foos", "ingress.37211640.to_port", "22"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.action", "deny"), "aws_network_acl.foos", "ingress.37211640.action", "deny"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.3697634361.cidr_block", "10.2.2.3/18"), "aws_network_acl.foos", "ingress.37211640.cidr_block", "10.2.2.3/18"),
), ),
}, },
}, },
@ -139,7 +140,7 @@ func TestAccAWSNetworkAclsOnlyIngressRulesChange(t *testing.T) {
} }
func TestAccAWSNetworkAclsOnlyEgressRules(t *testing.T) { func TestAccAWSNetworkAclsOnlyEgressRules(t *testing.T) {
var networkAcl ec2.NetworkAcl var networkAcl ec2.NetworkACL
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -150,14 +151,14 @@ func TestAccAWSNetworkAclsOnlyEgressRules(t *testing.T) {
Config: testAccAWSNetworkAclEgressConfig, Config: testAccAWSNetworkAclEgressConfig,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAWSNetworkAclExists("aws_network_acl.bond", &networkAcl), testAccCheckAWSNetworkAclExists("aws_network_acl.bond", &networkAcl),
testAccCheckTags(&networkAcl.Tags, "foo", "bar"), testAccCheckTagsSDK(&networkAcl.Tags, "foo", "bar"),
), ),
}, },
}, },
}) })
} }
func TestAccNetworkAcl_SubnetChange(t *testing.T) { func TestAccAWSNetworkAcl_SubnetChange(t *testing.T) {
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -183,7 +184,7 @@ func TestAccNetworkAcl_SubnetChange(t *testing.T) {
} }
func testAccCheckAWSNetworkAclDestroy(s *terraform.State) error { func testAccCheckAWSNetworkAclDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_network" { if rs.Type != "aws_network" {
@ -191,16 +192,18 @@ func testAccCheckAWSNetworkAclDestroy(s *terraform.State) error {
} }
// Retrieve the network acl // Retrieve the network acl
resp, err := conn.NetworkAcls([]string{rs.Primary.ID}, ec2.NewFilter()) resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
NetworkACLIDs: []string{rs.Primary.ID},
})
if err == nil { if err == nil {
if len(resp.NetworkAcls) > 0 && resp.NetworkAcls[0].NetworkAclId == rs.Primary.ID { if len(resp.NetworkACLs) > 0 && *resp.NetworkACLs[0].NetworkACLID == rs.Primary.ID {
return fmt.Errorf("Network Acl (%s) still exists.", rs.Primary.ID) return fmt.Errorf("Network Acl (%s) still exists.", rs.Primary.ID)
} }
return nil return nil
} }
ec2err, ok := err.(*ec2.Error) ec2err, ok := err.(aws.APIError)
if !ok { if !ok {
return err return err
} }
@ -213,7 +216,7 @@ func testAccCheckAWSNetworkAclDestroy(s *terraform.State) error {
return nil return nil
} }
func testAccCheckAWSNetworkAclExists(n string, networkAcl *ec2.NetworkAcl) resource.TestCheckFunc { func testAccCheckAWSNetworkAclExists(n string, networkAcl *ec2.NetworkACL) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n] rs, ok := s.RootModule().Resources[n]
if !ok { if !ok {
@ -223,15 +226,17 @@ func testAccCheckAWSNetworkAclExists(n string, networkAcl *ec2.NetworkAcl) resou
if rs.Primary.ID == "" { if rs.Primary.ID == "" {
return fmt.Errorf("No Security Group is set") return fmt.Errorf("No Security Group is set")
} }
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
resp, err := conn.NetworkAcls([]string{rs.Primary.ID}, nil) resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
NetworkACLIDs: []string{rs.Primary.ID},
})
if err != nil { if err != nil {
return err return err
} }
if len(resp.NetworkAcls) > 0 && resp.NetworkAcls[0].NetworkAclId == rs.Primary.ID { if len(resp.NetworkACLs) > 0 && *resp.NetworkACLs[0].NetworkACLID == rs.Primary.ID {
*networkAcl = resp.NetworkAcls[0] *networkAcl = resp.NetworkACLs[0]
return nil return nil
} }
@ -239,11 +244,11 @@ func testAccCheckAWSNetworkAclExists(n string, networkAcl *ec2.NetworkAcl) resou
} }
} }
func testIngressRuleLength(networkAcl *ec2.NetworkAcl, length int) resource.TestCheckFunc { func testIngressRuleLength(networkAcl *ec2.NetworkACL, length int) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
var ingressEntries []ec2.NetworkAclEntry var ingressEntries []ec2.NetworkACLEntry
for _, e := range networkAcl.EntrySet { for _, e := range networkAcl.Entries {
if e.Egress == false { if *e.Egress == false {
ingressEntries = append(ingressEntries, e) ingressEntries = append(ingressEntries, e)
} }
} }
@ -261,21 +266,26 @@ func testAccCheckSubnetIsAssociatedWithAcl(acl string, sub string) resource.Test
networkAcl := s.RootModule().Resources[acl] networkAcl := s.RootModule().Resources[acl]
subnet := s.RootModule().Resources[sub] subnet := s.RootModule().Resources[sub]
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
filter := ec2.NewFilter() resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
filter.Add("association.subnet-id", subnet.Primary.ID) NetworkACLIDs: []string{networkAcl.Primary.ID},
resp, err := conn.NetworkAcls([]string{networkAcl.Primary.ID}, filter) Filters: []ec2.Filter{
ec2.Filter{
Name: aws.String("association.subnet-id"),
Values: []string{subnet.Primary.ID},
},
},
})
if err != nil { if err != nil {
return err return err
} }
if len(resp.NetworkAcls) > 0 { if len(resp.NetworkACLs) > 0 {
return nil return nil
} }
r, _ := conn.NetworkAcls([]string{}, ec2.NewFilter()) // r, _ := conn.NetworkACLs([]string{}, ec2.NewFilter())
fmt.Printf("\n\nall acls\n %#v\n\n", r.NetworkAcls) // fmt.Printf("\n\nall acls\n %#v\n\n", r.NetworkAcls)
conn.NetworkAcls([]string{}, filter) // conn.NetworkAcls([]string{}, filter)
return fmt.Errorf("Network Acl %s is not associated with subnet %s", acl, sub) return fmt.Errorf("Network Acl %s is not associated with subnet %s", acl, sub)
} }
@ -286,15 +296,21 @@ func testAccCheckSubnetIsNotAssociatedWithAcl(acl string, subnet string) resourc
networkAcl := s.RootModule().Resources[acl] networkAcl := s.RootModule().Resources[acl]
subnet := s.RootModule().Resources[subnet] subnet := s.RootModule().Resources[subnet]
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
filter := ec2.NewFilter() resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
filter.Add("association.subnet-id", subnet.Primary.ID) NetworkACLIDs: []string{networkAcl.Primary.ID},
resp, err := conn.NetworkAcls([]string{networkAcl.Primary.ID}, filter) Filters: []ec2.Filter{
ec2.Filter{
Name: aws.String("association.subnet-id"),
Values: []string{subnet.Primary.ID},
},
},
})
if err != nil { if err != nil {
return err return err
} }
if len(resp.NetworkAcls) > 0 { if len(resp.NetworkACLs) > 0 {
return fmt.Errorf("Network Acl %s is still associated with subnet %s", acl, subnet) return fmt.Errorf("Network Acl %s is still associated with subnet %s", acl, subnet)
} }
return nil return nil

View File

@ -138,7 +138,7 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er
Delay: 30 * time.Second, Delay: 30 * time.Second,
Pending: []string{"PENDING"}, Pending: []string{"PENDING"},
Target: "INSYNC", Target: "INSYNC",
Timeout: 10 * time.Minute, Timeout: 30 * time.Minute,
MinTimeout: 5 * time.Second, MinTimeout: 5 * time.Second,
Refresh: func() (result interface{}, state string, err error) { Refresh: func() (result interface{}, state string, err error) {
changeRequest := &route53.GetChangeRequest{ changeRequest := &route53.GetChangeRequest{

View File

@ -6,10 +6,11 @@ import (
"log" "log"
"time" "time"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
) )
func resourceAwsRouteTable() *schema.Resource { func resourceAwsRouteTable() *schema.Resource {
@ -61,11 +62,11 @@ func resourceAwsRouteTable() *schema.Resource {
} }
func resourceAwsRouteTableCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsRouteTableCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
// Create the routing table // Create the routing table
createOpts := &ec2.CreateRouteTable{ createOpts := &ec2.CreateRouteTableRequest{
VpcId: d.Get("vpc_id").(string), VPCID: aws.String(d.Get("vpc_id").(string)),
} }
log.Printf("[DEBUG] RouteTable create config: %#v", createOpts) log.Printf("[DEBUG] RouteTable create config: %#v", createOpts)
@ -75,8 +76,8 @@ func resourceAwsRouteTableCreate(d *schema.ResourceData, meta interface{}) error
} }
// Get the ID and store it // Get the ID and store it
rt := &resp.RouteTable rt := resp.RouteTable
d.SetId(rt.RouteTableId) d.SetId(*rt.RouteTableID)
log.Printf("[INFO] Route Table ID: %s", d.Id()) log.Printf("[INFO] Route Table ID: %s", d.Id())
// Wait for the route table to become available // Wait for the route table to become available
@ -99,7 +100,7 @@ func resourceAwsRouteTableCreate(d *schema.ResourceData, meta interface{}) error
} }
func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(ec2conn, d.Id())() rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(ec2conn, d.Id())()
if err != nil { if err != nil {
@ -110,40 +111,48 @@ func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error {
} }
rt := rtRaw.(*ec2.RouteTable) rt := rtRaw.(*ec2.RouteTable)
d.Set("vpc_id", rt.VpcId) d.Set("vpc_id", rt.VPCID)
// Create an empty schema.Set to hold all routes // Create an empty schema.Set to hold all routes
route := &schema.Set{F: resourceAwsRouteTableHash} route := &schema.Set{F: resourceAwsRouteTableHash}
// Loop through the routes and add them to the set // Loop through the routes and add them to the set
for _, r := range rt.Routes { for _, r := range rt.Routes {
if r.GatewayId == "local" { if r.GatewayID != nil && *r.GatewayID == "local" {
continue continue
} }
if r.Origin == "EnableVgwRoutePropagation" { if r.Origin != nil && *r.Origin == "EnableVgwRoutePropagation" {
continue continue
} }
m := make(map[string]interface{}) m := make(map[string]interface{})
m["cidr_block"] = r.DestinationCidrBlock
m["gateway_id"] = r.GatewayId if r.DestinationCIDRBlock != nil {
m["instance_id"] = r.InstanceId m["cidr_block"] = *r.DestinationCIDRBlock
m["vpc_peering_connection_id"] = r.VpcPeeringConnectionId }
if r.GatewayID != nil {
m["gateway_id"] = *r.GatewayID
}
if r.InstanceID != nil {
m["instance_id"] = *r.InstanceID
}
if r.VPCPeeringConnectionID != nil {
m["vpc_peering_connection_id"] = *r.VPCPeeringConnectionID
}
route.Add(m) route.Add(m)
} }
d.Set("route", route) d.Set("route", route)
// Tags // Tags
d.Set("tags", tagsToMap(rt.Tags)) d.Set("tags", tagsToMapSDK(rt.Tags))
return nil return nil
} }
func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
// Check if the route set as a whole has changed // Check if the route set as a whole has changed
if d.HasChange("route") { if d.HasChange("route") {
@ -159,8 +168,10 @@ func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error
log.Printf( log.Printf(
"[INFO] Deleting route from %s: %s", "[INFO] Deleting route from %s: %s",
d.Id(), m["cidr_block"].(string)) d.Id(), m["cidr_block"].(string))
_, err := ec2conn.DeleteRoute( err := ec2conn.DeleteRoute(&ec2.DeleteRouteRequest{
d.Id(), m["cidr_block"].(string)) RouteTableID: aws.String(d.Id()),
DestinationCIDRBlock: aws.String(m["cidr_block"].(string)),
})
if err != nil { if err != nil {
return err return err
} }
@ -174,17 +185,16 @@ func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error
for _, route := range nrs.List() { for _, route := range nrs.List() {
m := route.(map[string]interface{}) m := route.(map[string]interface{})
opts := ec2.CreateRoute{ opts := ec2.CreateRouteRequest{
RouteTableId: d.Id(), RouteTableID: aws.String(d.Id()),
DestinationCidrBlock: m["cidr_block"].(string), DestinationCIDRBlock: aws.String(m["cidr_block"].(string)),
GatewayId: m["gateway_id"].(string), GatewayID: aws.String(m["gateway_id"].(string)),
InstanceId: m["instance_id"].(string), InstanceID: aws.String(m["instance_id"].(string)),
VpcPeeringConnectionId: m["vpc_peering_connection_id"].(string), VPCPeeringConnectionID: aws.String(m["vpc_peering_connection_id"].(string)),
} }
log.Printf("[INFO] Creating route for %s: %#v", d.Id(), opts) log.Printf("[INFO] Creating route for %s: %#v", d.Id(), opts)
_, err := ec2conn.CreateRoute(&opts) if err := ec2conn.CreateRoute(&opts); err != nil {
if err != nil {
return err return err
} }
@ -193,7 +203,7 @@ func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error
} }
} }
if err := setTags(ec2conn, d); err != nil { if err := setTagsSDK(ec2conn, d); err != nil {
return err return err
} else { } else {
d.SetPartial("tags") d.SetPartial("tags")
@ -203,7 +213,7 @@ func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error
} }
func resourceAwsRouteTableDelete(d *schema.ResourceData, meta interface{}) error { func resourceAwsRouteTableDelete(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
// First request the routing table since we'll have to disassociate // First request the routing table since we'll have to disassociate
// all the subnets first. // all the subnets first.
@ -218,16 +228,22 @@ func resourceAwsRouteTableDelete(d *schema.ResourceData, meta interface{}) error
// Do all the disassociations // Do all the disassociations
for _, a := range rt.Associations { for _, a := range rt.Associations {
log.Printf("[INFO] Disassociating association: %s", a.AssociationId) log.Printf("[INFO] Disassociating association: %s", *a.RouteTableAssociationID)
if _, err := ec2conn.DisassociateRouteTable(a.AssociationId); err != nil { err := ec2conn.DisassociateRouteTable(&ec2.DisassociateRouteTableRequest{
AssociationID: a.RouteTableAssociationID,
})
if err != nil {
return err return err
} }
} }
// Delete the route table // Delete the route table
log.Printf("[INFO] Deleting Route Table: %s", d.Id()) log.Printf("[INFO] Deleting Route Table: %s", d.Id())
if _, err := ec2conn.DeleteRouteTable(d.Id()); err != nil { err = ec2conn.DeleteRouteTable(&ec2.DeleteRouteTableRequest{
ec2err, ok := err.(*ec2.Error) RouteTableID: aws.String(d.Id()),
})
if err != nil {
ec2err, ok := err.(aws.APIError)
if ok && ec2err.Code == "InvalidRouteTableID.NotFound" { if ok && ec2err.Code == "InvalidRouteTableID.NotFound" {
return nil return nil
} }
@ -279,9 +295,11 @@ func resourceAwsRouteTableHash(v interface{}) int {
// a RouteTable. // a RouteTable.
func resourceAwsRouteTableStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { func resourceAwsRouteTableStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
resp, err := conn.DescribeRouteTables([]string{id}, ec2.NewFilter()) resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
RouteTableIDs: []string{id},
})
if err != nil { if err != nil {
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidRouteTableID.NotFound" { if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidRouteTableID.NotFound" {
resp = nil resp = nil
} else { } else {
log.Printf("Error on RouteTableStateRefresh: %s", err) log.Printf("Error on RouteTableStateRefresh: %s", err)

View File

@ -4,8 +4,9 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
) )
func resourceAwsRouteTableAssociation() *schema.Resource { func resourceAwsRouteTableAssociation() *schema.Resource {
@ -31,30 +32,31 @@ func resourceAwsRouteTableAssociation() *schema.Resource {
} }
func resourceAwsRouteTableAssociationCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsRouteTableAssociationCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
log.Printf( log.Printf(
"[INFO] Creating route table association: %s => %s", "[INFO] Creating route table association: %s => %s",
d.Get("subnet_id").(string), d.Get("subnet_id").(string),
d.Get("route_table_id").(string)) d.Get("route_table_id").(string))
resp, err := ec2conn.AssociateRouteTable( resp, err := ec2conn.AssociateRouteTable(&ec2.AssociateRouteTableRequest{
d.Get("route_table_id").(string), RouteTableID: aws.String(d.Get("route_table_id").(string)),
d.Get("subnet_id").(string)) SubnetID: aws.String(d.Get("subnet_id").(string)),
})
if err != nil { if err != nil {
return err return err
} }
// Set the ID and return // Set the ID and return
d.SetId(resp.AssociationId) d.SetId(*resp.AssociationID)
log.Printf("[INFO] Association ID: %s", d.Id()) log.Printf("[INFO] Association ID: %s", d.Id())
return nil return nil
} }
func resourceAwsRouteTableAssociationRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsRouteTableAssociationRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
// Get the routing table that this association belongs to // Get the routing table that this association belongs to
rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc( rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(
@ -70,9 +72,9 @@ func resourceAwsRouteTableAssociationRead(d *schema.ResourceData, meta interface
// Inspect that the association exists // Inspect that the association exists
found := false found := false
for _, a := range rt.Associations { for _, a := range rt.Associations {
if a.AssociationId == d.Id() { if *a.RouteTableAssociationID == d.Id() {
found = true found = true
d.Set("subnet_id", a.SubnetId) d.Set("subnet_id", *a.SubnetID)
break break
} }
} }
@ -86,19 +88,21 @@ func resourceAwsRouteTableAssociationRead(d *schema.ResourceData, meta interface
} }
func resourceAwsRouteTableAssociationUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAwsRouteTableAssociationUpdate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
log.Printf( log.Printf(
"[INFO] Creating route table association: %s => %s", "[INFO] Creating route table association: %s => %s",
d.Get("subnet_id").(string), d.Get("subnet_id").(string),
d.Get("route_table_id").(string)) d.Get("route_table_id").(string))
resp, err := ec2conn.ReassociateRouteTable( req := &ec2.ReplaceRouteTableAssociationRequest{
d.Id(), AssociationID: aws.String(d.Id()),
d.Get("route_table_id").(string)) RouteTableID: aws.String(d.Get("route_table_id").(string)),
}
resp, err := ec2conn.ReplaceRouteTableAssociation(req)
if err != nil { if err != nil {
ec2err, ok := err.(*ec2.Error) ec2err, ok := err.(aws.APIError)
if ok && ec2err.Code == "InvalidAssociationID.NotFound" { if ok && ec2err.Code == "InvalidAssociationID.NotFound" {
// Not found, so just create a new one // Not found, so just create a new one
return resourceAwsRouteTableAssociationCreate(d, meta) return resourceAwsRouteTableAssociationCreate(d, meta)
@ -108,18 +112,21 @@ func resourceAwsRouteTableAssociationUpdate(d *schema.ResourceData, meta interfa
} }
// Update the ID // Update the ID
d.SetId(resp.AssociationId) d.SetId(*resp.NewAssociationID)
log.Printf("[INFO] Association ID: %s", d.Id()) log.Printf("[INFO] Association ID: %s", d.Id())
return nil return nil
} }
func resourceAwsRouteTableAssociationDelete(d *schema.ResourceData, meta interface{}) error { func resourceAwsRouteTableAssociationDelete(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
log.Printf("[INFO] Deleting route table association: %s", d.Id()) log.Printf("[INFO] Deleting route table association: %s", d.Id())
if _, err := ec2conn.DisassociateRouteTable(d.Id()); err != nil { err := ec2conn.DisassociateRouteTable(&ec2.DisassociateRouteTableRequest{
ec2err, ok := err.(*ec2.Error) AssociationID: aws.String(d.Id()),
})
if err != nil {
ec2err, ok := err.(aws.APIError)
if ok && ec2err.Code == "InvalidAssociationID.NotFound" { if ok && ec2err.Code == "InvalidAssociationID.NotFound" {
return nil return nil
} }

View File

@ -4,9 +4,10 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/goamz/ec2"
) )
func TestAccAWSRouteTableAssociation(t *testing.T) { func TestAccAWSRouteTableAssociation(t *testing.T) {
@ -37,7 +38,7 @@ func TestAccAWSRouteTableAssociation(t *testing.T) {
} }
func testAccCheckRouteTableAssociationDestroy(s *terraform.State) error { func testAccCheckRouteTableAssociationDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_route_table_association" { if rs.Type != "aws_route_table_association" {
@ -45,11 +46,12 @@ func testAccCheckRouteTableAssociationDestroy(s *terraform.State) error {
} }
// Try to find the resource // Try to find the resource
resp, err := conn.DescribeRouteTables( resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
[]string{rs.Primary.Attributes["route_table_Id"]}, ec2.NewFilter()) RouteTableIDs: []string{rs.Primary.Attributes["route_table_id"]},
})
if err != nil { if err != nil {
// Verify the error is what we want // Verify the error is what we want
ec2err, ok := err.(*ec2.Error) ec2err, ok := err.(aws.APIError)
if !ok { if !ok {
return err return err
} }
@ -62,7 +64,7 @@ func testAccCheckRouteTableAssociationDestroy(s *terraform.State) error {
rt := resp.RouteTables[0] rt := resp.RouteTables[0]
if len(rt.Associations) > 0 { if len(rt.Associations) > 0 {
return fmt.Errorf( return fmt.Errorf(
"route table %s has associations", rt.RouteTableId) "route table %s has associations", *rt.RouteTableID)
} }
} }
@ -81,9 +83,10 @@ func testAccCheckRouteTableAssociationExists(n string, v *ec2.RouteTable) resour
return fmt.Errorf("No ID is set") return fmt.Errorf("No ID is set")
} }
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
resp, err := conn.DescribeRouteTables( resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
[]string{rs.Primary.Attributes["route_table_id"]}, ec2.NewFilter()) RouteTableIDs: []string{rs.Primary.Attributes["route_table_id"]},
})
if err != nil { if err != nil {
return err return err
} }

View File

@ -4,9 +4,10 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/goamz/ec2"
) )
func TestAccAWSRouteTable_normal(t *testing.T) { func TestAccAWSRouteTable_normal(t *testing.T) {
@ -19,7 +20,7 @@ func TestAccAWSRouteTable_normal(t *testing.T) {
routes := make(map[string]ec2.Route) routes := make(map[string]ec2.Route)
for _, r := range v.Routes { for _, r := range v.Routes {
routes[r.DestinationCidrBlock] = r routes[*r.DestinationCIDRBlock] = r
} }
if _, ok := routes["10.1.0.0/16"]; !ok { if _, ok := routes["10.1.0.0/16"]; !ok {
@ -39,7 +40,7 @@ func TestAccAWSRouteTable_normal(t *testing.T) {
routes := make(map[string]ec2.Route) routes := make(map[string]ec2.Route)
for _, r := range v.Routes { for _, r := range v.Routes {
routes[r.DestinationCidrBlock] = r routes[*r.DestinationCIDRBlock] = r
} }
if _, ok := routes["10.1.0.0/16"]; !ok { if _, ok := routes["10.1.0.0/16"]; !ok {
@ -91,7 +92,7 @@ func TestAccAWSRouteTable_instance(t *testing.T) {
routes := make(map[string]ec2.Route) routes := make(map[string]ec2.Route)
for _, r := range v.Routes { for _, r := range v.Routes {
routes[r.DestinationCidrBlock] = r routes[*r.DestinationCIDRBlock] = r
} }
if _, ok := routes["10.1.0.0/16"]; !ok { if _, ok := routes["10.1.0.0/16"]; !ok {
@ -133,7 +134,7 @@ func TestAccAWSRouteTable_tags(t *testing.T) {
Config: testAccRouteTableConfigTags, Config: testAccRouteTableConfigTags,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckRouteTableExists("aws_route_table.foo", &route_table), testAccCheckRouteTableExists("aws_route_table.foo", &route_table),
testAccCheckTags(&route_table.Tags, "foo", "bar"), testAccCheckTagsSDK(&route_table.Tags, "foo", "bar"),
), ),
}, },
@ -141,8 +142,8 @@ func TestAccAWSRouteTable_tags(t *testing.T) {
Config: testAccRouteTableConfigTagsUpdate, Config: testAccRouteTableConfigTagsUpdate,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckRouteTableExists("aws_route_table.foo", &route_table), testAccCheckRouteTableExists("aws_route_table.foo", &route_table),
testAccCheckTags(&route_table.Tags, "foo", ""), testAccCheckTagsSDK(&route_table.Tags, "foo", ""),
testAccCheckTags(&route_table.Tags, "bar", "baz"), testAccCheckTagsSDK(&route_table.Tags, "bar", "baz"),
), ),
}, },
}, },
@ -150,7 +151,7 @@ func TestAccAWSRouteTable_tags(t *testing.T) {
} }
func testAccCheckRouteTableDestroy(s *terraform.State) error { func testAccCheckRouteTableDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_route_table" { if rs.Type != "aws_route_table" {
@ -158,8 +159,9 @@ func testAccCheckRouteTableDestroy(s *terraform.State) error {
} }
// Try to find the resource // Try to find the resource
resp, err := conn.DescribeRouteTables( resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
[]string{rs.Primary.ID}, ec2.NewFilter()) RouteTableIDs: []string{rs.Primary.ID},
})
if err == nil { if err == nil {
if len(resp.RouteTables) > 0 { if len(resp.RouteTables) > 0 {
return fmt.Errorf("still exist.") return fmt.Errorf("still exist.")
@ -169,7 +171,7 @@ func testAccCheckRouteTableDestroy(s *terraform.State) error {
} }
// Verify the error is what we want // Verify the error is what we want
ec2err, ok := err.(*ec2.Error) ec2err, ok := err.(aws.APIError)
if !ok { if !ok {
return err return err
} }
@ -192,9 +194,10 @@ func testAccCheckRouteTableExists(n string, v *ec2.RouteTable) resource.TestChec
return fmt.Errorf("No ID is set") return fmt.Errorf("No ID is set")
} }
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
resp, err := conn.DescribeRouteTables( resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
[]string{rs.Primary.ID}, ec2.NewFilter()) RouteTableIDs: []string{rs.Primary.ID},
})
if err != nil { if err != nil {
return err return err
} }
@ -208,7 +211,10 @@ func testAccCheckRouteTableExists(n string, v *ec2.RouteTable) resource.TestChec
} }
} }
func TestAccAWSRouteTable_vpcPeering(t *testing.T) { // TODO: re-enable this test.
// VPC Peering connections are prefixed with pcx
// Right now there is no VPC Peering resource
func _TestAccAWSRouteTable_vpcPeering(t *testing.T) {
var v ec2.RouteTable var v ec2.RouteTable
testCheck := func(*terraform.State) error { testCheck := func(*terraform.State) error {
@ -218,7 +224,7 @@ func TestAccAWSRouteTable_vpcPeering(t *testing.T) {
routes := make(map[string]ec2.Route) routes := make(map[string]ec2.Route)
for _, r := range v.Routes { for _, r := range v.Routes {
routes[r.DestinationCidrBlock] = r routes[*r.DestinationCIDRBlock] = r
} }
if _, ok := routes["10.1.0.0/16"]; !ok { if _, ok := routes["10.1.0.0/16"]; !ok {
@ -345,6 +351,9 @@ resource "aws_route_table" "foo" {
} }
` `
// TODO: re-enable this test.
// VPC Peering connections are prefixed with pcx
// Right now there is no VPC Peering resource
const testAccRouteTableVpcPeeringConfig = ` const testAccRouteTableVpcPeeringConfig = `
resource "aws_vpc" "foo" { resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16" cidr_block = "10.1.0.0/16"
@ -359,7 +368,7 @@ resource "aws_route_table" "foo" {
route { route {
cidr_block = "10.2.0.0/16" cidr_block = "10.2.0.0/16"
vpc_peering_connection_id = "vpc-12345" vpc_peering_connection_id = "pcx-12345"
} }
} }
` `

View File

@ -7,10 +7,11 @@ import (
"sort" "sort"
"time" "time"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
) )
func resourceAwsSecurityGroup() *schema.Resource { func resourceAwsSecurityGroup() *schema.Resource {
@ -141,18 +142,18 @@ func resourceAwsSecurityGroup() *schema.Resource {
} }
func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
securityGroupOpts := ec2.SecurityGroup{ securityGroupOpts := &ec2.CreateSecurityGroupRequest{
Name: d.Get("name").(string), GroupName: aws.String(d.Get("name").(string)),
} }
if v := d.Get("vpc_id"); v != nil { if v := d.Get("vpc_id"); v != nil {
securityGroupOpts.VpcId = v.(string) securityGroupOpts.VPCID = aws.String(v.(string))
} }
if v := d.Get("description"); v != nil { if v := d.Get("description"); v != nil {
securityGroupOpts.Description = v.(string) securityGroupOpts.Description = aws.String(v.(string))
} }
log.Printf( log.Printf(
@ -162,7 +163,7 @@ func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) er
return fmt.Errorf("Error creating Security Group: %s", err) return fmt.Errorf("Error creating Security Group: %s", err)
} }
d.SetId(createResp.Id) d.SetId(*createResp.GroupID)
log.Printf("[INFO] Security Group ID: %s", d.Id()) log.Printf("[INFO] Security Group ID: %s", d.Id())
@ -186,7 +187,7 @@ func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) er
} }
func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())() sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
if err != nil { if err != nil {
@ -197,24 +198,23 @@ func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) erro
return nil return nil
} }
sg := sgRaw.(*ec2.SecurityGroupInfo) sg := sgRaw.(ec2.SecurityGroup)
ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPerms) ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissions)
egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermsEgress) egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissionsEgress)
d.Set("description", sg.Description) d.Set("description", sg.Description)
d.Set("name", sg.Name) d.Set("name", sg.GroupName)
d.Set("vpc_id", sg.VpcId) d.Set("vpc_id", sg.VPCID)
d.Set("owner_id", sg.OwnerId) d.Set("owner_id", sg.OwnerID)
d.Set("ingress", ingressRules) d.Set("ingress", ingressRules)
d.Set("egress", egressRules) d.Set("egress", egressRules)
d.Set("tags", tagsToMap(sg.Tags)) d.Set("tags", tagsToMapSDK(sg.Tags))
return nil return nil
} }
func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())() sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
if err != nil { if err != nil {
@ -224,7 +224,8 @@ func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) er
d.SetId("") d.SetId("")
return nil return nil
} }
group := sgRaw.(*ec2.SecurityGroupInfo).SecurityGroup
group := sgRaw.(ec2.SecurityGroup)
err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group) err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group)
if err != nil { if err != nil {
@ -238,7 +239,7 @@ func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) er
} }
} }
if err := setTags(ec2conn, d); err != nil { if err := setTagsSDK(ec2conn, d); err != nil {
return err return err
} }
@ -248,14 +249,16 @@ func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) er
} }
func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
log.Printf("[DEBUG] Security Group destroy: %v", d.Id()) log.Printf("[DEBUG] Security Group destroy: %v", d.Id())
return resource.Retry(5*time.Minute, func() error { return resource.Retry(5*time.Minute, func() error {
_, err := ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: d.Id()}) err := ec2conn.DeleteSecurityGroup(&ec2.DeleteSecurityGroupRequest{
GroupID: aws.String(d.Id()),
})
if err != nil { if err != nil {
ec2err, ok := err.(*ec2.Error) ec2err, ok := err.(aws.APIError)
if !ok { if !ok {
return err return err
} }
@ -313,34 +316,45 @@ func resourceAwsSecurityGroupRuleHash(v interface{}) int {
return hashcode.String(buf.String()) return hashcode.String(buf.String())
} }
func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []ec2.IPPerm) []map[string]interface{} { func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []ec2.IPPermission) []map[string]interface{} {
ruleMap := make(map[string]map[string]interface{}) ruleMap := make(map[string]map[string]interface{})
for _, perm := range permissions { for _, perm := range permissions {
k := fmt.Sprintf("%s-%d-%d", perm.Protocol, perm.FromPort, perm.ToPort) var fromPort, toPort int
if v := perm.FromPort; v != nil {
fromPort = *v
}
if v := perm.ToPort; v != nil {
toPort = *v
}
k := fmt.Sprintf("%s-%d-%d", *perm.IPProtocol, fromPort, toPort)
m, ok := ruleMap[k] m, ok := ruleMap[k]
if !ok { if !ok {
m = make(map[string]interface{}) m = make(map[string]interface{})
ruleMap[k] = m ruleMap[k] = m
} }
m["from_port"] = perm.FromPort m["from_port"] = fromPort
m["to_port"] = perm.ToPort m["to_port"] = toPort
m["protocol"] = perm.Protocol m["protocol"] = *perm.IPProtocol
if len(perm.SourceIPs) > 0 { if len(perm.IPRanges) > 0 {
raw, ok := m["cidr_blocks"] raw, ok := m["cidr_blocks"]
if !ok { if !ok {
raw = make([]string, 0, len(perm.SourceIPs)) raw = make([]string, 0, len(perm.IPRanges))
} }
list := raw.([]string) list := raw.([]string)
list = append(list, perm.SourceIPs...) for _, ip := range perm.IPRanges {
list = append(list, *ip.CIDRIP)
}
m["cidr_blocks"] = list m["cidr_blocks"] = list
} }
var groups []string var groups []string
if len(perm.SourceGroups) > 0 { if len(perm.UserIDGroupPairs) > 0 {
groups = flattenSecurityGroups(perm.SourceGroups) groups = flattenSecurityGroupsSDK(perm.UserIDGroupPairs)
} }
for i, id := range groups { for i, id := range groups {
if id == d.Id() { if id == d.Id() {
@ -364,7 +378,6 @@ func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []
for _, m := range ruleMap { for _, m := range ruleMap {
rules = append(rules, m) rules = append(rules, m)
} }
return rules return rules
} }
@ -383,6 +396,7 @@ func resourceAwsSecurityGroupUpdateRules(
os := o.(*schema.Set) os := o.(*schema.Set)
ns := n.(*schema.Set) ns := n.(*schema.Set)
// TODO: re-munge this when test is updated
remove := expandIPPerms(d.Id(), os.Difference(ns).List()) remove := expandIPPerms(d.Id(), os.Difference(ns).List())
add := expandIPPerms(d.Id(), ns.Difference(os).List()) add := expandIPPerms(d.Id(), ns.Difference(os).List())
@ -396,34 +410,53 @@ func resourceAwsSecurityGroupUpdateRules(
// not have service issues. // not have service issues.
if len(remove) > 0 || len(add) > 0 { if len(remove) > 0 || len(add) > 0 {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
var err error
if len(remove) > 0 { if len(remove) > 0 {
// Revoke the old rules log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v",
revoke := ec2conn.RevokeSecurityGroup group, ruleset, remove)
if ruleset == "egress" { if ruleset == "egress" {
revoke = ec2conn.RevokeSecurityGroupEgress req := &ec2.RevokeSecurityGroupEgressRequest{
GroupID: group.GroupID,
IPPermissions: remove,
}
err = ec2conn.RevokeSecurityGroupEgress(req)
} else {
req := &ec2.RevokeSecurityGroupIngressRequest{
GroupID: group.GroupID,
IPPermissions: remove,
}
err = ec2conn.RevokeSecurityGroupIngress(req)
} }
log.Printf("[DEBUG] Revoking security group %s %s rule: %#v", if err != nil {
group, ruleset, remove)
if _, err := revoke(group, remove); err != nil {
return fmt.Errorf( return fmt.Errorf(
"Error revoking security group %s rules: %s", "Error authorizing security group %s rules: %s",
ruleset, err) ruleset, err)
} }
} }
if len(add) > 0 { if len(add) > 0 {
log.Printf("[DEBUG] Authorizing security group %#v %s rule: %#v",
group, ruleset, add)
// Authorize the new rules // Authorize the new rules
authorize := ec2conn.AuthorizeSecurityGroup
if ruleset == "egress" { if ruleset == "egress" {
authorize = ec2conn.AuthorizeSecurityGroupEgress req := &ec2.AuthorizeSecurityGroupEgressRequest{
GroupID: group.GroupID,
IPPermissions: add,
}
err = ec2conn.AuthorizeSecurityGroupEgress(req)
} else {
req := &ec2.AuthorizeSecurityGroupIngressRequest{
GroupID: group.GroupID,
IPPermissions: add,
}
err = ec2conn.AuthorizeSecurityGroupIngress(req)
} }
log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v", if err != nil {
group, ruleset, add)
if _, err := authorize(group, add); err != nil {
return fmt.Errorf( return fmt.Errorf(
"Error authorizing security group %s rules: %s", "Error authorizing security group %s rules: %s",
ruleset, err) ruleset, err)
@ -431,7 +464,6 @@ func resourceAwsSecurityGroupUpdateRules(
} }
} }
} }
return nil return nil
} }
@ -439,10 +471,12 @@ func resourceAwsSecurityGroupUpdateRules(
// a security group. // a security group.
func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
sgs := []ec2.SecurityGroup{ec2.SecurityGroup{Id: id}} req := &ec2.DescribeSecurityGroupsRequest{
resp, err := conn.SecurityGroups(sgs, nil) GroupIDs: []string{id},
}
resp, err := conn.DescribeSecurityGroups(req)
if err != nil { if err != nil {
if ec2err, ok := err.(*ec2.Error); ok { if ec2err, ok := err.(aws.APIError); ok {
if ec2err.Code == "InvalidSecurityGroupID.NotFound" || if ec2err.Code == "InvalidSecurityGroupID.NotFound" ||
ec2err.Code == "InvalidGroup.NotFound" { ec2err.Code == "InvalidGroup.NotFound" {
resp = nil resp = nil
@ -460,7 +494,7 @@ func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return nil, "", nil return nil, "", nil
} }
group := &resp.Groups[0] group := resp.SecurityGroups[0]
return group, "exists", nil return group, "exists", nil
} }
} }

View File

@ -2,16 +2,18 @@ package aws
import ( import (
"fmt" "fmt"
"log"
"reflect" "reflect"
"testing" "testing"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/goamz/ec2"
) )
func TestAccAWSSecurityGroup_normal(t *testing.T) { func TestAccAWSSecurityGroup_normal(t *testing.T) {
var group ec2.SecurityGroupInfo var group ec2.SecurityGroup
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -44,7 +46,7 @@ func TestAccAWSSecurityGroup_normal(t *testing.T) {
} }
func TestAccAWSSecurityGroup_self(t *testing.T) { func TestAccAWSSecurityGroup_self(t *testing.T) {
var group ec2.SecurityGroupInfo var group ec2.SecurityGroup
checkSelf := func(s *terraform.State) (err error) { checkSelf := func(s *terraform.State) (err error) {
defer func() { defer func() {
@ -53,7 +55,7 @@ func TestAccAWSSecurityGroup_self(t *testing.T) {
} }
}() }()
if group.IPPerms[0].SourceGroups[0].Id != group.Id { if *group.IPPermissions[0].UserIDGroupPairs[0].GroupID != *group.GroupID {
return fmt.Errorf("bad: %#v", group) return fmt.Errorf("bad: %#v", group)
} }
@ -89,10 +91,10 @@ func TestAccAWSSecurityGroup_self(t *testing.T) {
} }
func TestAccAWSSecurityGroup_vpc(t *testing.T) { func TestAccAWSSecurityGroup_vpc(t *testing.T) {
var group ec2.SecurityGroupInfo var group ec2.SecurityGroup
testCheck := func(*terraform.State) error { testCheck := func(*terraform.State) error {
if group.VpcId == "" { if *group.VPCID == "" {
return fmt.Errorf("should have vpc ID") return fmt.Errorf("should have vpc ID")
} }
@ -141,7 +143,7 @@ func TestAccAWSSecurityGroup_vpc(t *testing.T) {
} }
func TestAccAWSSecurityGroup_MultiIngress(t *testing.T) { func TestAccAWSSecurityGroup_MultiIngress(t *testing.T) {
var group ec2.SecurityGroupInfo var group ec2.SecurityGroup
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -159,7 +161,7 @@ func TestAccAWSSecurityGroup_MultiIngress(t *testing.T) {
} }
func TestAccAWSSecurityGroup_Change(t *testing.T) { func TestAccAWSSecurityGroup_Change(t *testing.T) {
var group ec2.SecurityGroupInfo var group ec2.SecurityGroup
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -184,30 +186,27 @@ func TestAccAWSSecurityGroup_Change(t *testing.T) {
} }
func testAccCheckAWSSecurityGroupDestroy(s *terraform.State) error { func testAccCheckAWSSecurityGroupDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_security_group" { if rs.Type != "aws_security_group" {
continue continue
} }
sgs := []ec2.SecurityGroup{
ec2.SecurityGroup{
Id: rs.Primary.ID,
},
}
// Retrieve our group // Retrieve our group
resp, err := conn.SecurityGroups(sgs, nil) req := &ec2.DescribeSecurityGroupsRequest{
GroupIDs: []string{rs.Primary.ID},
}
resp, err := conn.DescribeSecurityGroups(req)
if err == nil { if err == nil {
if len(resp.Groups) > 0 && resp.Groups[0].Id == rs.Primary.ID { if len(resp.SecurityGroups) > 0 && *resp.SecurityGroups[0].GroupID == rs.Primary.ID {
return fmt.Errorf("Security Group (%s) still exists.", rs.Primary.ID) return fmt.Errorf("Security Group (%s) still exists.", rs.Primary.ID)
} }
return nil return nil
} }
ec2err, ok := err.(*ec2.Error) ec2err, ok := err.(aws.APIError)
if !ok { if !ok {
return err return err
} }
@ -220,7 +219,7 @@ func testAccCheckAWSSecurityGroupDestroy(s *terraform.State) error {
return nil return nil
} }
func testAccCheckAWSSecurityGroupExists(n string, group *ec2.SecurityGroupInfo) resource.TestCheckFunc { func testAccCheckAWSSecurityGroupExists(n string, group *ec2.SecurityGroup) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n] rs, ok := s.RootModule().Resources[n]
if !ok { if !ok {
@ -231,20 +230,19 @@ func testAccCheckAWSSecurityGroupExists(n string, group *ec2.SecurityGroupInfo)
return fmt.Errorf("No Security Group is set") return fmt.Errorf("No Security Group is set")
} }
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
sgs := []ec2.SecurityGroup{ req := &ec2.DescribeSecurityGroupsRequest{
ec2.SecurityGroup{ GroupIDs: []string{rs.Primary.ID},
Id: rs.Primary.ID,
},
} }
resp, err := conn.SecurityGroups(sgs, nil) resp, err := conn.DescribeSecurityGroups(req)
if err != nil { if err != nil {
return err return err
} }
if len(resp.Groups) > 0 && resp.Groups[0].Id == rs.Primary.ID { if len(resp.SecurityGroups) > 0 && *resp.SecurityGroups[0].GroupID == rs.Primary.ID {
*group = resp.Groups[0] log.Printf("\n==\n===\nfound group\n===\n==\n")
*group = resp.SecurityGroups[0]
return nil return nil
} }
@ -253,32 +251,32 @@ func testAccCheckAWSSecurityGroupExists(n string, group *ec2.SecurityGroupInfo)
} }
} }
func testAccCheckAWSSecurityGroupAttributes(group *ec2.SecurityGroupInfo) resource.TestCheckFunc { func testAccCheckAWSSecurityGroupAttributes(group *ec2.SecurityGroup) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
p := ec2.IPPerm{ p := ec2.IPPermission{
FromPort: 80, FromPort: aws.Integer(80),
ToPort: 8000, ToPort: aws.Integer(8000),
Protocol: "tcp", IPProtocol: aws.String("tcp"),
SourceIPs: []string{"10.0.0.0/8"}, IPRanges: []ec2.IPRange{ec2.IPRange{aws.String("10.0.0.0/8")}},
} }
if group.Name != "terraform_acceptance_test_example" { if *group.GroupName != "terraform_acceptance_test_example" {
return fmt.Errorf("Bad name: %s", group.Name) return fmt.Errorf("Bad name: %s", *group.GroupName)
} }
if group.Description != "Used in the terraform acceptance tests" { if *group.Description != "Used in the terraform acceptance tests" {
return fmt.Errorf("Bad description: %s", group.Description) return fmt.Errorf("Bad description: %s", *group.Description)
} }
if len(group.IPPerms) == 0 { if len(group.IPPermissions) == 0 {
return fmt.Errorf("No IPPerms") return fmt.Errorf("No IPPerms")
} }
// Compare our ingress // Compare our ingress
if !reflect.DeepEqual(group.IPPerms[0], p) { if !reflect.DeepEqual(group.IPPermissions[0], p) {
return fmt.Errorf( return fmt.Errorf(
"Got:\n\n%#v\n\nExpected:\n\n%#v\n", "Got:\n\n%#v\n\nExpected:\n\n%#v\n",
group.IPPerms[0], group.IPPermissions[0],
p) p)
} }
@ -287,7 +285,7 @@ func testAccCheckAWSSecurityGroupAttributes(group *ec2.SecurityGroupInfo) resour
} }
func TestAccAWSSecurityGroup_tags(t *testing.T) { func TestAccAWSSecurityGroup_tags(t *testing.T) {
var group ec2.SecurityGroupInfo var group ec2.SecurityGroup
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -298,7 +296,7 @@ func TestAccAWSSecurityGroup_tags(t *testing.T) {
Config: testAccAWSSecurityGroupConfigTags, Config: testAccAWSSecurityGroupConfigTags,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSecurityGroupExists("aws_security_group.foo", &group), testAccCheckAWSSecurityGroupExists("aws_security_group.foo", &group),
testAccCheckTags(&group.Tags, "foo", "bar"), testAccCheckTagsSDK(&group.Tags, "foo", "bar"),
), ),
}, },
@ -306,56 +304,56 @@ func TestAccAWSSecurityGroup_tags(t *testing.T) {
Config: testAccAWSSecurityGroupConfigTagsUpdate, Config: testAccAWSSecurityGroupConfigTagsUpdate,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSecurityGroupExists("aws_security_group.foo", &group), testAccCheckAWSSecurityGroupExists("aws_security_group.foo", &group),
testAccCheckTags(&group.Tags, "foo", ""), testAccCheckTagsSDK(&group.Tags, "foo", ""),
testAccCheckTags(&group.Tags, "bar", "baz"), testAccCheckTagsSDK(&group.Tags, "bar", "baz"),
), ),
}, },
}, },
}) })
} }
func testAccCheckAWSSecurityGroupAttributesChanged(group *ec2.SecurityGroupInfo) resource.TestCheckFunc { func testAccCheckAWSSecurityGroupAttributesChanged(group *ec2.SecurityGroup) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
p := []ec2.IPPerm{ p := []ec2.IPPermission{
ec2.IPPerm{ ec2.IPPermission{
FromPort: 80, FromPort: aws.Integer(80),
ToPort: 9000, ToPort: aws.Integer(9000),
Protocol: "tcp", IPProtocol: aws.String("tcp"),
SourceIPs: []string{"10.0.0.0/8"}, IPRanges: []ec2.IPRange{ec2.IPRange{aws.String("10.0.0.0/8")}},
}, },
ec2.IPPerm{ ec2.IPPermission{
FromPort: 80, FromPort: aws.Integer(80),
ToPort: 8000, ToPort: aws.Integer(8000),
Protocol: "tcp", IPProtocol: aws.String("tcp"),
SourceIPs: []string{"0.0.0.0/0", "10.0.0.0/8"}, IPRanges: []ec2.IPRange{ec2.IPRange{aws.String("0.0.0.0/0")}, ec2.IPRange{aws.String("10.0.0.0/8")}},
}, },
} }
if group.Name != "terraform_acceptance_test_example" { if *group.GroupName != "terraform_acceptance_test_example" {
return fmt.Errorf("Bad name: %s", group.Name) return fmt.Errorf("Bad name: %s", *group.GroupName)
} }
if group.Description != "Used in the terraform acceptance tests" { if *group.Description != "Used in the terraform acceptance tests" {
return fmt.Errorf("Bad description: %s", group.Description) return fmt.Errorf("Bad description: %s", *group.Description)
} }
// Compare our ingress // Compare our ingress
if len(group.IPPerms) != 2 { if len(group.IPPermissions) != 2 {
return fmt.Errorf( return fmt.Errorf(
"Got:\n\n%#v\n\nExpected:\n\n%#v\n", "Got:\n\n%#v\n\nExpected:\n\n%#v\n",
group.IPPerms, group.IPPermissions,
p) p)
} }
if group.IPPerms[0].ToPort == 8000 { if *group.IPPermissions[0].ToPort == 8000 {
group.IPPerms[1], group.IPPerms[0] = group.IPPermissions[1], group.IPPermissions[0] =
group.IPPerms[0], group.IPPerms[1] group.IPPermissions[0], group.IPPermissions[1]
} }
if !reflect.DeepEqual(group.IPPerms, p) { if !reflect.DeepEqual(group.IPPermissions, p) {
return fmt.Errorf( return fmt.Errorf(
"Got:\n\n%#v\n\nExpected:\n\n%#v\n", "Got:\n\n%#v\n\nExpected:\n\n%#v\n",
group.IPPerms, group.IPPermissions,
p) p)
} }

View File

@ -68,10 +68,10 @@ func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error {
// Get the ID and store it // Get the ID and store it
subnet := resp.Subnet subnet := resp.Subnet
d.SetId(*subnet.SubnetID) d.SetId(*subnet.SubnetID)
log.Printf("[INFO] Subnet ID: %s", subnet.SubnetID) log.Printf("[INFO] Subnet ID: %s", *subnet.SubnetID)
// Wait for the Subnet to become available // Wait for the Subnet to become available
log.Printf("[DEBUG] Waiting for subnet (%s) to become available", subnet.SubnetID) log.Printf("[DEBUG] Waiting for subnet (%s) to become available", *subnet.SubnetID)
stateConf := &resource.StateChangeConf{ stateConf := &resource.StateChangeConf{
Pending: []string{"pending"}, Pending: []string{"pending"},
Target: "available", Target: "available",

View File

@ -15,11 +15,11 @@ func TestAccAWSSubnet(t *testing.T) {
testCheck := func(*terraform.State) error { testCheck := func(*terraform.State) error {
if *v.CIDRBlock != "10.1.1.0/24" { if *v.CIDRBlock != "10.1.1.0/24" {
return fmt.Errorf("bad cidr: %s", v.CIDRBlock) return fmt.Errorf("bad cidr: %s", *v.CIDRBlock)
} }
if *v.MapPublicIPOnLaunch != true { if *v.MapPublicIPOnLaunch != true {
return fmt.Errorf("bad MapPublicIpOnLaunch: %t", v.MapPublicIPOnLaunch) return fmt.Errorf("bad MapPublicIpOnLaunch: %t", *v.MapPublicIPOnLaunch)
} }
return nil return nil

View File

@ -5,9 +5,10 @@ import (
"log" "log"
"time" "time"
"github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
) )
func resourceAwsVpcPeeringConnection() *schema.Resource { func resourceAwsVpcPeeringConnection() *schema.Resource {
@ -19,9 +20,10 @@ func resourceAwsVpcPeeringConnection() *schema.Resource {
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"peer_owner_id": &schema.Schema{ "peer_owner_id": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
ForceNew: true, ForceNew: true,
DefaultFunc: schema.EnvDefaultFunc("AWS_ACCOUNT_ID", nil),
}, },
"peer_vpc_id": &schema.Schema{ "peer_vpc_id": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
@ -39,23 +41,23 @@ func resourceAwsVpcPeeringConnection() *schema.Resource {
} }
func resourceAwsVpcPeeringCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsVpcPeeringCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
// Create the vpc peering connection // Create the vpc peering connection
createOpts := &ec2.CreateVpcPeeringConnection{ createOpts := &ec2.CreateVPCPeeringConnectionRequest{
PeerOwnerId: d.Get("peer_owner_id").(string), PeerOwnerID: aws.String(d.Get("peer_owner_id").(string)),
PeerVpcId: d.Get("peer_vpc_id").(string), PeerVPCID: aws.String(d.Get("peer_vpc_id").(string)),
VpcId: d.Get("vpc_id").(string), VPCID: aws.String(d.Get("vpc_id").(string)),
} }
log.Printf("[DEBUG] VpcPeeringCreate create config: %#v", createOpts) log.Printf("[DEBUG] VpcPeeringCreate create config: %#v", createOpts)
resp, err := ec2conn.CreateVpcPeeringConnection(createOpts) resp, err := ec2conn.CreateVPCPeeringConnection(createOpts)
if err != nil { if err != nil {
return fmt.Errorf("Error creating vpc peering connection: %s", err) return fmt.Errorf("Error creating vpc peering connection: %s", err)
} }
// Get the ID and store it // Get the ID and store it
rt := &resp.VpcPeeringConnection rt := resp.VPCPeeringConnection
d.SetId(rt.VpcPeeringConnectionId) d.SetId(*rt.VPCPeeringConnectionID)
log.Printf("[INFO] Vpc Peering Connection ID: %s", d.Id()) log.Printf("[INFO] Vpc Peering Connection ID: %s", d.Id())
// Wait for the vpc peering connection to become available // Wait for the vpc peering connection to become available
@ -78,7 +80,7 @@ func resourceAwsVpcPeeringCreate(d *schema.ResourceData, meta interface{}) error
} }
func resourceAwsVpcPeeringRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsVpcPeeringRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
pcRaw, _, err := resourceAwsVpcPeeringConnectionStateRefreshFunc(ec2conn, d.Id())() pcRaw, _, err := resourceAwsVpcPeeringConnectionStateRefreshFunc(ec2conn, d.Id())()
if err != nil { if err != nil {
return err return err
@ -88,20 +90,20 @@ func resourceAwsVpcPeeringRead(d *schema.ResourceData, meta interface{}) error {
return nil return nil
} }
pc := pcRaw.(*ec2.VpcPeeringConnection) pc := pcRaw.(*ec2.VPCPeeringConnection)
d.Set("peer_owner_id", pc.AccepterVpcInfo.OwnerId) d.Set("peer_owner_id", pc.AccepterVPCInfo.OwnerID)
d.Set("peer_vpc_id", pc.AccepterVpcInfo.VpcId) d.Set("peer_vpc_id", pc.AccepterVPCInfo.VPCID)
d.Set("vpc_id", pc.RequesterVpcInfo.VpcId) d.Set("vpc_id", pc.RequesterVPCInfo.VPCID)
d.Set("tags", tagsToMap(pc.Tags)) d.Set("tags", tagsToMapSDK(pc.Tags))
return nil return nil
} }
func resourceAwsVpcPeeringUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAwsVpcPeeringUpdate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
if err := setTags(ec2conn, d); err != nil { if err := setTagsSDK(ec2conn, d); err != nil {
return err return err
} else { } else {
d.SetPartial("tags") d.SetPartial("tags")
@ -111,9 +113,12 @@ func resourceAwsVpcPeeringUpdate(d *schema.ResourceData, meta interface{}) error
} }
func resourceAwsVpcPeeringDelete(d *schema.ResourceData, meta interface{}) error { func resourceAwsVpcPeeringDelete(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn ec2conn := meta.(*AWSClient).awsEC2conn
_, err := ec2conn.DeleteVpcPeeringConnection(d.Id()) _, err := ec2conn.DeleteVPCPeeringConnection(
&ec2.DeleteVPCPeeringConnectionRequest{
VPCPeeringConnectionID: aws.String(d.Id()),
})
return err return err
} }
@ -122,9 +127,11 @@ func resourceAwsVpcPeeringDelete(d *schema.ResourceData, meta interface{}) error
func resourceAwsVpcPeeringConnectionStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { func resourceAwsVpcPeeringConnectionStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
resp, err := conn.DescribeVpcPeeringConnection([]string{id}, ec2.NewFilter()) resp, err := conn.DescribeVPCPeeringConnections(&ec2.DescribeVPCPeeringConnectionsRequest{
VPCPeeringConnectionIDs: []string{id},
})
if err != nil { if err != nil {
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidVpcPeeringConnectionID.NotFound" { if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpcPeeringConnectionID.NotFound" {
resp = nil resp = nil
} else { } else {
log.Printf("Error on VpcPeeringConnectionStateRefresh: %s", err) log.Printf("Error on VpcPeeringConnectionStateRefresh: %s", err)
@ -138,7 +145,7 @@ func resourceAwsVpcPeeringConnectionStateRefreshFunc(conn *ec2.EC2, id string) r
return nil, "", nil return nil, "", nil
} }
pc := &resp.VpcPeeringConnections[0] pc := &resp.VPCPeeringConnections[0]
return pc, "ready", nil return pc, "ready", nil
} }

View File

@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/goamz/ec2"
) )
func TestAccAWSVPCPeeringConnection_normal(t *testing.T) { func TestAccAWSVPCPeeringConnection_normal(t *testing.T) {
@ -28,17 +28,20 @@ func TestAccAWSVPCPeeringConnection_normal(t *testing.T) {
} }
func testAccCheckAWSVpcPeeringConnectionDestroy(s *terraform.State) error { func testAccCheckAWSVpcPeeringConnectionDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_vpc_peering_connection" { if rs.Type != "aws_vpc_peering_connection" {
continue continue
} }
describe, err := conn.DescribeVpcPeeringConnection([]string{rs.Primary.ID}, ec2.NewFilter()) describe, err := conn.DescribeVPCPeeringConnections(
&ec2.DescribeVPCPeeringConnectionsRequest{
VPCPeeringConnectionIDs: []string{rs.Primary.ID},
})
if err == nil { if err == nil {
if len(describe.VpcPeeringConnections) != 0 { if len(describe.VPCPeeringConnections) != 0 {
return fmt.Errorf("vpc peering connection still exists") return fmt.Errorf("vpc peering connection still exists")
} }
} }
@ -68,11 +71,10 @@ resource "aws_vpc" "foo" {
} }
resource "aws_vpc" "bar" { resource "aws_vpc" "bar" {
cidr_block = "10.0.1.0/16" cidr_block = "10.1.0.0/16"
} }
resource "aws_vpc_peering_connection" "foo" { resource "aws_vpc_peering_connection" "foo" {
peer_owner_id = "12345"
vpc_id = "${aws_vpc.foo.id}" vpc_id = "${aws_vpc.foo.id}"
peer_vpc_id = "${aws_vpc.bar.id}" peer_vpc_id = "${aws_vpc.bar.id}"
} }

View File

@ -4,6 +4,7 @@ import (
"strings" "strings"
"github.com/hashicorp/aws-sdk-go/aws" "github.com/hashicorp/aws-sdk-go/aws"
awsEC2 "github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/aws-sdk-go/gen/elb" "github.com/hashicorp/aws-sdk-go/gen/elb"
"github.com/hashicorp/aws-sdk-go/gen/rds" "github.com/hashicorp/aws-sdk-go/gen/rds"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
@ -39,15 +40,15 @@ func expandListeners(configured []interface{}) ([]elb.Listener, error) {
// Takes the result of flatmap.Expand for an array of ingress/egress // Takes the result of flatmap.Expand for an array of ingress/egress
// security group rules and returns EC2 API compatible objects // security group rules and returns EC2 API compatible objects
func expandIPPerms(id string, configured []interface{}) []ec2.IPPerm { func expandIPPerms(id string, configured []interface{}) []awsEC2.IPPermission {
perms := make([]ec2.IPPerm, len(configured)) perms := make([]awsEC2.IPPermission, len(configured))
for i, mRaw := range configured { for i, mRaw := range configured {
var perm ec2.IPPerm var perm awsEC2.IPPermission
m := mRaw.(map[string]interface{}) m := mRaw.(map[string]interface{})
perm.FromPort = m["from_port"].(int) perm.FromPort = aws.Integer(m["from_port"].(int))
perm.ToPort = m["to_port"].(int) perm.ToPort = aws.Integer(m["to_port"].(int))
perm.Protocol = m["protocol"].(string) perm.IPProtocol = aws.String(m["protocol"].(string))
var groups []string var groups []string
if raw, ok := m["security_groups"]; ok { if raw, ok := m["security_groups"]; ok {
@ -61,25 +62,25 @@ func expandIPPerms(id string, configured []interface{}) []ec2.IPPerm {
} }
if len(groups) > 0 { if len(groups) > 0 {
perm.SourceGroups = make([]ec2.UserSecurityGroup, len(groups)) perm.UserIDGroupPairs = make([]awsEC2.UserIDGroupPair, len(groups))
for i, name := range groups { for i, name := range groups {
ownerId, id := "", name ownerId, id := "", name
if items := strings.Split(id, "/"); len(items) > 1 { if items := strings.Split(id, "/"); len(items) > 1 {
ownerId, id = items[0], items[1] ownerId, id = items[0], items[1]
} }
perm.SourceGroups[i] = ec2.UserSecurityGroup{ perm.UserIDGroupPairs[i] = awsEC2.UserIDGroupPair{
Id: id, GroupID: aws.String(id),
OwnerId: ownerId, UserID: aws.String(ownerId),
} }
} }
} }
if raw, ok := m["cidr_blocks"]; ok { if raw, ok := m["cidr_blocks"]; ok {
list := raw.([]interface{}) list := raw.([]interface{})
perm.SourceIPs = make([]string, len(list)) perm.IPRanges = make([]awsEC2.IPRange, len(list))
for i, v := range list { for i, v := range list {
perm.SourceIPs[i] = v.(string) perm.IPRanges[i] = awsEC2.IPRange{aws.String(v.(string))}
} }
} }
@ -111,31 +112,6 @@ func expandParameters(configured []interface{}) ([]rds.Parameter, error) {
return parameters, nil return parameters, nil
} }
// Flattens an array of ipPerms into a list of primitives that
// flatmap.Flatten() can handle
func flattenIPPerms(list []ec2.IPPerm) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))
for _, perm := range list {
n := make(map[string]interface{})
n["from_port"] = perm.FromPort
n["protocol"] = perm.Protocol
n["to_port"] = perm.ToPort
if len(perm.SourceIPs) > 0 {
n["cidr_blocks"] = perm.SourceIPs
}
if v := flattenSecurityGroups(perm.SourceGroups); len(v) > 0 {
n["security_groups"] = v
}
result = append(result, n)
}
return result
}
// Flattens a health check into something that flatmap.Flatten() // Flattens a health check into something that flatmap.Flatten()
// can handle // can handle
func flattenHealthCheck(check *elb.HealthCheck) []map[string]interface{} { func flattenHealthCheck(check *elb.HealthCheck) []map[string]interface{} {
@ -162,6 +138,15 @@ func flattenSecurityGroups(list []ec2.UserSecurityGroup) []string {
return result return result
} }
// Flattens an array of UserSecurityGroups into a []string
func flattenSecurityGroupsSDK(list []awsEC2.UserIDGroupPair) []string {
result := make([]string, 0, len(list))
for _, g := range list {
result = append(result, *g.GroupID)
}
return result
}
// Flattens an array of Instances into a []string // Flattens an array of Instances into a []string
func flattenInstances(list []elb.Instance) []string { func flattenInstances(list []elb.Instance) []string {
result := make([]string, 0, len(list)) result := make([]string, 0, len(list))

View File

@ -5,12 +5,12 @@ import (
"testing" "testing"
"github.com/hashicorp/aws-sdk-go/aws" "github.com/hashicorp/aws-sdk-go/aws"
awsEC2 "github.com/hashicorp/aws-sdk-go/gen/ec2"
"github.com/hashicorp/aws-sdk-go/gen/elb" "github.com/hashicorp/aws-sdk-go/gen/elb"
"github.com/hashicorp/aws-sdk-go/gen/rds" "github.com/hashicorp/aws-sdk-go/gen/rds"
"github.com/hashicorp/terraform/flatmap" "github.com/hashicorp/terraform/flatmap"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
) )
// Returns test configuration // Returns test configuration
@ -61,120 +61,58 @@ func TestExpandIPPerms(t *testing.T) {
} }
perms := expandIPPerms("foo", expanded) perms := expandIPPerms("foo", expanded)
expected := []ec2.IPPerm{ expected := []awsEC2.IPPermission{
ec2.IPPerm{ awsEC2.IPPermission{
Protocol: "icmp", IPProtocol: aws.String("icmp"),
FromPort: 1, FromPort: aws.Integer(1),
ToPort: -1, ToPort: aws.Integer(-1),
SourceIPs: []string{"0.0.0.0/0"}, IPRanges: []awsEC2.IPRange{awsEC2.IPRange{aws.String("0.0.0.0/0")}},
SourceGroups: []ec2.UserSecurityGroup{ UserIDGroupPairs: []awsEC2.UserIDGroupPair{
ec2.UserSecurityGroup{ awsEC2.UserIDGroupPair{
OwnerId: "foo", UserID: aws.String("foo"),
Id: "sg-22222", GroupID: aws.String("sg-22222"),
}, },
ec2.UserSecurityGroup{ awsEC2.UserIDGroupPair{
Id: "sg-11111", GroupID: aws.String("sg-22222"),
}, },
}, },
}, },
ec2.IPPerm{ awsEC2.IPPermission{
Protocol: "icmp", IPProtocol: aws.String("icmp"),
FromPort: 1, FromPort: aws.Integer(1),
ToPort: -1, ToPort: aws.Integer(-1),
SourceGroups: []ec2.UserSecurityGroup{ UserIDGroupPairs: []awsEC2.UserIDGroupPair{
ec2.UserSecurityGroup{ awsEC2.UserIDGroupPair{
Id: "foo", UserID: aws.String("foo"),
}, },
}, },
}, },
} }
if !reflect.DeepEqual(perms, expected) { exp := expected[0]
perm := perms[0]
if *exp.FromPort != *perm.FromPort {
t.Fatalf( t.Fatalf(
"Got:\n\n%#v\n\nExpected:\n\n%#v\n", "Got:\n\n%#v\n\nExpected:\n\n%#v\n",
perms[0], *perm.FromPort,
expected) *exp.FromPort)
} }
} if *exp.IPRanges[0].CIDRIP != *perm.IPRanges[0].CIDRIP {
t.Fatalf(
func TestFlattenIPPerms(t *testing.T) { "Got:\n\n%#v\n\nExpected:\n\n%#v\n",
cases := []struct { *perm.IPRanges[0].CIDRIP,
Input []ec2.IPPerm *exp.IPRanges[0].CIDRIP)
Output []map[string]interface{}
}{
{
Input: []ec2.IPPerm{
ec2.IPPerm{
Protocol: "icmp",
FromPort: 1,
ToPort: -1,
SourceIPs: []string{"0.0.0.0/0"},
SourceGroups: []ec2.UserSecurityGroup{
ec2.UserSecurityGroup{
Id: "sg-11111",
},
},
},
},
Output: []map[string]interface{}{
map[string]interface{}{
"protocol": "icmp",
"from_port": 1,
"to_port": -1,
"cidr_blocks": []string{"0.0.0.0/0"},
"security_groups": []string{"sg-11111"},
},
},
},
{
Input: []ec2.IPPerm{
ec2.IPPerm{
Protocol: "icmp",
FromPort: 1,
ToPort: -1,
SourceIPs: []string{"0.0.0.0/0"},
SourceGroups: nil,
},
},
Output: []map[string]interface{}{
map[string]interface{}{
"protocol": "icmp",
"from_port": 1,
"to_port": -1,
"cidr_blocks": []string{"0.0.0.0/0"},
},
},
},
{
Input: []ec2.IPPerm{
ec2.IPPerm{
Protocol: "icmp",
FromPort: 1,
ToPort: -1,
SourceIPs: nil,
},
},
Output: []map[string]interface{}{
map[string]interface{}{
"protocol": "icmp",
"from_port": 1,
"to_port": -1,
},
},
},
} }
for _, tc := range cases { if *exp.UserIDGroupPairs[0].UserID != *perm.UserIDGroupPairs[0].UserID {
output := flattenIPPerms(tc.Input) t.Fatalf(
if !reflect.DeepEqual(output, tc.Output) { "Got:\n\n%#v\n\nExpected:\n\n%#v\n",
t.Fatalf("Input:\n\n%#v\n\nOutput:\n\n%#v", tc.Input, output) *perm.UserIDGroupPairs[0].UserID,
} *exp.UserIDGroupPairs[0].UserID)
} }
} }
func TestExpandListeners(t *testing.T) { func TestExpandListeners(t *testing.T) {

View File

@ -30,22 +30,25 @@ func Provider() terraform.ResourceProvider {
"timeout": &schema.Schema{ "timeout": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Required: true, Required: true,
DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_TIMEOUT", 180), DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_TIMEOUT", 300),
}, },
}, },
ResourcesMap: map[string]*schema.Resource{ ResourcesMap: map[string]*schema.Resource{
"cloudstack_disk": resourceCloudStackDisk(), "cloudstack_disk": resourceCloudStackDisk(),
"cloudstack_egress_firewall": resourceCloudStackEgressFirewall(), "cloudstack_egress_firewall": resourceCloudStackEgressFirewall(),
"cloudstack_firewall": resourceCloudStackFirewall(), "cloudstack_firewall": resourceCloudStackFirewall(),
"cloudstack_instance": resourceCloudStackInstance(), "cloudstack_instance": resourceCloudStackInstance(),
"cloudstack_ipaddress": resourceCloudStackIPAddress(), "cloudstack_ipaddress": resourceCloudStackIPAddress(),
"cloudstack_network": resourceCloudStackNetwork(), "cloudstack_network": resourceCloudStackNetwork(),
"cloudstack_network_acl": resourceCloudStackNetworkACL(), "cloudstack_network_acl": resourceCloudStackNetworkACL(),
"cloudstack_network_acl_rule": resourceCloudStackNetworkACLRule(), "cloudstack_network_acl_rule": resourceCloudStackNetworkACLRule(),
"cloudstack_nic": resourceCloudStackNIC(), "cloudstack_nic": resourceCloudStackNIC(),
"cloudstack_port_forward": resourceCloudStackPortForward(), "cloudstack_port_forward": resourceCloudStackPortForward(),
"cloudstack_vpc": resourceCloudStackVPC(), "cloudstack_vpc": resourceCloudStackVPC(),
"cloudstack_vpn_connection": resourceCloudStackVPNConnection(),
"cloudstack_vpn_customer_gateway": resourceCloudStackVPNCustomerGateway(),
"cloudstack_vpn_gateway": resourceCloudStackVPNGateway(),
}, },
ConfigureFunc: providerConfigure, ConfigureFunc: providerConfigure,

View File

@ -51,7 +51,8 @@ var CLOUDSTACK_NETWORK_1_OFFERING = ""
var CLOUDSTACK_NETWORK_1_IPADDRESS = "" var CLOUDSTACK_NETWORK_1_IPADDRESS = ""
var CLOUDSTACK_NETWORK_2 = "" var CLOUDSTACK_NETWORK_2 = ""
var CLOUDSTACK_NETWORK_2_IPADDRESS = "" var CLOUDSTACK_NETWORK_2_IPADDRESS = ""
var CLOUDSTACK_VPC_CIDR = "" var CLOUDSTACK_VPC_CIDR_1 = ""
var CLOUDSTACK_VPC_CIDR_2 = ""
var CLOUDSTACK_VPC_OFFERING = "" var CLOUDSTACK_VPC_OFFERING = ""
var CLOUDSTACK_VPC_NETWORK_CIDR = "" var CLOUDSTACK_VPC_NETWORK_CIDR = ""
var CLOUDSTACK_VPC_NETWORK_OFFERING = "" var CLOUDSTACK_VPC_NETWORK_OFFERING = ""

View File

@ -95,18 +95,18 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{})
return e.Error() return e.Error()
} }
// Retrieve the template UUID
templateid, e := retrieveUUID(cs, "template", d.Get("template").(string))
if e != nil {
return e.Error()
}
// Retrieve the zone object // Retrieve the zone object
zone, _, err := cs.Zone.GetZoneByName(d.Get("zone").(string)) zone, _, err := cs.Zone.GetZoneByName(d.Get("zone").(string))
if err != nil { if err != nil {
return err return err
} }
// Retrieve the template UUID
templateid, e := retrieveTemplateUUID(cs, zone.Id, d.Get("template").(string))
if e != nil {
return e.Error()
}
// Create a new parameter struct // Create a new parameter struct
p := cs.VirtualMachine.NewDeployVirtualMachineParams(serviceofferingid, templateid, zone.Id) p := cs.VirtualMachine.NewDeployVirtualMachineParams(serviceofferingid, templateid, zone.Id)

View File

@ -132,6 +132,6 @@ resource "cloudstack_vpc" "foobar" {
resource "cloudstack_ipaddress" "foo" { resource "cloudstack_ipaddress" "foo" {
vpc = "${cloudstack_vpc.foobar.name}" vpc = "${cloudstack_vpc.foobar.name}"
}`, }`,
CLOUDSTACK_VPC_CIDR, CLOUDSTACK_VPC_CIDR_1,
CLOUDSTACK_VPC_OFFERING, CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE) CLOUDSTACK_ZONE)

View File

@ -196,7 +196,7 @@ resource "cloudstack_network_acl_rule" "foo" {
traffic_type = "ingress" traffic_type = "ingress"
} }
}`, }`,
CLOUDSTACK_VPC_CIDR, CLOUDSTACK_VPC_CIDR_1,
CLOUDSTACK_VPC_OFFERING, CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE) CLOUDSTACK_ZONE)
@ -233,6 +233,6 @@ resource "cloudstack_network_acl_rule" "foo" {
traffic_type = "egress" traffic_type = "egress"
} }
}`, }`,
CLOUDSTACK_VPC_CIDR, CLOUDSTACK_VPC_CIDR_1,
CLOUDSTACK_VPC_OFFERING, CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE) CLOUDSTACK_ZONE)

View File

@ -112,6 +112,6 @@ resource "cloudstack_network_acl" "foo" {
description = "terraform-acl-text" description = "terraform-acl-text"
vpc = "${cloudstack_vpc.foobar.name}" vpc = "${cloudstack_vpc.foobar.name}"
}`, }`,
CLOUDSTACK_VPC_CIDR, CLOUDSTACK_VPC_CIDR_1,
CLOUDSTACK_VPC_OFFERING, CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE) CLOUDSTACK_ZONE)

View File

@ -186,7 +186,7 @@ resource "cloudstack_network" "foo" {
aclid = "${cloudstack_network_acl.foo.id}" aclid = "${cloudstack_network_acl.foo.id}"
zone = "${cloudstack_vpc.foobar.zone}" zone = "${cloudstack_vpc.foobar.zone}"
}`, }`,
CLOUDSTACK_VPC_CIDR, CLOUDSTACK_VPC_CIDR_1,
CLOUDSTACK_VPC_OFFERING, CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE, CLOUDSTACK_ZONE,
CLOUDSTACK_VPC_NETWORK_CIDR, CLOUDSTACK_VPC_NETWORK_CIDR,

View File

@ -72,8 +72,8 @@ func testAccCheckCloudStackVPCAttributes(
return fmt.Errorf("Bad display text: %s", vpc.Displaytext) return fmt.Errorf("Bad display text: %s", vpc.Displaytext)
} }
if vpc.Cidr != CLOUDSTACK_VPC_CIDR { if vpc.Cidr != CLOUDSTACK_VPC_CIDR_1 {
return fmt.Errorf("Bad VPC offering: %s", vpc.Cidr) return fmt.Errorf("Bad VPC CIDR: %s", vpc.Cidr)
} }
return nil return nil
@ -113,6 +113,6 @@ resource "cloudstack_vpc" "foo" {
vpc_offering = "%s" vpc_offering = "%s"
zone = "%s" zone = "%s"
}`, }`,
CLOUDSTACK_VPC_CIDR, CLOUDSTACK_VPC_CIDR_1,
CLOUDSTACK_VPC_OFFERING, CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE) CLOUDSTACK_ZONE)

View File

@ -0,0 +1,95 @@
package cloudstack
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackVPNConnection() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackVPNConnectionCreate,
Read: resourceCloudStackVPNConnectionRead,
Delete: resourceCloudStackVPNConnectionDelete,
Schema: map[string]*schema.Schema{
"customergatewayid": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"vpngatewayid": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceCloudStackVPNConnectionCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.VPN.NewCreateVpnConnectionParams(
d.Get("customergatewayid").(string),
d.Get("vpngatewayid").(string),
)
// Create the new VPN Connection
v, err := cs.VPN.CreateVpnConnection(p)
if err != nil {
return fmt.Errorf("Error creating VPN Connection: %s", err)
}
d.SetId(v.Id)
return resourceCloudStackVPNConnectionRead(d, meta)
}
func resourceCloudStackVPNConnectionRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the VPN Connection details
v, count, err := cs.VPN.GetVpnConnectionByID(d.Id())
if err != nil {
if count == 0 {
log.Printf("[DEBUG] VPN Connection does no longer exist")
d.SetId("")
return nil
}
return err
}
d.Set("customergatewayid", v.S2scustomergatewayid)
d.Set("vpngatewayid", v.S2svpngatewayid)
return nil
}
func resourceCloudStackVPNConnectionDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.VPN.NewDeleteVpnConnectionParams(d.Id())
// Delete the VPN Connection
_, err := cs.VPN.DeleteVpnConnection(p)
if err != nil {
// This is a very poor way to be told the UUID does no longer exist :(
if strings.Contains(err.Error(), fmt.Sprintf(
"Invalid parameter id value=%s due to incorrect long value format, "+
"or entity does not exist", d.Id())) {
return nil
}
return fmt.Errorf("Error deleting VPN Connection: %s", err)
}
return nil
}

View File

@ -0,0 +1,142 @@
package cloudstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackVPNConnection_basic(t *testing.T) {
var vpnConnection cloudstack.VpnConnection
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackVPNConnectionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackVPNConnection_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackVPNConnectionExists(
"cloudstack_vpn_connection.foo-bar", &vpnConnection),
testAccCheckCloudStackVPNConnectionExists(
"cloudstack_vpn_connection.bar-foo", &vpnConnection),
),
},
},
})
}
func testAccCheckCloudStackVPNConnectionExists(
n string, vpnConnection *cloudstack.VpnConnection) 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 VPN Connection ID is set")
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
v, _, err := cs.VPN.GetVpnConnectionByID(rs.Primary.ID)
if err != nil {
return err
}
if v.Id != rs.Primary.ID {
return fmt.Errorf("VPN Connection not found")
}
*vpnConnection = *v
return nil
}
}
func testAccCheckCloudStackVPNConnectionDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_vpn_connection" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No VPN Connection ID is set")
}
p := cs.VPN.NewDeleteVpnConnectionParams(rs.Primary.ID)
_, err := cs.VPN.DeleteVpnConnection(p)
if err != nil {
return fmt.Errorf(
"Error deleting VPN Connection (%s): %s",
rs.Primary.ID, err)
}
}
return nil
}
var testAccCloudStackVPNConnection_basic = fmt.Sprintf(`
resource "cloudstack_vpc" "foo" {
name = "terraform-vpc-foo"
cidr = "%s"
vpc_offering = "%s"
zone = "%s"
}
resource "cloudstack_vpc" "bar" {
name = "terraform-vpc-bar"
cidr = "%s"
vpc_offering = "%s"
zone = "%s"
}
resource "cloudstack_vpn_gateway" "foo" {
vpc = "${cloudstack_vpc.foo.name}"
}
resource "cloudstack_vpn_gateway" "bar" {
vpc = "${cloudstack_vpc.bar.name}"
}
resource "cloudstack_vpn_customer_gateway" "foo" {
name = "terraform-foo"
cidr = "${cloudstack_vpc.foo.cidr}"
esp_policy = "aes256-sha1"
gateway = "${cloudstack_vpn_gateway.foo.public_ip}"
ike_policy = "aes256-sha1"
ipsec_psk = "terraform"
}
resource "cloudstack_vpn_customer_gateway" "bar" {
name = "terraform-bar"
cidr = "${cloudstack_vpc.bar.cidr}"
esp_policy = "aes256-sha1"
gateway = "${cloudstack_vpn_gateway.bar.public_ip}"
ike_policy = "aes256-sha1"
ipsec_psk = "terraform"
}
resource "cloudstack_vpn_connection" "foo-bar" {
customergatewayid = "${cloudstack_vpn_customer_gateway.foo.id}"
vpngatewayid = "${cloudstack_vpn_gateway.bar.id}"
}
resource "cloudstack_vpn_connection" "bar-foo" {
customergatewayid = "${cloudstack_vpn_customer_gateway.bar.id}"
vpngatewayid = "${cloudstack_vpn_gateway.foo.id}"
}`,
CLOUDSTACK_VPC_CIDR_1,
CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE,
CLOUDSTACK_VPC_CIDR_2,
CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE)

View File

@ -0,0 +1,193 @@
package cloudstack
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackVPNCustomerGateway() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackVPNCustomerGatewayCreate,
Read: resourceCloudStackVPNCustomerGatewayRead,
Update: resourceCloudStackVPNCustomerGatewayUpdate,
Delete: resourceCloudStackVPNCustomerGatewayDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"cidr": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"esp_policy": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"gateway": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"ike_policy": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"ipsec_psk": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"dpd": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"esp_lifetime": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"ike_lifetime": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
},
}
}
func resourceCloudStackVPNCustomerGatewayCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.VPN.NewCreateVpnCustomerGatewayParams(
d.Get("cidr").(string),
d.Get("esp_policy").(string),
d.Get("gateway").(string),
d.Get("ike_policy").(string),
d.Get("ipsec_psk").(string),
)
p.SetName(d.Get("name").(string))
if dpd, ok := d.GetOk("dpd"); ok {
p.SetDpd(dpd.(bool))
}
if esplifetime, ok := d.GetOk("esp_lifetime"); ok {
p.SetEsplifetime(esplifetime.(int))
}
if ikelifetime, ok := d.GetOk("ike_lifetime"); ok {
p.SetIkelifetime(ikelifetime.(int))
}
// Create the new VPN Customer Gateway
v, err := cs.VPN.CreateVpnCustomerGateway(p)
if err != nil {
return fmt.Errorf("Error creating VPN Customer Gateway %s: %s", d.Get("name").(string), err)
}
d.SetId(v.Id)
return resourceCloudStackVPNCustomerGatewayRead(d, meta)
}
func resourceCloudStackVPNCustomerGatewayRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the VPN Customer Gateway details
v, count, err := cs.VPN.GetVpnCustomerGatewayByID(d.Id())
if err != nil {
if count == 0 {
log.Printf(
"[DEBUG] VPN Customer Gateway %s does no longer exist", d.Get("name").(string))
d.SetId("")
return nil
}
return err
}
d.Set("name", v.Name)
d.Set("cidr", v.Cidrlist)
d.Set("esp_policy", v.Esppolicy)
d.Set("gateway", v.Gateway)
d.Set("ike_policy", v.Ikepolicy)
d.Set("ipsec_psk", v.Ipsecpsk)
d.Set("dpd", v.Dpd)
d.Set("esp_lifetime", v.Esplifetime)
d.Set("ike_lifetime", v.Ikelifetime)
return nil
}
func resourceCloudStackVPNCustomerGatewayUpdate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.VPN.NewUpdateVpnCustomerGatewayParams(
d.Get("cidr").(string),
d.Get("esp_policy").(string),
d.Get("gateway").(string),
d.Id(),
d.Get("ike_policy").(string),
d.Get("ipsec_psk").(string),
)
p.SetName(d.Get("name").(string))
if dpd, ok := d.GetOk("dpd"); ok {
p.SetDpd(dpd.(bool))
}
if esplifetime, ok := d.GetOk("esp_lifetime"); ok {
p.SetEsplifetime(esplifetime.(int))
}
if ikelifetime, ok := d.GetOk("ike_lifetime"); ok {
p.SetIkelifetime(ikelifetime.(int))
}
// Update the VPN Customer Gateway
_, err := cs.VPN.UpdateVpnCustomerGateway(p)
if err != nil {
return fmt.Errorf("Error updating VPN Customer Gateway %s: %s", d.Get("name").(string), err)
}
return resourceCloudStackVPNCustomerGatewayRead(d, meta)
}
func resourceCloudStackVPNCustomerGatewayDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.VPN.NewDeleteVpnCustomerGatewayParams(d.Id())
// Delete the VPN Customer Gateway
_, err := cs.VPN.DeleteVpnCustomerGateway(p)
if err != nil {
// This is a very poor way to be told the UUID does no longer exist :(
if strings.Contains(err.Error(), fmt.Sprintf(
"Invalid parameter id value=%s due to incorrect long value format, "+
"or entity does not exist", d.Id())) {
return nil
}
return fmt.Errorf("Error deleting VPN Customer Gateway %s: %s", d.Get("name").(string), err)
}
return nil
}

View File

@ -0,0 +1,223 @@
package cloudstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackVPNCustomerGateway_basic(t *testing.T) {
var vpnCustomerGateway cloudstack.VpnCustomerGateway
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackVPNCustomerGatewayDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackVPNCustomerGateway_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackVPNCustomerGatewayExists(
"cloudstack_vpn_customer_gateway.foo", &vpnCustomerGateway),
testAccCheckCloudStackVPNCustomerGatewayAttributes(&vpnCustomerGateway),
resource.TestCheckResourceAttr(
"cloudstack_vpn_customer_gateway.foo", "name", "terraform-foo"),
resource.TestCheckResourceAttr(
"cloudstack_vpn_customer_gateway.bar", "name", "terraform-bar"),
resource.TestCheckResourceAttr(
"cloudstack_vpn_customer_gateway.foo", "ike_policy", "aes256-sha1"),
resource.TestCheckResourceAttr(
"cloudstack_vpn_customer_gateway.bar", "esp_policy", "aes256-sha1"),
),
},
},
})
}
func TestAccCloudStackVPNCustomerGateway_update(t *testing.T) {
var vpnCustomerGateway cloudstack.VpnCustomerGateway
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackVPNCustomerGatewayDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackVPNCustomerGateway_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackVPNCustomerGatewayExists(
"cloudstack_vpn_customer_gateway.foo", &vpnCustomerGateway),
testAccCheckCloudStackVPNCustomerGatewayAttributes(&vpnCustomerGateway),
resource.TestCheckResourceAttr(
"cloudstack_vpn_customer_gateway.foo", "name", "terraform-foo"),
resource.TestCheckResourceAttr(
"cloudstack_vpn_customer_gateway.bar", "name", "terraform-bar"),
resource.TestCheckResourceAttr(
"cloudstack_vpn_customer_gateway.foo", "ike_policy", "aes256-sha1"),
resource.TestCheckResourceAttr(
"cloudstack_vpn_customer_gateway.bar", "esp_policy", "aes256-sha1"),
),
},
resource.TestStep{
Config: testAccCloudStackVPNCustomerGateway_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackVPNCustomerGatewayExists(
"cloudstack_vpn_customer_gateway.foo", &vpnCustomerGateway),
testAccCheckCloudStackVPNCustomerGatewayAttributes(&vpnCustomerGateway),
resource.TestCheckResourceAttr(
"cloudstack_vpn_customer_gateway.foo", "name", "terraform-foo-bar"),
resource.TestCheckResourceAttr(
"cloudstack_vpn_customer_gateway.bar", "name", "terraform-bar-foo"),
resource.TestCheckResourceAttr(
"cloudstack_vpn_customer_gateway.foo", "ike_policy", "3des-md5"),
resource.TestCheckResourceAttr(
"cloudstack_vpn_customer_gateway.bar", "esp_policy", "3des-md5"),
),
},
},
})
}
func testAccCheckCloudStackVPNCustomerGatewayExists(
n string, vpnCustomerGateway *cloudstack.VpnCustomerGateway) 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 VPN CustomerGateway ID is set")
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
v, _, err := cs.VPN.GetVpnCustomerGatewayByID(rs.Primary.ID)
if err != nil {
return err
}
if v.Id != rs.Primary.ID {
return fmt.Errorf("VPN CustomerGateway not found")
}
*vpnCustomerGateway = *v
return nil
}
}
func testAccCheckCloudStackVPNCustomerGatewayAttributes(
vpnCustomerGateway *cloudstack.VpnCustomerGateway) resource.TestCheckFunc {
return func(s *terraform.State) error {
if vpnCustomerGateway.Esppolicy != "aes256-sha1" {
return fmt.Errorf("Bad ESP policy: %s", vpnCustomerGateway.Esppolicy)
}
if vpnCustomerGateway.Ikepolicy != "aes256-sha1" {
return fmt.Errorf("Bad IKE policy: %s", vpnCustomerGateway.Ikepolicy)
}
if vpnCustomerGateway.Ipsecpsk != "terraform" {
return fmt.Errorf("Bad IPSEC pre-shared key: %s", vpnCustomerGateway.Ipsecpsk)
}
return nil
}
}
func testAccCheckCloudStackVPNCustomerGatewayDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_vpn_customer_gateway" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No VPN Customer Gateway ID is set")
}
p := cs.VPN.NewDeleteVpnCustomerGatewayParams(rs.Primary.ID)
_, err := cs.VPN.DeleteVpnCustomerGateway(p)
if err != nil {
return fmt.Errorf(
"Error deleting VPN Customer Gateway (%s): %s",
rs.Primary.ID, err)
}
}
return nil
}
var testAccCloudStackVPNCustomerGateway_basic = fmt.Sprintf(`
resource "cloudstack_vpc" "foo" {
name = "terraform-vpc-foo"
cidr = "%s"
vpc_offering = "%s"
zone = "%s"
}
resource "cloudstack_vpc" "bar" {
name = "terraform-vpc-bar"
cidr = "%s"
vpc_offering = "%s"
zone = "%s"
}
resource "cloudstack_vpn_gateway" "foo" {
vpc = "${cloudstack_vpc.foo.name}"
}
resource "cloudstack_vpn_gateway" "bar" {
vpc = "${cloudstack_vpc.bar.name}"
}
resource "cloudstack_vpn_customer_gateway" "foo" {
name = "terraform-foo"
cidr = "${cloudstack_vpc.foo.cidr}"
esp_policy = "aes256-sha1"
gateway = "${cloudstack_vpn_gateway.foo.public_ip}"
ike_policy = "aes256-sha1"
ipsec_psk = "terraform"
}
resource "cloudstack_vpn_customer_gateway" "bar" {
name = "terraform-bar"
cidr = "${cloudstack_vpc.bar.cidr}"
esp_policy = "aes256-sha1"
gateway = "${cloudstack_vpn_gateway.bar.public_ip}"
ike_policy = "aes256-sha1"
ipsec_psk = "terraform"
}`,
CLOUDSTACK_VPC_CIDR_1,
CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE,
CLOUDSTACK_VPC_CIDR_2,
CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE)
var testAccCloudStackVPNCustomerGateway_update = fmt.Sprintf(`
resource "cloudstack_vpn_customer_gateway" "foo" {
name = "terraform-foo-bar"
cidr = "${cloudstack_vpc.foo.cidr}"
esp_policy = "3des-md5"
gateway = "${cloudstack_vpn_gateway.foo.public_ip}"
ike_policy = "3des-md5"
ipsec_psk = "terraform"
}
resource "cloudstack_vpn_customer_gateway" "bar" {
name = "terraform-bar-foo"
cidr = "${cloudstack_vpc.bar.cidr}"
esp_policy = "3des-md5"
gateway = "${cloudstack_vpn_gateway.bar.public_ip}"
ike_policy = "3des-md5"
ipsec_psk = "terraform"
}`)

View File

@ -0,0 +1,97 @@
package cloudstack
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackVPNGateway() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackVPNGatewayCreate,
Read: resourceCloudStackVPNGatewayRead,
Delete: resourceCloudStackVPNGatewayDelete,
Schema: map[string]*schema.Schema{
"vpc": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"public_ip": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceCloudStackVPNGatewayCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Retrieve the VPC UUID
vpcid, e := retrieveUUID(cs, "vpc", d.Get("vpc").(string))
if e != nil {
return e.Error()
}
// Create a new parameter struct
p := cs.VPN.NewCreateVpnGatewayParams(vpcid)
// Create the new VPN Gateway
v, err := cs.VPN.CreateVpnGateway(p)
if err != nil {
return fmt.Errorf("Error creating VPN Gateway for VPC %s: %s", d.Get("vpc").(string), err)
}
d.SetId(v.Id)
return resourceCloudStackVPNGatewayRead(d, meta)
}
func resourceCloudStackVPNGatewayRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the VPN Gateway details
v, count, err := cs.VPN.GetVpnGatewayByID(d.Id())
if err != nil {
if count == 0 {
log.Printf(
"[DEBUG] VPN Gateway for VPC %s does no longer exist", d.Get("vpc").(string))
d.SetId("")
return nil
}
return err
}
d.Set("public_ip", v.Publicip)
return nil
}
func resourceCloudStackVPNGatewayDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.VPN.NewDeleteVpnGatewayParams(d.Id())
// Delete the VPN Gateway
_, err := cs.VPN.DeleteVpnGateway(p)
if err != nil {
// This is a very poor way to be told the UUID does no longer exist :(
if strings.Contains(err.Error(), fmt.Sprintf(
"Invalid parameter id value=%s due to incorrect long value format, "+
"or entity does not exist", d.Id())) {
return nil
}
return fmt.Errorf("Error deleting VPN Gateway for VPC %s: %s", d.Get("vpc").(string), err)
}
return nil
}

View File

@ -0,0 +1,101 @@
package cloudstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackVPNGateway_basic(t *testing.T) {
var vpnGateway cloudstack.VpnGateway
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackVPNGatewayDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackVPNGateway_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackVPNGatewayExists(
"cloudstack_vpn_gateway.foo", &vpnGateway),
resource.TestCheckResourceAttr(
"cloudstack_vpn_gateway.foo", "vpc", "terraform-vpc"),
),
},
},
})
}
func testAccCheckCloudStackVPNGatewayExists(
n string, vpnGateway *cloudstack.VpnGateway) 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 VPN Gateway ID is set")
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
v, _, err := cs.VPN.GetVpnGatewayByID(rs.Primary.ID)
if err != nil {
return err
}
if v.Id != rs.Primary.ID {
return fmt.Errorf("VPN Gateway not found")
}
*vpnGateway = *v
return nil
}
}
func testAccCheckCloudStackVPNGatewayDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_vpn_gateway" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No VPN Gateway ID is set")
}
p := cs.VPN.NewDeleteVpnGatewayParams(rs.Primary.ID)
_, err := cs.VPN.DeleteVpnGateway(p)
if err != nil {
return fmt.Errorf(
"Error deleting VPN Gateway (%s): %s",
rs.Primary.ID, err)
}
}
return nil
}
var testAccCloudStackVPNGateway_basic = fmt.Sprintf(`
resource "cloudstack_vpc" "foo" {
name = "terraform-vpc"
display_text = "terraform-vpc-text"
cidr = "%s"
vpc_offering = "%s"
zone = "%s"
}
resource "cloudstack_vpn_gateway" "foo" {
vpc = "${cloudstack_vpc.foo.name}"
}`,
CLOUDSTACK_VPC_CIDR_1,
CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE)

View File

@ -40,8 +40,6 @@ func retrieveUUID(cs *cloudstack.CloudStackClient, name, value string) (uuid str
uuid, err = cs.VPC.GetVPCOfferingID(value) uuid, err = cs.VPC.GetVPCOfferingID(value)
case "vpc": case "vpc":
uuid, err = cs.VPC.GetVPCID(value) uuid, err = cs.VPC.GetVPCID(value)
case "template":
uuid, err = cs.Template.GetTemplateID(value, "executable")
case "network": case "network":
uuid, err = cs.Network.GetNetworkID(value) uuid, err = cs.Network.GetNetworkID(value)
case "zone": case "zone":
@ -71,6 +69,22 @@ func retrieveUUID(cs *cloudstack.CloudStackClient, name, value string) (uuid str
return uuid, nil return uuid, nil
} }
func retrieveTemplateUUID(cs *cloudstack.CloudStackClient, zoneid, value string) (uuid string, e *retrieveError) {
// If the supplied value isn't a UUID, try to retrieve the UUID ourselves
if isUUID(value) {
return value, nil
}
log.Printf("[DEBUG] Retrieving UUID of template: %s", value)
uuid, err := cs.Template.GetTemplateID(value, "executable", zoneid)
if err != nil {
return uuid, &retrieveError{name: "template", value: value, err: err}
}
return uuid, nil
}
func isUUID(s string) bool { func isUUID(s string) bool {
re := regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`) re := regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
return re.MatchString(s) return re.MatchString(s)

View File

@ -21,6 +21,7 @@ func Provider() terraform.ResourceProvider {
"digitalocean_domain": resourceDigitalOceanDomain(), "digitalocean_domain": resourceDigitalOceanDomain(),
"digitalocean_droplet": resourceDigitalOceanDroplet(), "digitalocean_droplet": resourceDigitalOceanDroplet(),
"digitalocean_record": resourceDigitalOceanRecord(), "digitalocean_record": resourceDigitalOceanRecord(),
"digitalocean_ssh_key": resourceDigitalOceanSSHKey(),
}, },
ConfigureFunc: providerConfigure, ConfigureFunc: providerConfigure,

View File

@ -0,0 +1,114 @@
package digitalocean
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/pearkes/digitalocean"
)
func resourceDigitalOceanSSHKey() *schema.Resource {
return &schema.Resource{
Create: resourceDigitalOceanSSHKeyCreate,
Read: resourceDigitalOceanSSHKeyRead,
Update: resourceDigitalOceanSSHKeyUpdate,
Delete: resourceDigitalOceanSSHKeyDelete,
Schema: map[string]*schema.Schema{
"id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"public_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"fingerprint": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceDigitalOceanSSHKeyCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*digitalocean.Client)
// Build up our creation options
opts := &digitalocean.CreateSSHKey{
Name: d.Get("name").(string),
PublicKey: d.Get("public_key").(string),
}
log.Printf("[DEBUG] SSH Key create configuration: %#v", opts)
id, err := client.CreateSSHKey(opts)
if err != nil {
return fmt.Errorf("Error creating SSH Key: %s", err)
}
d.SetId(id)
log.Printf("[INFO] SSH Key: %s", id)
return resourceDigitalOceanSSHKeyRead(d, meta)
}
func resourceDigitalOceanSSHKeyRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*digitalocean.Client)
key, err := client.RetrieveSSHKey(d.Id())
if err != nil {
// If the key is somehow already destroyed, mark as
// succesfully gone
if strings.Contains(err.Error(), "404 Not Found") {
d.SetId("")
return nil
}
return fmt.Errorf("Error retrieving SSH key: %s", err)
}
d.Set("name", key.Name)
d.Set("fingerprint", key.Fingerprint)
return nil
}
func resourceDigitalOceanSSHKeyUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*digitalocean.Client)
var newName string
if v, ok := d.GetOk("name"); ok {
newName = v.(string)
}
log.Printf("[DEBUG] SSH key update name: %#v", newName)
err := client.RenameSSHKey(d.Id(), newName)
if err != nil {
return fmt.Errorf("Failed to update SSH key: %s", err)
}
return resourceDigitalOceanSSHKeyRead(d, meta)
}
func resourceDigitalOceanSSHKeyDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*digitalocean.Client)
log.Printf("[INFO] Deleting SSH key: %s", d.Id())
err := client.DestroySSHKey(d.Id())
if err != nil {
return fmt.Errorf("Error deleting SSH key: %s", err)
}
d.SetId("")
return nil
}

View File

@ -0,0 +1,99 @@
package digitalocean
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/pearkes/digitalocean"
)
func TestAccDigitalOceanSSHKey_Basic(t *testing.T) {
var key digitalocean.SSHKey
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanSSHKeyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckDigitalOceanSSHKeyConfig_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanSSHKeyExists("digitalocean_ssh_key.foobar", &key),
testAccCheckDigitalOceanSSHKeyAttributes(&key),
resource.TestCheckResourceAttr(
"digitalocean_ssh_key.foobar", "name", "foobar"),
resource.TestCheckResourceAttr(
"digitalocean_ssh_key.foobar", "public_key", "abcdef"),
),
},
},
})
}
func testAccCheckDigitalOceanSSHKeyDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*digitalocean.Client)
for _, rs := range s.RootModule().Resources {
if rs.Type != "digitalocean_ssh_key" {
continue
}
// Try to find the key
_, err := client.RetrieveSSHKey(rs.Primary.ID)
if err == nil {
fmt.Errorf("SSH key still exists")
}
}
return nil
}
func testAccCheckDigitalOceanSSHKeyAttributes(key *digitalocean.SSHKey) resource.TestCheckFunc {
return func(s *terraform.State) error {
if key.Name != "foobar" {
return fmt.Errorf("Bad name: %s", key.Name)
}
return nil
}
}
func testAccCheckDigitalOceanSSHKeyExists(n string, key *digitalocean.SSHKey) 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().(*digitalocean.Client)
foundKey, err := client.RetrieveSSHKey(rs.Primary.ID)
if err != nil {
return err
}
if foundKey.Name != rs.Primary.ID {
return fmt.Errorf("Record not found")
}
*key = foundKey
return nil
}
}
const testAccCheckDigitalOceanSSHKeyConfig_basic = `
resource "digitalocean_ssh_key" "foobar" {
name = "foobar"
public_key = "abcdef"
}`

View File

@ -483,14 +483,19 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
d.Set("can_ip_forward", instance.CanIpForward) d.Set("can_ip_forward", instance.CanIpForward)
// Set the service accounts // Set the service accounts
for i, serviceAccount := range instance.ServiceAccounts { serviceAccounts := make([]map[string]interface{}, 0, 1)
prefix := fmt.Sprintf("service_account.%d", i) for _, serviceAccount := range instance.ServiceAccounts {
d.Set(prefix+".email", serviceAccount.Email) scopes := make([]string, len(serviceAccount.Scopes))
d.Set(prefix+".scopes.#", len(serviceAccount.Scopes)) for i, scope := range serviceAccount.Scopes {
for j, scope := range serviceAccount.Scopes { scopes[i] = scope
d.Set(fmt.Sprintf("%s.scopes.%d", prefix, j), scope)
} }
serviceAccounts = append(serviceAccounts, map[string]interface{}{
"email": serviceAccount.Email,
"scopes": scopes,
})
} }
d.Set("service_account", serviceAccounts)
networksCount := d.Get("network.#").(int) networksCount := d.Get("network.#").(int)
networkInterfacesCount := d.Get("network_interface.#").(int) networkInterfacesCount := d.Get("network_interface.#").(int)
@ -506,13 +511,10 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
// Use the first external IP found for the default connection info. // Use the first external IP found for the default connection info.
externalIP := "" externalIP := ""
internalIP := "" internalIP := ""
networks := make([]map[string]interface{}, 0, 1)
if networksCount > 0 { if networksCount > 0 {
// TODO: Remove this when realizing deprecation of .network // TODO: Remove this when realizing deprecation of .network
for i, iface := range instance.NetworkInterfaces { for _, iface := range instance.NetworkInterfaces {
prefix := fmt.Sprintf("network.%d", i)
d.Set(prefix+".name", iface.Name)
log.Printf(prefix+".name = %s", iface.Name)
var natIP string var natIP string
for _, config := range iface.AccessConfigs { for _, config := range iface.AccessConfigs {
if config.Type == "ONE_TO_ONE_NAT" { if config.Type == "ONE_TO_ONE_NAT" {
@ -524,23 +526,28 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
if externalIP == "" && natIP != "" { if externalIP == "" && natIP != "" {
externalIP = natIP externalIP = natIP
} }
d.Set(prefix+".external_address", natIP)
d.Set(prefix+".internal_address", iface.NetworkIP) network := make(map[string]interface{})
network["name"] = iface.Name
network["external_address"] = natIP
network["internal_address"] = iface.NetworkIP
networks = append(networks, network)
} }
} }
d.Set("network", networks)
networkInterfaces := make([]map[string]interface{}, 0, 1)
if networkInterfacesCount > 0 { if networkInterfacesCount > 0 {
for i, iface := range instance.NetworkInterfaces { for _, iface := range instance.NetworkInterfaces {
prefix := fmt.Sprintf("network_interface.%d", i)
d.Set(prefix+".name", iface.Name)
// The first non-empty ip is left in natIP // The first non-empty ip is left in natIP
var natIP string var natIP string
for j, config := range iface.AccessConfigs { accessConfigs := make(
acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j) []map[string]interface{}, 0, len(iface.AccessConfigs))
d.Set(acPrefix+".nat_ip", config.NatIP) for _, config := range iface.AccessConfigs {
accessConfigs = append(accessConfigs, map[string]interface{}{
"nat_ip": config.NatIP,
})
if natIP == "" { if natIP == "" {
natIP = config.NatIP natIP = config.NatIP
} }
@ -550,13 +557,18 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
externalIP = natIP externalIP = natIP
} }
d.Set(prefix+".address", iface.NetworkIP)
if internalIP == "" { if internalIP == "" {
internalIP = iface.NetworkIP internalIP = iface.NetworkIP
} }
networkInterfaces = append(networkInterfaces, map[string]interface{}{
"name": iface.Name,
"address": iface.NetworkIP,
"access_config": accessConfigs,
})
} }
} }
d.Set("network_interface", networkInterfaces)
// Fall back on internal ip if there is no external ip. This makes sense in the situation where // Fall back on internal ip if there is no external ip. This makes sense in the situation where
// terraform is being used on a cloud instance and can therefore access the instances it creates // terraform is being used on a cloud instance and can therefore access the instances it creates

View File

@ -14,6 +14,7 @@ type ColorizeUi struct {
OutputColor string OutputColor string
InfoColor string InfoColor string
ErrorColor string ErrorColor string
WarnColor string
Ui cli.Ui Ui cli.Ui
} }
@ -33,6 +34,10 @@ func (u *ColorizeUi) Error(message string) {
u.Ui.Error(u.colorize(message, u.ErrorColor)) u.Ui.Error(u.colorize(message, u.ErrorColor))
} }
func (u *ColorizeUi) Warn(message string) {
u.Ui.Warn(u.colorize(message, u.WarnColor))
}
func (u *ColorizeUi) colorize(message string, color string) string { func (u *ColorizeUi) colorize(message string, color string) string {
if color == "" { if color == "" {
return message return message

View File

@ -33,9 +33,9 @@ func validateContext(ctx *terraform.Context, ui cli.Ui) bool {
"fix these before continuing.\n") "fix these before continuing.\n")
if len(ws) > 0 { if len(ws) > 0 {
ui.Output("Warnings:\n") ui.Warn("Warnings:\n")
for _, w := range ws { for _, w := range ws {
ui.Output(fmt.Sprintf(" * %s", w)) ui.Warn(fmt.Sprintf(" * %s", w))
} }
if len(es) > 0 { if len(es) > 0 {
@ -44,13 +44,16 @@ func validateContext(ctx *terraform.Context, ui cli.Ui) bool {
} }
if len(es) > 0 { if len(es) > 0 {
ui.Output("Errors:\n") ui.Error("Errors:\n")
for _, e := range es { for _, e := range es {
ui.Output(fmt.Sprintf(" * %s", e)) ui.Error(fmt.Sprintf(" * %s", e))
} }
return false
} else {
ui.Warn(fmt.Sprintf("\n"+
"No errors found. Continuing with %d warning(s).\n", len(ws)))
return true
} }
return false
} }
return true return true

View File

@ -120,7 +120,7 @@ func (c *InitCommand) Run(args []string) int {
} }
// Initialize a blank state file with remote enabled // Initialize a blank state file with remote enabled
remoteCmd := &RemoteCommand{ remoteCmd := &RemoteConfigCommand{
Meta: c.Meta, Meta: c.Meta,
remoteConf: remoteConf, remoteConf: remoteConf,
} }

View File

@ -331,6 +331,7 @@ func (m *Meta) process(args []string, vars bool) []string {
Ui: &ColorizeUi{ Ui: &ColorizeUi{
Colorize: m.Colorize(), Colorize: m.Colorize(),
ErrorColor: "[red]", ErrorColor: "[red]",
WarnColor: "[yellow]",
Ui: m.oldUi, Ui: m.oldUi,
}, },
} }

View File

@ -1,339 +1,57 @@
package command package command
import ( import (
"flag"
"fmt"
"log"
"os"
"strings" "strings"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
"github.com/hashicorp/terraform/terraform"
) )
// remoteCommandConfig is used to encapsulate our configuration
type remoteCommandConfig struct {
disableRemote bool
pullOnDisable bool
statePath string
backupPath string
}
// RemoteCommand is a Command implementation that is used to
// enable and disable remote state management
type RemoteCommand struct { type RemoteCommand struct {
Meta Meta
conf remoteCommandConfig
remoteConf terraform.RemoteState
} }
func (c *RemoteCommand) Run(args []string) int { func (c *RemoteCommand) Run(argsRaw []string) int {
// Duplicate the args so we can munge them without affecting
// future subcommand invocations which will do the same.
args := make([]string, len(argsRaw))
copy(args, argsRaw)
args = c.Meta.process(args, false) args = c.Meta.process(args, false)
config := make(map[string]string)
cmdFlags := flag.NewFlagSet("remote", flag.ContinueOnError) if len(args) == 0 {
cmdFlags.BoolVar(&c.conf.disableRemote, "disable", false, "") c.Ui.Error(c.Help())
cmdFlags.BoolVar(&c.conf.pullOnDisable, "pull", true, "")
cmdFlags.StringVar(&c.conf.statePath, "state", DefaultStateFilename, "path")
cmdFlags.StringVar(&c.conf.backupPath, "backup", "", "path")
cmdFlags.StringVar(&c.remoteConf.Type, "backend", "atlas", "")
cmdFlags.Var((*FlagKV)(&config), "backend-config", "config")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
return 1 return 1
} }
// Show help if given no inputs switch args[0] {
if !c.conf.disableRemote && c.remoteConf.Type == "atlas" && len(config) == 0 { case "config":
cmdFlags.Usage() cmd := &RemoteConfigCommand{Meta: c.Meta}
return cmd.Run(args[1:])
case "pull":
cmd := &RemotePullCommand{Meta: c.Meta}
return cmd.Run(args[1:])
case "push":
cmd := &RemotePushCommand{Meta: c.Meta}
return cmd.Run(args[1:])
default:
c.Ui.Error(c.Help())
return 1 return 1
} }
// Set the local state path
c.statePath = c.conf.statePath
// Populate the various configurations
c.remoteConf.Config = config
// Get the state information. We specifically request the cache only
// for the remote state here because it is possible the remote state
// is invalid and we don't want to error.
stateOpts := c.StateOpts()
stateOpts.RemoteCacheOnly = true
if _, err := c.StateRaw(stateOpts); err != nil {
c.Ui.Error(fmt.Sprintf("Error loading local state: %s", err))
return 1
}
// Get the local and remote [cached] state
localState := c.stateResult.Local.State()
var remoteState *terraform.State
if remote := c.stateResult.Remote; remote != nil {
remoteState = remote.State()
}
// Check if remote state is being disabled
if c.conf.disableRemote {
if !remoteState.IsRemote() {
c.Ui.Error(fmt.Sprintf("Remote state management not enabled! Aborting."))
return 1
}
if !localState.Empty() {
c.Ui.Error(fmt.Sprintf("State file already exists at '%s'. Aborting.",
c.conf.statePath))
return 1
}
return c.disableRemoteState()
}
// Ensure there is no conflict
haveCache := !remoteState.Empty()
haveLocal := !localState.Empty()
switch {
case haveCache && haveLocal:
c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!",
c.conf.statePath))
return 1
case !haveCache && !haveLocal:
// If we don't have either state file, initialize a blank state file
return c.initBlankState()
case haveCache && !haveLocal:
// Update the remote state target potentially
return c.updateRemoteConfig()
case !haveCache && haveLocal:
// Enable remote state management
return c.enableRemoteState()
}
panic("unhandled case")
}
// disableRemoteState is used to disable remote state management,
// and move the state file into place.
func (c *RemoteCommand) disableRemoteState() int {
if c.stateResult == nil {
c.Ui.Error(fmt.Sprintf(
"Internal error. State() must be called internally before remote\n" +
"state can be disabled. Please report this as a bug."))
return 1
}
if !c.stateResult.State.State().IsRemote() {
c.Ui.Error(fmt.Sprintf(
"Remote state is not enabled. Can't disable remote state."))
return 1
}
local := c.stateResult.Local
remote := c.stateResult.Remote
// Ensure we have the latest state before disabling
if c.conf.pullOnDisable {
log.Printf("[INFO] Refreshing local state from remote server")
if err := remote.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf(
"Failed to refresh from remote state: %s", err))
return 1
}
// Exit if we were unable to update
if change := remote.RefreshResult(); !change.SuccessfulPull() {
c.Ui.Error(fmt.Sprintf("%s", change))
return 1
} else {
log.Printf("[INFO] %s", change)
}
}
// Clear the remote management, and copy into place
newState := remote.State()
newState.Remote = nil
if err := local.WriteState(newState); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
c.conf.statePath, err))
return 1
}
if err := local.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
c.conf.statePath, err))
return 1
}
// Remove the old state file
if err := os.Remove(c.stateResult.RemotePath); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to remove the local state file: %v", err))
return 1
}
return 0
}
// validateRemoteConfig is used to verify that the remote configuration
// we have is valid
func (c *RemoteCommand) validateRemoteConfig() error {
conf := c.remoteConf
_, err := remote.NewClient(conf.Type, conf.Config)
if err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
}
return err
}
// initBlank state is used to initialize a blank state that is
// remote enabled
func (c *RemoteCommand) initBlankState() int {
// Validate the remote configuration
if err := c.validateRemoteConfig(); err != nil {
return 1
}
// Make a blank state, attach the remote configuration
blank := terraform.NewState()
blank.Remote = &c.remoteConf
// Persist the state
remote := &state.LocalState{Path: c.stateResult.RemotePath}
if err := remote.WriteState(blank); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
return 1
}
if err := remote.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
return 1
}
// Success!
c.Ui.Output("Initialized blank state with remote state enabled!")
return 0
}
// updateRemoteConfig is used to update the configuration of the
// remote state store
func (c *RemoteCommand) updateRemoteConfig() int {
// Validate the remote configuration
if err := c.validateRemoteConfig(); err != nil {
return 1
}
// Read in the local state, which is just the cache of the remote state
remote := c.stateResult.Remote.Cache
// Update the configuration
state := remote.State()
state.Remote = &c.remoteConf
if err := remote.WriteState(state); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
if err := remote.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
// Success!
c.Ui.Output("Remote configuration updated")
return 0
}
// enableRemoteState is used to enable remote state management
// and to move a state file into place
func (c *RemoteCommand) enableRemoteState() int {
// Validate the remote configuration
if err := c.validateRemoteConfig(); err != nil {
return 1
}
// Read the local state
local := c.stateResult.Local
if err := local.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to read local state: %s", err))
return 1
}
// Backup the state file before we modify it
backupPath := c.conf.backupPath
if backupPath != "-" {
// Provide default backup path if none provided
if backupPath == "" {
backupPath = c.conf.statePath + DefaultBackupExtention
}
log.Printf("[INFO] Writing backup state to: %s", backupPath)
backup := &state.LocalState{Path: backupPath}
if err := backup.WriteState(local.State()); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
return 1
}
if err := backup.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
return 1
}
}
// Update the local configuration, move into place
state := local.State()
state.Remote = &c.remoteConf
remote := c.stateResult.Remote
if err := remote.WriteState(state); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
if err := remote.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
// Remove the original, local state file
log.Printf("[INFO] Removing state file: %s", c.conf.statePath)
if err := os.Remove(c.conf.statePath); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to remove state file '%s': %v",
c.conf.statePath, err))
return 1
}
// Success!
c.Ui.Output("Remote state management enabled")
return 0
} }
func (c *RemoteCommand) Help() string { func (c *RemoteCommand) Help() string {
helpText := ` helpText := `
Usage: terraform remote [options] Usage: terraform remote <subcommand> [options]
Configures Terraform to use a remote state server. This allows state Configure remote state storage with Terraform.
to be pulled down when necessary and then pushed to the server when
updated. In this mode, the state file does not need to be stored durably
since the remote server provides the durability.
Options: Available subcommands:
-backend=Atlas Specifies the type of remote backend. Must be one config Configure the remote storage settings.
of Atlas, Consul, or HTTP. Defaults to Atlas. pull Sync the remote storage by downloading to local storage.
push Sync the remote storage by uploading the local storage.
-backend-config="k=v" Specifies configuration for the remote storage
backend. This can be specified multiple times.
-backup=path Path to backup the existing state file before
modifying. Defaults to the "-state" path with
".backup" extension. Set to "-" to disable backup.
-disable Disables remote state management and migrates the state
to the -state path.
-pull=true Controls if the remote state is pulled before disabling.
This defaults to true to ensure the latest state is cached
before disabling.
-state=path Path to read state. Defaults to "terraform.tfstate"
unless remote state is enabled.
` `
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (c *RemoteCommand) Synopsis() string { func (c *RemoteCommand) Synopsis() string {
return "Configures remote state management" return "Configure remote state storage"
} }

339
command/remote_config.go Normal file
View File

@ -0,0 +1,339 @@
package command
import (
"flag"
"fmt"
"log"
"os"
"strings"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
"github.com/hashicorp/terraform/terraform"
)
// remoteCommandConfig is used to encapsulate our configuration
type remoteCommandConfig struct {
disableRemote bool
pullOnDisable bool
statePath string
backupPath string
}
// RemoteConfigCommand is a Command implementation that is used to
// enable and disable remote state management
type RemoteConfigCommand struct {
Meta
conf remoteCommandConfig
remoteConf terraform.RemoteState
}
func (c *RemoteConfigCommand) Run(args []string) int {
args = c.Meta.process(args, false)
config := make(map[string]string)
cmdFlags := flag.NewFlagSet("remote", flag.ContinueOnError)
cmdFlags.BoolVar(&c.conf.disableRemote, "disable", false, "")
cmdFlags.BoolVar(&c.conf.pullOnDisable, "pull", true, "")
cmdFlags.StringVar(&c.conf.statePath, "state", DefaultStateFilename, "path")
cmdFlags.StringVar(&c.conf.backupPath, "backup", "", "path")
cmdFlags.StringVar(&c.remoteConf.Type, "backend", "atlas", "")
cmdFlags.Var((*FlagKV)(&config), "backend-config", "config")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
return 1
}
// Show help if given no inputs
if !c.conf.disableRemote && c.remoteConf.Type == "atlas" && len(config) == 0 {
cmdFlags.Usage()
return 1
}
// Set the local state path
c.statePath = c.conf.statePath
// Populate the various configurations
c.remoteConf.Config = config
// Get the state information. We specifically request the cache only
// for the remote state here because it is possible the remote state
// is invalid and we don't want to error.
stateOpts := c.StateOpts()
stateOpts.RemoteCacheOnly = true
if _, err := c.StateRaw(stateOpts); err != nil {
c.Ui.Error(fmt.Sprintf("Error loading local state: %s", err))
return 1
}
// Get the local and remote [cached] state
localState := c.stateResult.Local.State()
var remoteState *terraform.State
if remote := c.stateResult.Remote; remote != nil {
remoteState = remote.State()
}
// Check if remote state is being disabled
if c.conf.disableRemote {
if !remoteState.IsRemote() {
c.Ui.Error(fmt.Sprintf("Remote state management not enabled! Aborting."))
return 1
}
if !localState.Empty() {
c.Ui.Error(fmt.Sprintf("State file already exists at '%s'. Aborting.",
c.conf.statePath))
return 1
}
return c.disableRemoteState()
}
// Ensure there is no conflict
haveCache := !remoteState.Empty()
haveLocal := !localState.Empty()
switch {
case haveCache && haveLocal:
c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!",
c.conf.statePath))
return 1
case !haveCache && !haveLocal:
// If we don't have either state file, initialize a blank state file
return c.initBlankState()
case haveCache && !haveLocal:
// Update the remote state target potentially
return c.updateRemoteConfig()
case !haveCache && haveLocal:
// Enable remote state management
return c.enableRemoteState()
}
panic("unhandled case")
}
// disableRemoteState is used to disable remote state management,
// and move the state file into place.
func (c *RemoteConfigCommand) disableRemoteState() int {
if c.stateResult == nil {
c.Ui.Error(fmt.Sprintf(
"Internal error. State() must be called internally before remote\n" +
"state can be disabled. Please report this as a bug."))
return 1
}
if !c.stateResult.State.State().IsRemote() {
c.Ui.Error(fmt.Sprintf(
"Remote state is not enabled. Can't disable remote state."))
return 1
}
local := c.stateResult.Local
remote := c.stateResult.Remote
// Ensure we have the latest state before disabling
if c.conf.pullOnDisable {
log.Printf("[INFO] Refreshing local state from remote server")
if err := remote.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf(
"Failed to refresh from remote state: %s", err))
return 1
}
// Exit if we were unable to update
if change := remote.RefreshResult(); !change.SuccessfulPull() {
c.Ui.Error(fmt.Sprintf("%s", change))
return 1
} else {
log.Printf("[INFO] %s", change)
}
}
// Clear the remote management, and copy into place
newState := remote.State()
newState.Remote = nil
if err := local.WriteState(newState); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
c.conf.statePath, err))
return 1
}
if err := local.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
c.conf.statePath, err))
return 1
}
// Remove the old state file
if err := os.Remove(c.stateResult.RemotePath); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to remove the local state file: %v", err))
return 1
}
return 0
}
// validateRemoteConfig is used to verify that the remote configuration
// we have is valid
func (c *RemoteConfigCommand) validateRemoteConfig() error {
conf := c.remoteConf
_, err := remote.NewClient(conf.Type, conf.Config)
if err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
}
return err
}
// initBlank state is used to initialize a blank state that is
// remote enabled
func (c *RemoteConfigCommand) initBlankState() int {
// Validate the remote configuration
if err := c.validateRemoteConfig(); err != nil {
return 1
}
// Make a blank state, attach the remote configuration
blank := terraform.NewState()
blank.Remote = &c.remoteConf
// Persist the state
remote := &state.LocalState{Path: c.stateResult.RemotePath}
if err := remote.WriteState(blank); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
return 1
}
if err := remote.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
return 1
}
// Success!
c.Ui.Output("Initialized blank state with remote state enabled!")
return 0
}
// updateRemoteConfig is used to update the configuration of the
// remote state store
func (c *RemoteConfigCommand) updateRemoteConfig() int {
// Validate the remote configuration
if err := c.validateRemoteConfig(); err != nil {
return 1
}
// Read in the local state, which is just the cache of the remote state
remote := c.stateResult.Remote.Cache
// Update the configuration
state := remote.State()
state.Remote = &c.remoteConf
if err := remote.WriteState(state); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
if err := remote.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
// Success!
c.Ui.Output("Remote configuration updated")
return 0
}
// enableRemoteState is used to enable remote state management
// and to move a state file into place
func (c *RemoteConfigCommand) enableRemoteState() int {
// Validate the remote configuration
if err := c.validateRemoteConfig(); err != nil {
return 1
}
// Read the local state
local := c.stateResult.Local
if err := local.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to read local state: %s", err))
return 1
}
// Backup the state file before we modify it
backupPath := c.conf.backupPath
if backupPath != "-" {
// Provide default backup path if none provided
if backupPath == "" {
backupPath = c.conf.statePath + DefaultBackupExtention
}
log.Printf("[INFO] Writing backup state to: %s", backupPath)
backup := &state.LocalState{Path: backupPath}
if err := backup.WriteState(local.State()); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
return 1
}
if err := backup.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
return 1
}
}
// Update the local configuration, move into place
state := local.State()
state.Remote = &c.remoteConf
remote := c.stateResult.Remote
if err := remote.WriteState(state); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
if err := remote.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
// Remove the original, local state file
log.Printf("[INFO] Removing state file: %s", c.conf.statePath)
if err := os.Remove(c.conf.statePath); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to remove state file '%s': %v",
c.conf.statePath, err))
return 1
}
// Success!
c.Ui.Output("Remote state management enabled")
return 0
}
func (c *RemoteConfigCommand) Help() string {
helpText := `
Usage: terraform remote [options]
Configures Terraform to use a remote state server. This allows state
to be pulled down when necessary and then pushed to the server when
updated. In this mode, the state file does not need to be stored durably
since the remote server provides the durability.
Options:
-backend=Atlas Specifies the type of remote backend. Must be one
of Atlas, Consul, or HTTP. Defaults to Atlas.
-backend-config="k=v" Specifies configuration for the remote storage
backend. This can be specified multiple times.
-backup=path Path to backup the existing state file before
modifying. Defaults to the "-state" path with
".backup" extension. Set to "-" to disable backup.
-disable Disables remote state management and migrates the state
to the -state path.
-pull=true Controls if the remote state is pulled before disabling.
This defaults to true to ensure the latest state is cached
before disabling.
-state=path Path to read state. Defaults to "terraform.tfstate"
unless remote state is enabled.
`
return strings.TrimSpace(helpText)
}
func (c *RemoteConfigCommand) Synopsis() string {
return "Configures remote state management"
}

View File

@ -8,11 +8,11 @@ import (
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
) )
type PullCommand struct { type RemotePullCommand struct {
Meta Meta
} }
func (c *PullCommand) Run(args []string) int { func (c *RemotePullCommand) Run(args []string) int {
args = c.Meta.process(args, false) args = c.Meta.process(args, false)
cmdFlags := flag.NewFlagSet("pull", flag.ContinueOnError) cmdFlags := flag.NewFlagSet("pull", flag.ContinueOnError)
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
@ -67,7 +67,7 @@ func (c *PullCommand) Run(args []string) int {
return 0 return 0
} }
func (c *PullCommand) Help() string { func (c *RemotePullCommand) Help() string {
helpText := ` helpText := `
Usage: terraform pull [options] Usage: terraform pull [options]
@ -77,6 +77,6 @@ Usage: terraform pull [options]
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (c *PullCommand) Synopsis() string { func (c *RemotePullCommand) Synopsis() string {
return "Refreshes the local state copy from the remote server" return "Refreshes the local state copy from the remote server"
} }

View File

@ -15,12 +15,12 @@ import (
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
func TestPull_noRemote(t *testing.T) { func TestRemotePull_noRemote(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &PullCommand{ c := &RemotePullCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -33,7 +33,7 @@ func TestPull_noRemote(t *testing.T) {
} }
} }
func TestPull_local(t *testing.T) { func TestRemotePull_local(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -62,7 +62,7 @@ func TestPull_local(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &PullCommand{ c := &RemotePullCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,

View File

@ -8,11 +8,11 @@ import (
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
) )
type PushCommand struct { type RemotePushCommand struct {
Meta Meta
} }
func (c *PushCommand) Run(args []string) int { func (c *RemotePushCommand) Run(args []string) int {
var force bool var force bool
args = c.Meta.process(args, false) args = c.Meta.process(args, false)
cmdFlags := flag.NewFlagSet("push", flag.ContinueOnError) cmdFlags := flag.NewFlagSet("push", flag.ContinueOnError)
@ -71,7 +71,7 @@ func (c *PushCommand) Run(args []string) int {
return 0 return 0
} }
func (c *PushCommand) Help() string { func (c *RemotePushCommand) Help() string {
helpText := ` helpText := `
Usage: terraform push [options] Usage: terraform push [options]
@ -87,6 +87,6 @@ Options:
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (c *PushCommand) Synopsis() string { func (c *RemotePushCommand) Synopsis() string {
return "Uploads the the local state to the remote server" return "Uploads the the local state to the remote server"
} }

View File

@ -9,12 +9,12 @@ import (
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
func TestPush_noRemote(t *testing.T) { func TestRemotePush_noRemote(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &PushCommand{ c := &RemotePushCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -27,7 +27,7 @@ func TestPush_noRemote(t *testing.T) {
} }
} }
func TestPush_local(t *testing.T) { func TestRemotePush_local(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -56,7 +56,7 @@ func TestPush_local(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &PushCommand{ c := &RemotePushCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,

View File

@ -13,7 +13,7 @@ import (
) )
// Test disabling remote management // Test disabling remote management
func TestRemote_disable(t *testing.T) { func TestRemoteConfig_disable(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -39,7 +39,7 @@ func TestRemote_disable(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -68,7 +68,7 @@ func TestRemote_disable(t *testing.T) {
} }
// Test disabling remote management without pulling // Test disabling remote management without pulling
func TestRemote_disable_noPull(t *testing.T) { func TestRemoteConfig_disable_noPull(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -94,7 +94,7 @@ func TestRemote_disable_noPull(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -122,12 +122,12 @@ func TestRemote_disable_noPull(t *testing.T) {
} }
// Test disabling remote management when not enabled // Test disabling remote management when not enabled
func TestRemote_disable_notEnabled(t *testing.T) { func TestRemoteConfig_disable_notEnabled(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -141,7 +141,7 @@ func TestRemote_disable_notEnabled(t *testing.T) {
} }
// Test disabling remote management with a state file in the way // Test disabling remote management with a state file in the way
func TestRemote_disable_otherState(t *testing.T) { func TestRemoteConfig_disable_otherState(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -171,7 +171,7 @@ func TestRemote_disable_otherState(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -185,7 +185,7 @@ func TestRemote_disable_otherState(t *testing.T) {
} }
// Test the case where both managed and non managed state present // Test the case where both managed and non managed state present
func TestRemote_managedAndNonManaged(t *testing.T) { func TestRemoteConfig_managedAndNonManaged(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -215,7 +215,7 @@ func TestRemote_managedAndNonManaged(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -229,12 +229,12 @@ func TestRemote_managedAndNonManaged(t *testing.T) {
} }
// Test initializing blank state // Test initializing blank state
func TestRemote_initBlank(t *testing.T) { func TestRemoteConfig_initBlank(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -269,12 +269,12 @@ func TestRemote_initBlank(t *testing.T) {
} }
// Test initializing without remote settings // Test initializing without remote settings
func TestRemote_initBlank_missingRemote(t *testing.T) { func TestRemoteConfig_initBlank_missingRemote(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -288,7 +288,7 @@ func TestRemote_initBlank_missingRemote(t *testing.T) {
} }
// Test updating remote config // Test updating remote config
func TestRemote_updateRemote(t *testing.T) { func TestRemoteConfig_updateRemote(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -310,7 +310,7 @@ func TestRemote_updateRemote(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -345,7 +345,7 @@ func TestRemote_updateRemote(t *testing.T) {
} }
// Test enabling remote state // Test enabling remote state
func TestRemote_enableRemote(t *testing.T) { func TestRemoteConfig_enableRemote(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -365,7 +365,7 @@ func TestRemote_enableRemote(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,

View File

@ -80,18 +80,6 @@ func init() {
}, nil }, nil
}, },
"pull": func() (cli.Command, error) {
return &command.PullCommand{
Meta: meta,
}, nil
},
"push": func() (cli.Command, error) {
return &command.PushCommand{
Meta: meta,
}, nil
},
"refresh": func() (cli.Command, error) { "refresh": func() (cli.Command, error) {
return &command.RefreshCommand{ return &command.RefreshCommand{
Meta: meta, Meta: meta,

View File

@ -17,6 +17,7 @@ var Funcs map[string]ast.Function
func init() { func init() {
Funcs = map[string]ast.Function{ Funcs = map[string]ast.Function{
"file": interpolationFuncFile(), "file": interpolationFuncFile(),
"format": interpolationFuncFormat(),
"join": interpolationFuncJoin(), "join": interpolationFuncJoin(),
"element": interpolationFuncElement(), "element": interpolationFuncElement(),
"replace": interpolationFuncReplace(), "replace": interpolationFuncReplace(),
@ -66,6 +67,21 @@ func interpolationFuncFile() ast.Function {
} }
} }
// interpolationFuncFormat implements the "replace" function that does
// string replacement.
func interpolationFuncFormat() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
Variadic: true,
VariadicType: ast.TypeAny,
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
format := args[0].(string)
return fmt.Sprintf(format, args[1:]...), nil
},
}
}
// interpolationFuncJoin implements the "join" function that allows // interpolationFuncJoin implements the "join" function that allows
// multi-variable values to be joined by some character. // multi-variable values to be joined by some character.
func interpolationFuncJoin() ast.Function { func interpolationFuncJoin() ast.Function {

View File

@ -70,6 +70,42 @@ func TestInterpolateFuncFile(t *testing.T) {
}) })
} }
func TestInterpolateFuncFormat(t *testing.T) {
testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{
{
`${format("hello")}`,
"hello",
false,
},
{
`${format("hello %s", "world")}`,
"hello world",
false,
},
{
`${format("hello %d", 42)}`,
"hello 42",
false,
},
{
`${format("hello %05d", 42)}`,
"hello 00042",
false,
},
{
`${format("hello %05d", 12345)}`,
"hello 12345",
false,
},
},
})
}
func TestInterpolateFuncJoin(t *testing.T) { func TestInterpolateFuncJoin(t *testing.T) {
testFunction(t, testFunctionConfig{ testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{ Cases: []testFunctionCase{

View File

@ -48,7 +48,8 @@ type Type uint32
const ( const (
TypeInvalid Type = 0 TypeInvalid Type = 0
TypeString Type = 1 << iota TypeAny Type = 1 << iota
TypeString
TypeInt TypeInt
TypeFloat TypeFloat
) )

View File

@ -6,16 +6,18 @@ import "fmt"
const ( const (
_Type_name_0 = "TypeInvalid" _Type_name_0 = "TypeInvalid"
_Type_name_1 = "TypeString" _Type_name_1 = "TypeAny"
_Type_name_2 = "TypeInt" _Type_name_2 = "TypeString"
_Type_name_3 = "TypeFloat" _Type_name_3 = "TypeInt"
_Type_name_4 = "TypeFloat"
) )
var ( var (
_Type_index_0 = [...]uint8{0, 11} _Type_index_0 = [...]uint8{0, 11}
_Type_index_1 = [...]uint8{0, 10} _Type_index_1 = [...]uint8{0, 7}
_Type_index_2 = [...]uint8{0, 7} _Type_index_2 = [...]uint8{0, 10}
_Type_index_3 = [...]uint8{0, 9} _Type_index_3 = [...]uint8{0, 7}
_Type_index_4 = [...]uint8{0, 9}
) )
func (i Type) String() string { func (i Type) String() string {
@ -28,6 +30,8 @@ func (i Type) String() string {
return _Type_name_2 return _Type_name_2
case i == 8: case i == 8:
return _Type_name_3 return _Type_name_3
case i == 16:
return _Type_name_4
default: default:
return fmt.Sprintf("Type(%d)", i) return fmt.Sprintf("Type(%d)", i)
} }

View File

@ -174,6 +174,10 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
// Verify the args // Verify the args
for i, expected := range function.ArgTypes { for i, expected := range function.ArgTypes {
if expected == ast.TypeAny {
continue
}
if args[i] != expected { if args[i] != expected {
cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i]) cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i])
if cn != nil { if cn != nil {
@ -188,7 +192,7 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
} }
// If we're variadic, then verify the types there // If we're variadic, then verify the types there
if function.Variadic { if function.Variadic && function.VariadicType != ast.TypeAny {
args = args[len(function.ArgTypes):] args = args[len(function.ArgTypes):]
for i, t := range args { for i, t := range args {
if t != function.VariadicType { if t != function.VariadicType {

View File

@ -3,6 +3,7 @@ package schema
import ( import (
"errors" "errors"
"fmt" "fmt"
"strconv"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -24,6 +25,31 @@ type Resource struct {
// resource. // resource.
Schema map[string]*Schema Schema map[string]*Schema
// SchemaVersion is the version number for this resource's Schema
// definition. The current SchemaVersion stored in the state for each
// resource. Provider authors can increment this version number
// when Schema semantics change. If the State's SchemaVersion is less than
// the current SchemaVersion, the InstanceState is yielded to the
// MigrateState callback, where the provider can make whatever changes it
// needs to update the state to be compatible to the latest version of the
// Schema.
//
// When unset, SchemaVersion defaults to 0, so provider authors can start
// their Versioning at any integer >= 1
SchemaVersion int
// MigrateState is responsible for updating an InstanceState with an old
// version to the format expected by the current version of the Schema.
//
// It is called during Refresh if the State's stored SchemaVersion is less
// than the current SchemaVersion of the Resource.
//
// The function is yielded the state's stored SchemaVersion and a pointer to
// the InstanceState that needs updating, as well as the configured
// provider's configured meta interface{}, in case the migration process
// needs to make any remote API calls.
MigrateState StateMigrateFunc
// The functions below are the CRUD operations for this resource. // The functions below are the CRUD operations for this resource.
// //
// The only optional operation is Update. If Update is not implemented, // The only optional operation is Update. If Update is not implemented,
@ -69,6 +95,10 @@ type DeleteFunc func(*ResourceData, interface{}) error
// See Resource documentation. // See Resource documentation.
type ExistsFunc func(*ResourceData, interface{}) (bool, error) type ExistsFunc func(*ResourceData, interface{}) (bool, error)
// See Resource documentation.
type StateMigrateFunc func(
int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error)
// Apply creates, updates, and/or deletes a resource. // Apply creates, updates, and/or deletes a resource.
func (r *Resource) Apply( func (r *Resource) Apply(
s *terraform.InstanceState, s *terraform.InstanceState,
@ -158,6 +188,14 @@ func (r *Resource) Refresh(
} }
} }
needsMigration, stateSchemaVersion := r.checkSchemaVersion(s)
if needsMigration && r.MigrateState != nil {
s, err := r.MigrateState(stateSchemaVersion, s, meta)
if err != nil {
return s, err
}
}
data, err := schemaMap(r.Schema).Data(s, nil) data, err := schemaMap(r.Schema).Data(s, nil)
if err != nil { if err != nil {
return s, err return s, err
@ -169,6 +207,13 @@ func (r *Resource) Refresh(
state = nil state = nil
} }
if state != nil && r.SchemaVersion > 0 {
if state.Meta == nil {
state.Meta = make(map[string]string)
}
state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion)
}
return state, err return state, err
} }
@ -189,3 +234,10 @@ func (r *Resource) InternalValidate() error {
return schemaMap(r.Schema).InternalValidate() return schemaMap(r.Schema).InternalValidate()
} }
// Determines if a given InstanceState needs to be migrated by checking the
// stored version number with the current SchemaVersion
func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) {
stateSchemaVersion, _ := strconv.Atoi(is.Meta["schema_version"])
return stateSchemaVersion < r.SchemaVersion, stateSchemaVersion
}

View File

@ -3,6 +3,7 @@ package schema
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"strconv"
"testing" "testing"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
@ -478,3 +479,218 @@ func TestResourceRefresh_noExists(t *testing.T) {
t.Fatalf("should have no state") t.Fatalf("should have no state")
} }
} }
func TestResourceRefresh_needsMigration(t *testing.T) {
// Schema v2 it deals only in newfoo, which tracks foo as an int
r := &Resource{
SchemaVersion: 2,
Schema: map[string]*Schema{
"newfoo": &Schema{
Type: TypeInt,
Optional: true,
},
},
}
r.Read = func(d *ResourceData, m interface{}) error {
return d.Set("newfoo", d.Get("newfoo").(int)+1)
}
r.MigrateState = func(
v int,
s *terraform.InstanceState,
meta interface{}) (*terraform.InstanceState, error) {
// Real state migration functions will probably switch on this value,
// but we'll just assert on it for now.
if v != 1 {
t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v)
}
if meta != 42 {
t.Fatal("Expected meta to be passed through to the migration function")
}
oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64)
if err != nil {
t.Fatalf("err: %#v", err)
}
s.Attributes["newfoo"] = strconv.Itoa((int(oldfoo * 10)))
delete(s.Attributes, "oldfoo")
return s, nil
}
// State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th
// the scale of newfoo
s := &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"oldfoo": "1.2",
},
Meta: map[string]string{
"schema_version": "1",
},
}
actual, err := r.Refresh(s, 42)
if err != nil {
t.Fatalf("err: %s", err)
}
expected := &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"id": "bar",
"newfoo": "13",
},
Meta: map[string]string{
"schema_version": "2",
},
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
}
}
func TestResourceRefresh_noMigrationNeeded(t *testing.T) {
r := &Resource{
SchemaVersion: 2,
Schema: map[string]*Schema{
"newfoo": &Schema{
Type: TypeInt,
Optional: true,
},
},
}
r.Read = func(d *ResourceData, m interface{}) error {
return d.Set("newfoo", d.Get("newfoo").(int)+1)
}
r.MigrateState = func(
v int,
s *terraform.InstanceState,
meta interface{}) (*terraform.InstanceState, error) {
t.Fatal("Migrate function shouldn't be called!")
return nil, nil
}
s := &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"newfoo": "12",
},
Meta: map[string]string{
"schema_version": "2",
},
}
actual, err := r.Refresh(s, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
expected := &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"id": "bar",
"newfoo": "13",
},
Meta: map[string]string{
"schema_version": "2",
},
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
}
}
func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) {
r := &Resource{
// Version 1 > Version 0
SchemaVersion: 1,
Schema: map[string]*Schema{
"newfoo": &Schema{
Type: TypeInt,
Optional: true,
},
},
}
r.Read = func(d *ResourceData, m interface{}) error {
return d.Set("newfoo", d.Get("newfoo").(int)+1)
}
r.MigrateState = func(
v int,
s *terraform.InstanceState,
meta interface{}) (*terraform.InstanceState, error) {
s.Attributes["newfoo"] = s.Attributes["oldfoo"]
return s, nil
}
s := &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"oldfoo": "12",
},
}
actual, err := r.Refresh(s, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
expected := &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"id": "bar",
"newfoo": "13",
},
Meta: map[string]string{
"schema_version": "1",
},
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
}
}
func TestResourceRefresh_migrateStateErr(t *testing.T) {
r := &Resource{
SchemaVersion: 2,
Schema: map[string]*Schema{
"newfoo": &Schema{
Type: TypeInt,
Optional: true,
},
},
}
r.Read = func(d *ResourceData, m interface{}) error {
t.Fatal("Read should never be called!")
return nil
}
r.MigrateState = func(
v int,
s *terraform.InstanceState,
meta interface{}) (*terraform.InstanceState, error) {
return s, fmt.Errorf("triggering an error")
}
s := &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"oldfoo": "12",
},
}
_, err := r.Refresh(s, nil)
if err == nil {
t.Fatal("expected error, but got none!")
}
}

View File

@ -113,6 +113,21 @@ type Schema struct {
// //
// NOTE: This currently does not work. // NOTE: This currently does not work.
ComputedWhen []string ComputedWhen []string
// When Deprecated is set, this attribute is deprecated.
//
// A deprecated field still works, but will probably stop working in near
// future. This string is the message shown to the user with instructions on
// how to address the deprecation.
Deprecated string
// When Removed is set, this attribute has been removed from the schema
//
// Removed attributes can be left in the Schema to generate informative error
// messages for the user when they show up in resource configurations.
// This string is the message shown to the user with instructions on
// what do to about the removed attribute.
Removed string
} }
// SchemaDefaultFunc is a function called to return a default value for // SchemaDefaultFunc is a function called to return a default value for
@ -877,7 +892,7 @@ func (m schemaMap) validate(
raw, err = schema.DefaultFunc() raw, err = schema.DefaultFunc()
if err != nil { if err != nil {
return nil, []error{fmt.Errorf( return nil, []error{fmt.Errorf(
"%s, error loading default: %s", k, err)} "%q, error loading default: %s", k, err)}
} }
// We're okay as long as we had a value set // We're okay as long as we had a value set
@ -886,7 +901,7 @@ func (m schemaMap) validate(
if !ok { if !ok {
if schema.Required { if schema.Required {
return nil, []error{fmt.Errorf( return nil, []error{fmt.Errorf(
"%s: required field is not set", k)} "%q: required field is not set", k)}
} }
return nil, nil return nil, nil
@ -895,7 +910,7 @@ func (m schemaMap) validate(
if !schema.Required && !schema.Optional { if !schema.Required && !schema.Optional {
// This is a computed-only field // This is a computed-only field
return nil, []error{fmt.Errorf( return nil, []error{fmt.Errorf(
"%s: this field cannot be set", k)} "%q: this field cannot be set", k)}
} }
return m.validateType(k, raw, schema, c) return m.validateType(k, raw, schema, c)
@ -1066,16 +1081,30 @@ func (m schemaMap) validateType(
raw interface{}, raw interface{},
schema *Schema, schema *Schema,
c *terraform.ResourceConfig) ([]string, []error) { c *terraform.ResourceConfig) ([]string, []error) {
var ws []string
var es []error
switch schema.Type { switch schema.Type {
case TypeSet: case TypeSet:
fallthrough fallthrough
case TypeList: case TypeList:
return m.validateList(k, raw, schema, c) ws, es = m.validateList(k, raw, schema, c)
case TypeMap: case TypeMap:
return m.validateMap(k, raw, schema, c) ws, es = m.validateMap(k, raw, schema, c)
default: default:
return m.validatePrimitive(k, raw, schema, c) ws, es = m.validatePrimitive(k, raw, schema, c)
} }
if schema.Deprecated != "" {
ws = append(ws, fmt.Sprintf(
"%q: [DEPRECATED] %s", k, schema.Deprecated))
}
if schema.Removed != "" {
es = append(es, fmt.Errorf(
"%q: [REMOVED] %s", k, schema.Removed))
}
return ws, es
} }
// Zero returns the zero value for a type. // Zero returns the zero value for a type.

View File

@ -2583,15 +2583,15 @@ func TestSchemaMap_InternalValidate(t *testing.T) {
} }
func TestSchemaMap_Validate(t *testing.T) { func TestSchemaMap_Validate(t *testing.T) {
cases := []struct { cases := map[string]struct {
Schema map[string]*Schema Schema map[string]*Schema
Config map[string]interface{} Config map[string]interface{}
Vars map[string]string Vars map[string]string
Warn bool Err bool
Err bool Errors []error
Warnings []string
}{ }{
// #0 Good "Good": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"availability_zone": &Schema{ "availability_zone": &Schema{
Type: TypeString, Type: TypeString,
@ -2606,8 +2606,7 @@ func TestSchemaMap_Validate(t *testing.T) {
}, },
}, },
// #1 Good, because the var is not set and that error will come elsewhere "Good, because the var is not set and that error will come elsewhere": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"size": &Schema{ "size": &Schema{
Type: TypeInt, Type: TypeInt,
@ -2624,8 +2623,7 @@ func TestSchemaMap_Validate(t *testing.T) {
}, },
}, },
// #2 Required field not set "Required field not set": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"availability_zone": &Schema{ "availability_zone": &Schema{
Type: TypeString, Type: TypeString,
@ -2638,8 +2636,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #3 Invalid type "Invalid basic type": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"port": &Schema{ "port": &Schema{
Type: TypeInt, Type: TypeInt,
@ -2654,8 +2651,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #4 "Invalid complex type": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"user_data": &Schema{ "user_data": &Schema{
Type: TypeString, Type: TypeString,
@ -2674,8 +2670,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #5 Bad type, interpolated "Bad type, interpolated": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"size": &Schema{ "size": &Schema{
Type: TypeInt, Type: TypeInt,
@ -2694,8 +2689,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #6 Required but has DefaultFunc "Required but has DefaultFunc": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"availability_zone": &Schema{ "availability_zone": &Schema{
Type: TypeString, Type: TypeString,
@ -2709,8 +2703,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Config: nil, Config: nil,
}, },
// #7 Required but has DefaultFunc return nil "Required but has DefaultFunc return nil": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"availability_zone": &Schema{ "availability_zone": &Schema{
Type: TypeString, Type: TypeString,
@ -2726,8 +2719,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #8 Optional sub-resource "Optional sub-resource": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"ingress": &Schema{ "ingress": &Schema{
Type: TypeList, Type: TypeList,
@ -2747,8 +2739,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: false, Err: false,
}, },
// #9 Not a list "Not a list": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"ingress": &Schema{ "ingress": &Schema{
Type: TypeList, Type: TypeList,
@ -2770,8 +2761,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #10 Required sub-resource field "Required sub-resource field": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"ingress": &Schema{ "ingress": &Schema{
Type: TypeList, Type: TypeList,
@ -2795,8 +2785,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #11 Good sub-resource "Good sub-resource": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"ingress": &Schema{ "ingress": &Schema{
Type: TypeList, Type: TypeList,
@ -2823,8 +2812,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: false, Err: false,
}, },
// #12 Invalid/unknown field "Invalid/unknown field": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"availability_zone": &Schema{ "availability_zone": &Schema{
Type: TypeString, Type: TypeString,
@ -2841,8 +2829,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #13 Computed field set "Computed field set": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"availability_zone": &Schema{ "availability_zone": &Schema{
Type: TypeString, Type: TypeString,
@ -2857,8 +2844,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #14 Not a set "Not a set": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"ports": &Schema{ "ports": &Schema{
Type: TypeSet, Type: TypeSet,
@ -2877,8 +2863,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #15 Maps "Maps": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"user_data": &Schema{ "user_data": &Schema{
Type: TypeMap, Type: TypeMap,
@ -2893,8 +2878,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #16 "Good map: data surrounded by extra slice": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"user_data": &Schema{ "user_data": &Schema{
Type: TypeMap, Type: TypeMap,
@ -2911,8 +2895,7 @@ func TestSchemaMap_Validate(t *testing.T) {
}, },
}, },
// #17 "Good map": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"user_data": &Schema{ "user_data": &Schema{
Type: TypeMap, Type: TypeMap,
@ -2927,8 +2910,7 @@ func TestSchemaMap_Validate(t *testing.T) {
}, },
}, },
// #18 "Bad map: just a slice": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"user_data": &Schema{ "user_data": &Schema{
Type: TypeMap, Type: TypeMap,
@ -2945,8 +2927,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #19 "Good set: config has slice with single interpolated value": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"security_groups": &Schema{ "security_groups": &Schema{
Type: TypeSet, Type: TypeSet,
@ -2967,8 +2948,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: false, Err: false,
}, },
// #20 "Bad set: config has single interpolated value": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"security_groups": &Schema{ "security_groups": &Schema{
Type: TypeSet, Type: TypeSet,
@ -2986,8 +2966,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #21 Bad, subresource should not allow unknown elements "Bad, subresource should not allow unknown elements": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"ingress": &Schema{ "ingress": &Schema{
Type: TypeList, Type: TypeList,
@ -3015,8 +2994,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
// #22 Bad, subresource should not allow invalid types "Bad, subresource should not allow invalid types": {
{
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"ingress": &Schema{ "ingress": &Schema{
Type: TypeList, Type: TypeList,
@ -3042,9 +3020,74 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true, Err: true,
}, },
"Deprecated attribute usage generates warning, but not error": {
Schema: map[string]*Schema{
"old_news": &Schema{
Type: TypeString,
Optional: true,
Deprecated: "please use 'new_news' instead",
},
},
Config: map[string]interface{}{
"old_news": "extra extra!",
},
Err: false,
Warnings: []string{
"\"old_news\": [DEPRECATED] please use 'new_news' instead",
},
},
"Deprecated generates no warnings if attr not used": {
Schema: map[string]*Schema{
"old_news": &Schema{
Type: TypeString,
Optional: true,
Deprecated: "please use 'new_news' instead",
},
},
Err: false,
Warnings: nil,
},
"Removed attribute usage generates error": {
Schema: map[string]*Schema{
"long_gone": &Schema{
Type: TypeString,
Optional: true,
Removed: "no longer supported by Cloud API",
},
},
Config: map[string]interface{}{
"long_gone": "still here!",
},
Err: true,
Errors: []error{
fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"),
},
},
"Removed generates no errors if attr not used": {
Schema: map[string]*Schema{
"long_gone": &Schema{
Type: TypeString,
Optional: true,
Removed: "no longer supported by Cloud API",
},
},
Err: false,
},
} }
for i, tc := range cases { for tn, tc := range cases {
c, err := config.NewRawConfig(tc.Config) c, err := config.NewRawConfig(tc.Config)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
@ -3063,18 +3106,24 @@ func TestSchemaMap_Validate(t *testing.T) {
ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
if (len(es) > 0) != tc.Err { if (len(es) > 0) != tc.Err {
if len(es) == 0 { if len(es) == 0 {
t.Errorf("%d: no errors", i) t.Errorf("%q: no errors", tn)
} }
for _, e := range es { for _, e := range es {
t.Errorf("%d: err: %s", i, e) t.Errorf("%q: err: %s", tn, e)
} }
t.FailNow() t.FailNow()
} }
if (len(ws) > 0) != tc.Warn { if !reflect.DeepEqual(ws, tc.Warnings) {
t.Fatalf("%d: ws: %#v", i, ws) t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws)
}
if tc.Errors != nil {
if !reflect.DeepEqual(es, tc.Errors) {
t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es)
}
} }
} }
} }

View File

@ -14,7 +14,7 @@ import (
"sync" "sync"
"time" "time"
"code.google.com/p/go.crypto/ssh" "golang.org/x/crypto/ssh"
) )
// RemoteCmd represents a remote command being prepared or run. // RemoteCmd represents a remote command being prepared or run.

View File

@ -4,7 +4,7 @@ package ssh
import ( import (
"bytes" "bytes"
"code.google.com/p/go.crypto/ssh" "golang.org/x/crypto/ssh"
"fmt" "fmt"
"net" "net"
"testing" "testing"

View File

@ -1,7 +1,7 @@
package ssh package ssh
import ( import (
"code.google.com/p/go.crypto/ssh" "golang.org/x/crypto/ssh"
"log" "log"
) )

View File

@ -1,7 +1,7 @@
package ssh package ssh
import ( import (
"code.google.com/p/go.crypto/ssh" "golang.org/x/crypto/ssh"
"reflect" "reflect"
"testing" "testing"
) )

View File

@ -7,7 +7,7 @@ import (
"log" "log"
"time" "time"
"code.google.com/p/go.crypto/ssh" "golang.org/x/crypto/ssh"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"

View File

@ -19,9 +19,11 @@ GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)
XC_ARCH=${XC_ARCH:-"386 amd64 arm"} XC_ARCH=${XC_ARCH:-"386 amd64 arm"}
XC_OS=${XC_OS:-linux darwin windows freebsd openbsd} XC_OS=${XC_OS:-linux darwin windows freebsd openbsd}
# Install dependencies # Install dependencies unless running in quick mode
echo "==> Getting dependencies..." if [ "${TF_QUICKDEV}x" == "x" ]; then
go get ./... echo "==> Getting dependencies..."
go get ./...
fi
# Delete the old dir # Delete the old dir
echo "==> Removing old directory..." echo "==> Removing old directory..."

64
state/remote/file.go Normal file
View File

@ -0,0 +1,64 @@
package remote
import (
"bytes"
"crypto/md5"
"fmt"
"io"
"os"
)
func fileFactory(conf map[string]string) (Client, error) {
path, ok := conf["path"]
if !ok {
return nil, fmt.Errorf("missing 'path' configuration")
}
return &FileClient{
Path: path,
}, nil
}
// FileClient is a remote client that stores data locally on disk.
// This is only used for development reasons to test remote state... locally.
type FileClient struct {
Path string
}
func (c *FileClient) Get() (*Payload, error) {
var buf bytes.Buffer
f, err := os.Open(c.Path)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
defer f.Close()
if _, err := io.Copy(&buf, f); err != nil {
return nil, err
}
md5 := md5.Sum(buf.Bytes())
return &Payload{
Data: buf.Bytes(),
MD5: md5[:],
}, nil
}
func (c *FileClient) Put(data []byte) error {
f, err := os.Create(c.Path)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(data)
return err
}
func (c *FileClient) Delete() error {
return os.Remove(c.Path)
}

29
state/remote/file_test.go Normal file
View File

@ -0,0 +1,29 @@
package remote
import (
"io/ioutil"
"os"
"testing"
)
func TestFileClient_impl(t *testing.T) {
var _ Client = new(FileClient)
}
func TestFileClient(t *testing.T) {
tf, err := ioutil.TempFile("", "tf")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
defer os.Remove(tf.Name())
client, err := fileFactory(map[string]string{
"path": tf.Name(),
})
if err != nil {
t.Fatalf("bad: %s", err)
}
testClient(t, client)
}

View File

@ -39,4 +39,7 @@ var BuiltinClients = map[string]Factory{
"atlas": atlasFactory, "atlas": atlasFactory,
"consul": consulFactory, "consul": consulFactory,
"http": httpFactory, "http": httpFactory,
// This is used for development purposes only.
"_local": fileFactory,
} }

View File

@ -3845,6 +3845,136 @@ func TestContext2Apply_errorDestroy_createBeforeDestroy(t *testing.T) {
} }
} }
func TestContext2Apply_multiDepose_createBeforeDestroy(t *testing.T) {
m := testModule(t, "apply-multi-depose-create-before-destroy")
p := testProvider("aws")
p.DiffFn = testDiffFn
ps := map[string]ResourceProviderFactory{"aws": testProviderFuncFixed(p)}
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.web": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{ID: "foo"},
},
},
},
},
}
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: ps,
State: state,
})
createdInstanceId := "bar"
// Create works
createFunc := func(is *InstanceState) (*InstanceState, error) {
return &InstanceState{ID: createdInstanceId}, nil
}
// Destroy starts broken
destroyFunc := func(is *InstanceState) (*InstanceState, error) {
return is, fmt.Errorf("destroy failed")
}
p.ApplyFn = func(info *InstanceInfo, is *InstanceState, id *InstanceDiff) (*InstanceState, error) {
if id.Destroy {
return destroyFunc(is)
} else {
return createFunc(is)
}
}
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
// Destroy is broken, so even though CBD successfully replaces the instance,
// we'll have to save the Deposed instance to destroy later
state, err := ctx.Apply()
if err == nil {
t.Fatal("should have error")
}
checkStateString(t, state, `
aws_instance.web: (1 deposed)
ID = bar
Deposed ID 1 = foo
`)
createdInstanceId = "baz"
ctx = testContext2(t, &ContextOpts{
Module: m,
Providers: ps,
State: state,
})
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
// We're replacing the primary instance once again. Destroy is _still_
// broken, so the Deposed list gets longer
state, err = ctx.Apply()
if err == nil {
t.Fatal("should have error")
}
checkStateString(t, state, `
aws_instance.web: (2 deposed)
ID = baz
Deposed ID 1 = foo
Deposed ID 2 = bar
`)
// Destroy partially fixed!
destroyFunc = func(is *InstanceState) (*InstanceState, error) {
if is.ID == "foo" || is.ID == "baz" {
return nil, nil
} else {
return is, fmt.Errorf("destroy partially failed")
}
}
createdInstanceId = "qux"
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
state, err = ctx.Apply()
// Expect error because 1/2 of Deposed destroys failed
if err == nil {
t.Fatal("should have error")
}
// foo and baz are now gone, bar sticks around
checkStateString(t, state, `
aws_instance.web: (1 deposed)
ID = qux
Deposed ID 1 = bar
`)
// Destroy working fully!
destroyFunc = func(is *InstanceState) (*InstanceState, error) {
return nil, nil
}
createdInstanceId = "quux"
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
state, err = ctx.Apply()
if err != nil {
t.Fatal("should not have error:", err)
}
// And finally the state is clean
checkStateString(t, state, `
aws_instance.web:
ID = quux
`)
}
func TestContext2Apply_provisionerResourceRef(t *testing.T) { func TestContext2Apply_provisionerResourceRef(t *testing.T) {
m := testModule(t, "apply-provisioner-resource-ref") m := testModule(t, "apply-provisioner-resource-ref")
p := testProvider("aws") p := testProvider("aws")
@ -5343,6 +5473,15 @@ func testProvisioner() *MockResourceProvisioner {
return p return p
} }
func checkStateString(t *testing.T, state *State, expected string) {
actual := strings.TrimSpace(state.String())
expected = strings.TrimSpace(expected)
if actual != expected {
t.Fatalf("state does not match! actual:\n%s\n\nexpected:\n%s", actual, expected)
}
}
const testContextGraph = ` const testContextGraph = `
root: root root: root
aws_instance.bar aws_instance.bar

20
terraform/eval_error.go Normal file
View File

@ -0,0 +1,20 @@
package terraform
// EvalReturnError is an EvalNode implementation that returns an
// error if it is present.
//
// This is useful for scenarios where an error has been captured by
// another EvalNode (like EvalApply) for special EvalTree-based error
// handling, and that handling has completed, so the error should be
// returned normally.
type EvalReturnError struct {
Error *error
}
func (n *EvalReturnError) Eval(ctx EvalContext) (interface{}, error) {
if n.Error == nil {
return nil, nil
}
return nil, *n.Error
}

View File

@ -3,7 +3,8 @@ package terraform
// EvalIf is an EvalNode that is a conditional. // EvalIf is an EvalNode that is a conditional.
type EvalIf struct { type EvalIf struct {
If func(EvalContext) (bool, error) If func(EvalContext) (bool, error)
Node EvalNode Then EvalNode
Else EvalNode
} }
// TODO: test // TODO: test
@ -14,7 +15,11 @@ func (n *EvalIf) Eval(ctx EvalContext) (interface{}, error) {
} }
if yes { if yes {
return EvalRaw(n.Node, ctx) return EvalRaw(n.Then, ctx)
} else {
if n.Else != nil {
return EvalRaw(n.Else, ctx)
}
} }
return nil, nil return nil, nil

View File

@ -5,16 +5,77 @@ import (
) )
// EvalReadState is an EvalNode implementation that reads the // EvalReadState is an EvalNode implementation that reads the
// InstanceState for a specific resource out of the state. // primary InstanceState for a specific resource out of the state.
type EvalReadState struct { type EvalReadState struct {
Name string Name string
Tainted bool Output **InstanceState
TaintedIndex int
Output **InstanceState
} }
// TODO: test
func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) { func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) {
return readInstanceFromState(ctx, n.Name, n.Output, func(rs *ResourceState) (*InstanceState, error) {
return rs.Primary, nil
})
}
// EvalReadStateTainted is an EvalNode implementation that reads a
// tainted InstanceState for a specific resource out of the state
type EvalReadStateTainted struct {
Name string
Output **InstanceState
// Index indicates which instance in the Tainted list to target, or -1 for
// the last item.
Index int
}
func (n *EvalReadStateTainted) Eval(ctx EvalContext) (interface{}, error) {
return readInstanceFromState(ctx, n.Name, n.Output, func(rs *ResourceState) (*InstanceState, error) {
// Get the index. If it is negative, then we get the last one
idx := n.Index
if idx < 0 {
idx = len(rs.Tainted) - 1
}
if idx >= 0 && idx < len(rs.Tainted) {
return rs.Tainted[idx], nil
} else {
return nil, fmt.Errorf("bad tainted index: %d, for resource: %#v", idx, rs)
}
})
}
// EvalReadStateDeposed is an EvalNode implementation that reads the
// deposed InstanceState for a specific resource out of the state
type EvalReadStateDeposed struct {
Name string
Output **InstanceState
// Index indicates which instance in the Deposed list to target, or -1 for
// the last item.
Index int
}
func (n *EvalReadStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
return readInstanceFromState(ctx, n.Name, n.Output, func(rs *ResourceState) (*InstanceState, error) {
// Get the index. If it is negative, then we get the last one
idx := n.Index
if idx < 0 {
idx = len(rs.Deposed) - 1
}
if idx >= 0 && idx < len(rs.Deposed) {
return rs.Deposed[idx], nil
} else {
return nil, fmt.Errorf("bad deposed index: %d, for resource: %#v", idx, rs)
}
})
}
// Does the bulk of the work for the various flavors of ReadState eval nodes.
// Each node just provides a reader function to get from the ResourceState to the
// InstanceState, and this takes care of all the plumbing.
func readInstanceFromState(
ctx EvalContext,
resourceName string,
output **InstanceState,
readerFn func(*ResourceState) (*InstanceState, error),
) (*InstanceState, error) {
state, lock := ctx.State() state, lock := ctx.State()
// Get a read lock so we can access this instance // Get a read lock so we can access this instance
@ -28,33 +89,23 @@ func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) {
} }
// Look for the resource state. If we don't have one, then it is okay. // Look for the resource state. If we don't have one, then it is okay.
rs := mod.Resources[n.Name] rs := mod.Resources[resourceName]
if rs == nil { if rs == nil {
return nil, nil return nil, nil
} }
var result *InstanceState // Use the delegate function to get the instance state from the resource state
if !n.Tainted { is, err := readerFn(rs)
// Return the primary if err != nil {
result = rs.Primary return nil, err
} else {
// Get the index. If it is negative, then we get the last one
idx := n.TaintedIndex
if idx < 0 {
idx = len(rs.Tainted) - 1
}
if idx >= 0 && idx < len(rs.Tainted) {
// Return the proper tainted resource
result = rs.Tainted[idx]
}
} }
// Write the result to the output pointer // Write the result to the output pointer
if n.Output != nil { if output != nil {
*n.Output = result *output = is
} }
return result, nil return is, nil
} }
// EvalRequireState is an EvalNode implementation that early exits // EvalRequireState is an EvalNode implementation that early exits
@ -98,20 +149,85 @@ func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) {
return nil, nil return nil, nil
} }
// EvalWriteState is an EvalNode implementation that reads the // EvalWriteState is an EvalNode implementation that writes the
// InstanceState for a specific resource out of the state. // primary InstanceState for a specific resource into the state.
type EvalWriteState struct { type EvalWriteState struct {
Name string Name string
ResourceType string ResourceType string
Dependencies []string Dependencies []string
State **InstanceState State **InstanceState
Tainted *bool
TaintedIndex int
TaintedClearPrimary bool
} }
// TODO: test
func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) { func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Dependencies,
func(rs *ResourceState) error {
rs.Primary = *n.State
return nil
},
)
}
// EvalWriteStateTainted is an EvalNode implementation that writes
// an InstanceState out to the Tainted list of a resource in the state.
type EvalWriteStateTainted struct {
Name string
ResourceType string
Dependencies []string
State **InstanceState
// Index indicates which instance in the Tainted list to target, or -1 to append.
Index int
}
// EvalWriteStateTainted is an EvalNode implementation that writes the
// one of the tainted InstanceStates for a specific resource out of the state.
func (n *EvalWriteStateTainted) Eval(ctx EvalContext) (interface{}, error) {
return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Dependencies,
func(rs *ResourceState) error {
if n.Index == -1 {
rs.Tainted = append(rs.Tainted, *n.State)
} else {
rs.Tainted[n.Index] = *n.State
}
return nil
},
)
}
// EvalWriteStateDeposed is an EvalNode implementation that writes
// an InstanceState out to the Deposed list of a resource in the state.
type EvalWriteStateDeposed struct {
Name string
ResourceType string
Dependencies []string
State **InstanceState
// Index indicates which instance in the Deposed list to target, or -1 to append.
Index int
}
func (n *EvalWriteStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Dependencies,
func(rs *ResourceState) error {
if n.Index == -1 {
rs.Deposed = append(rs.Deposed, *n.State)
} else {
rs.Deposed[n.Index] = *n.State
}
return nil
},
)
}
// Pulls together the common tasks of the EvalWriteState nodes. All the args
// are passed directly down from the EvalNode along with a `writer` function
// which is yielded the *ResourceState and is responsible for writing an
// InstanceState to the proper field in the ResourceState.
func writeInstanceToState(
ctx EvalContext,
resourceName string,
resourceType string,
dependencies []string,
writerFn func(*ResourceState) error,
) (*InstanceState, error) {
state, lock := ctx.State() state, lock := ctx.State()
if state == nil { if state == nil {
return nil, fmt.Errorf("cannot write state to nil state") return nil, fmt.Errorf("cannot write state to nil state")
@ -128,35 +244,55 @@ func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
} }
// Look for the resource state. // Look for the resource state.
rs := mod.Resources[n.Name] rs := mod.Resources[resourceName]
if rs == nil { if rs == nil {
rs = &ResourceState{} rs = &ResourceState{}
rs.init() rs.init()
mod.Resources[n.Name] = rs mod.Resources[resourceName] = rs
} }
rs.Type = n.ResourceType rs.Type = resourceType
rs.Dependencies = n.Dependencies rs.Dependencies = dependencies
if n.Tainted != nil && *n.Tainted { if err := writerFn(rs); err != nil {
if n.TaintedIndex != -1 { return nil, err
rs.Tainted[n.TaintedIndex] = *n.State
} else {
rs.Tainted = append(rs.Tainted, *n.State)
}
if n.TaintedClearPrimary {
rs.Primary = nil
}
} else {
// Set the primary state
rs.Primary = *n.State
} }
return nil, nil return nil, nil
} }
// EvalClearPrimaryState is an EvalNode implementation that clears the primary
// instance from a resource state.
type EvalClearPrimaryState struct {
Name string
}
func (n *EvalClearPrimaryState) Eval(ctx EvalContext) (interface{}, error) {
state, lock := ctx.State()
// Get a read lock so we can access this instance
lock.RLock()
defer lock.RUnlock()
// Look for the module state. If we don't have one, then it doesn't matter.
mod := state.ModuleByPath(ctx.Path())
if mod == nil {
return nil, nil
}
// Look for the resource state. If we don't have one, then it is okay.
rs := mod.Resources[n.Name]
if rs == nil {
return nil, nil
}
// Clear primary from the resource state
rs.Primary = nil
return nil, nil
}
// EvalDeposeState is an EvalNode implementation that takes the primary // EvalDeposeState is an EvalNode implementation that takes the primary
// out of a state and makes it tainted. This is done at the beggining of // out of a state and makes it Deposed. This is done at the beginning of
// create-before-destroy calls so that the create can create while preserving // create-before-destroy calls so that the create can create while preserving
// the old state of the to-be-destroyed resource. // the old state of the to-be-destroyed resource.
type EvalDeposeState struct { type EvalDeposeState struct {
@ -188,8 +324,8 @@ func (n *EvalDeposeState) Eval(ctx EvalContext) (interface{}, error) {
return nil, nil return nil, nil
} }
// Depose to the tainted // Depose
rs.Tainted = append(rs.Tainted, rs.Primary) rs.Deposed = append(rs.Deposed, rs.Primary)
rs.Primary = nil rs.Primary = nil
return nil, nil return nil, nil
@ -221,15 +357,15 @@ func (n *EvalUndeposeState) Eval(ctx EvalContext) (interface{}, error) {
return nil, nil return nil, nil
} }
// If we don't have any tainted, then we don't have anything to do // If we don't have any desposed resource, then we don't have anything to do
if len(rs.Tainted) == 0 { if len(rs.Deposed) == 0 {
return nil, nil return nil, nil
} }
// Undepose to the tainted // Undepose
idx := len(rs.Tainted) - 1 idx := len(rs.Deposed) - 1
rs.Primary = rs.Tainted[idx] rs.Primary = rs.Deposed[idx]
rs.Tainted[idx] = nil rs.Deposed[idx] = nil
return nil, nil return nil, nil
} }

View File

@ -66,3 +66,163 @@ func TestEvalUpdateStateHook(t *testing.T) {
t.Fatalf("bad: %#v", mockHook.PostStateUpdateState) t.Fatalf("bad: %#v", mockHook.PostStateUpdateState)
} }
} }
func TestEvalReadState(t *testing.T) {
var output *InstanceState
cases := map[string]struct {
Resources map[string]*ResourceState
Node EvalNode
ExpectedInstanceId string
}{
"ReadState gets primary instance state": {
Resources: map[string]*ResourceState{
"aws_instance.bar": &ResourceState{
Primary: &InstanceState{
ID: "i-abc123",
},
},
},
Node: &EvalReadState{
Name: "aws_instance.bar",
Output: &output,
},
ExpectedInstanceId: "i-abc123",
},
"ReadStateTainted gets tainted instance": {
Resources: map[string]*ResourceState{
"aws_instance.bar": &ResourceState{
Tainted: []*InstanceState{
&InstanceState{ID: "i-abc123"},
},
},
},
Node: &EvalReadStateTainted{
Name: "aws_instance.bar",
Output: &output,
Index: 0,
},
ExpectedInstanceId: "i-abc123",
},
"ReadStateDeposed gets deposed instance": {
Resources: map[string]*ResourceState{
"aws_instance.bar": &ResourceState{
Deposed: []*InstanceState{
&InstanceState{ID: "i-abc123"},
},
},
},
Node: &EvalReadStateDeposed{
Name: "aws_instance.bar",
Output: &output,
Index: 0,
},
ExpectedInstanceId: "i-abc123",
},
}
for k, c := range cases {
ctx := new(MockEvalContext)
ctx.StateState = &State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: c.Resources,
},
},
}
ctx.StateLock = new(sync.RWMutex)
ctx.PathPath = rootModulePath
result, err := c.Node.Eval(ctx)
if err != nil {
t.Fatalf("[%s] Got err: %#v", k, err)
}
expected := c.ExpectedInstanceId
if !(result != nil && result.(*InstanceState).ID == expected) {
t.Fatalf("[%s] Expected return with ID %#v, got: %#v", k, expected, result)
}
if !(output != nil && output.ID == expected) {
t.Fatalf("[%s] Expected output with ID %#v, got: %#v", k, expected, output)
}
output = nil
}
}
func TestEvalWriteState(t *testing.T) {
state := &State{}
ctx := new(MockEvalContext)
ctx.StateState = state
ctx.StateLock = new(sync.RWMutex)
ctx.PathPath = rootModulePath
is := &InstanceState{ID: "i-abc123"}
node := &EvalWriteState{
Name: "restype.resname",
ResourceType: "restype",
State: &is,
}
_, err := node.Eval(ctx)
if err != nil {
t.Fatalf("Got err: %#v", err)
}
checkStateString(t, state, `
restype.resname:
ID = i-abc123
`)
}
func TestEvalWriteStateTainted(t *testing.T) {
state := &State{}
ctx := new(MockEvalContext)
ctx.StateState = state
ctx.StateLock = new(sync.RWMutex)
ctx.PathPath = rootModulePath
is := &InstanceState{ID: "i-abc123"}
node := &EvalWriteStateTainted{
Name: "restype.resname",
ResourceType: "restype",
State: &is,
Index: -1,
}
_, err := node.Eval(ctx)
if err != nil {
t.Fatalf("Got err: %#v", err)
}
checkStateString(t, state, `
restype.resname: (1 tainted)
ID = <not created>
Tainted ID 1 = i-abc123
`)
}
func TestEvalWriteStateDeposed(t *testing.T) {
state := &State{}
ctx := new(MockEvalContext)
ctx.StateState = state
ctx.StateLock = new(sync.RWMutex)
ctx.PathPath = rootModulePath
is := &InstanceState{ID: "i-abc123"}
node := &EvalWriteStateDeposed{
Name: "restype.resname",
ResourceType: "restype",
State: &is,
Index: -1,
}
_, err := node.Eval(ctx)
if err != nil {
t.Fatalf("Got err: %#v", err)
}
checkStateString(t, state, `
restype.resname: (1 deposed)
ID = <not created>
Deposed ID 1 = i-abc123
`)
}

View File

@ -293,24 +293,16 @@ func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error)
View: n.Resource.Id(), View: n.Resource.Id(),
}) })
if n.Resource.Lifecycle.CreateBeforeDestroy { steps = append(steps, &DeposedTransformer{
// If we're only destroying tainted resources, then we only State: state,
// want to find tainted resources and destroy them here. View: n.Resource.Id(),
steps = append(steps, &TaintedTransformer{ })
State: state,
View: n.Resource.Id(),
Deposed: n.Resource.Lifecycle.CreateBeforeDestroy,
DeposedInclude: true,
})
}
case DestroyTainted: case DestroyTainted:
// If we're only destroying tainted resources, then we only // If we're only destroying tainted resources, then we only
// want to find tainted resources and destroy them here. // want to find tainted resources and destroy them here.
steps = append(steps, &TaintedTransformer{ steps = append(steps, &TaintedTransformer{
State: state, State: state,
View: n.Resource.Id(), View: n.Resource.Id(),
Deposed: n.Resource.Lifecycle.CreateBeforeDestroy,
DeposedInclude: false,
}) })
} }

View File

@ -498,7 +498,7 @@ func (m *ModuleState) prune() {
for k, v := range m.Resources { for k, v := range m.Resources {
v.prune() v.prune()
if (v.Primary == nil || v.Primary.ID == "") && len(v.Tainted) == 0 { if (v.Primary == nil || v.Primary.ID == "") && len(v.Tainted) == 0 && len(v.Deposed) == 0 {
delete(m.Resources, k) delete(m.Resources, k)
} }
} }
@ -548,7 +548,12 @@ func (m *ModuleState) String() string {
taintStr = fmt.Sprintf(" (%d tainted)", len(rs.Tainted)) taintStr = fmt.Sprintf(" (%d tainted)", len(rs.Tainted))
} }
buf.WriteString(fmt.Sprintf("%s:%s\n", k, taintStr)) deposedStr := ""
if len(rs.Deposed) > 0 {
deposedStr = fmt.Sprintf(" (%d deposed)", len(rs.Deposed))
}
buf.WriteString(fmt.Sprintf("%s:%s%s\n", k, taintStr, deposedStr))
buf.WriteString(fmt.Sprintf(" ID = %s\n", id)) buf.WriteString(fmt.Sprintf(" ID = %s\n", id))
var attributes map[string]string var attributes map[string]string
@ -574,6 +579,10 @@ func (m *ModuleState) String() string {
buf.WriteString(fmt.Sprintf(" Tainted ID %d = %s\n", idx+1, t.ID)) buf.WriteString(fmt.Sprintf(" Tainted ID %d = %s\n", idx+1, t.ID))
} }
for idx, t := range rs.Deposed {
buf.WriteString(fmt.Sprintf(" Deposed ID %d = %s\n", idx+1, t.ID))
}
if len(rs.Dependencies) > 0 { if len(rs.Dependencies) > 0 {
buf.WriteString(fmt.Sprintf("\n Dependencies:\n")) buf.WriteString(fmt.Sprintf("\n Dependencies:\n"))
for _, dep := range rs.Dependencies { for _, dep := range rs.Dependencies {
@ -644,6 +653,16 @@ type ResourceState struct {
// However, in pathological cases, it is possible for the number // However, in pathological cases, it is possible for the number
// of instances to accumulate. // of instances to accumulate.
Tainted []*InstanceState `json:"tainted,omitempty"` Tainted []*InstanceState `json:"tainted,omitempty"`
// Deposed is used in the mechanics of CreateBeforeDestroy: the existing
// Primary is Deposed to get it out of the way for the replacement Primary to
// be created by Apply. If the replacement Primary creates successfully, the
// Deposed instance is cleaned up. If there were problems creating the
// replacement, the instance remains in the Deposed list so it can be
// destroyed in a future run. Functionally, Deposed instances are very
// similar to Tainted instances in that Terraform is only tracking them in
// order to remember to destroy them.
Deposed []*InstanceState `json:"deposed,omitempty"`
} }
// Equal tests whether two ResourceStates are equal. // Equal tests whether two ResourceStates are equal.
@ -744,6 +763,12 @@ func (r *ResourceState) deepcopy() *ResourceState {
n.Tainted = append(n.Tainted, inst.deepcopy()) n.Tainted = append(n.Tainted, inst.deepcopy())
} }
} }
if r.Deposed != nil {
n.Deposed = make([]*InstanceState, 0, len(r.Deposed))
for _, inst := range r.Deposed {
n.Deposed = append(n.Deposed, inst.deepcopy())
}
}
return n return n
} }
@ -762,6 +787,19 @@ func (r *ResourceState) prune() {
} }
r.Tainted = r.Tainted[:n] r.Tainted = r.Tainted[:n]
n = len(r.Deposed)
for i := 0; i < n; i++ {
inst := r.Deposed[i]
if inst == nil || inst.ID == "" {
copy(r.Deposed[i:], r.Deposed[i+1:])
r.Deposed[n-1] = nil
n--
i--
}
}
r.Deposed = r.Deposed[:n]
} }
func (r *ResourceState) sort() { func (r *ResourceState) sort() {
@ -794,6 +832,11 @@ type InstanceState struct {
// that is necessary for the Terraform run to complete, but is not // that is necessary for the Terraform run to complete, but is not
// persisted to a state file. // persisted to a state file.
Ephemeral EphemeralState `json:"-"` Ephemeral EphemeralState `json:"-"`
// Meta is a simple K/V map that is persisted to the State but otherwise
// ignored by Terraform core. It's meant to be used for accounting by
// external client code.
Meta map[string]string `json:"meta,omitempty"`
} }
func (i *InstanceState) init() { func (i *InstanceState) init() {

View File

@ -443,9 +443,9 @@ aws_instance.bar:
` `
const testTerraformApplyErrorDestroyCreateBeforeDestroyStr = ` const testTerraformApplyErrorDestroyCreateBeforeDestroyStr = `
aws_instance.bar: (1 tainted) aws_instance.bar: (1 deposed)
ID = foo ID = foo
Tainted ID 1 = bar Deposed ID 1 = bar
` `
const testTerraformApplyErrorPartialStr = ` const testTerraformApplyErrorPartialStr = `

View File

@ -0,0 +1,8 @@
resource "aws_instance" "web" {
// require_new is a special attribute recognized by testDiffFn that forces
// a new resource on every apply
require_new = "yes"
lifecycle {
create_before_destroy = true
}
}

View File

@ -0,0 +1,153 @@
package terraform
import "fmt"
// DeposedTransformer is a GraphTransformer that adds deposed resources
// to the graph.
type DeposedTransformer struct {
// State is the global state. We'll automatically find the correct
// ModuleState based on the Graph.Path that is being transformed.
State *State
// View, if non-empty, is the ModuleState.View used around the state
// to find deposed resources.
View string
}
func (t *DeposedTransformer) Transform(g *Graph) error {
state := t.State.ModuleByPath(g.Path)
if state == nil {
// If there is no state for our module there can't be any deposed
// resources, since they live in the state.
return nil
}
// If we have a view, apply it now
if t.View != "" {
state = state.View(t.View)
}
// Go through all the resources in our state to look for deposed resources
for k, rs := range state.Resources {
// If we have no deposed resources, then move on
if len(rs.Deposed) == 0 {
continue
}
deposed := rs.Deposed
for i, _ := range deposed {
g.Add(&graphNodeDeposedResource{
Index: i,
ResourceName: k,
ResourceType: rs.Type,
})
}
}
return nil
}
// graphNodeDeposedResource is the graph vertex representing a deposed resource.
type graphNodeDeposedResource struct {
Index int
ResourceName string
ResourceType string
}
func (n *graphNodeDeposedResource) Name() string {
return fmt.Sprintf("%s (deposed #%d)", n.ResourceName, n.Index)
}
func (n *graphNodeDeposedResource) ProvidedBy() []string {
return []string{resourceProvider(n.ResourceName)}
}
// GraphNodeEvalable impl.
func (n *graphNodeDeposedResource) EvalTree() EvalNode {
var provider ResourceProvider
var state *InstanceState
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
// Build instance info
info := &InstanceInfo{Id: n.ResourceName, Type: n.ResourceType}
seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})
// Refresh the resource
seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkRefresh},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadStateDeposed{
Name: n.ResourceName,
Output: &state,
Index: n.Index,
},
&EvalRefresh{
Info: info,
Provider: &provider,
State: &state,
Output: &state,
},
&EvalWriteStateDeposed{
Name: n.ResourceName,
ResourceType: n.ResourceType,
State: &state,
Index: n.Index,
},
},
},
})
// Apply
var diff *InstanceDiff
var err error
seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkApply},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadStateDeposed{
Name: n.ResourceName,
Output: &state,
Index: n.Index,
},
&EvalDiffDestroy{
Info: info,
State: &state,
Output: &diff,
},
&EvalApply{
Info: info,
State: &state,
Diff: &diff,
Provider: &provider,
Output: &state,
Error: &err,
},
// Always write the resource back to the state deposed... if it
// was successfully destroyed it will be pruned. If it was not, it will
// be caught on the next run.
&EvalWriteStateDeposed{
Name: n.ResourceName,
ResourceType: n.ResourceType,
State: &state,
Index: n.Index,
},
&EvalReturnError{
Error: &err,
},
&EvalUpdateStateHook{},
},
},
})
return seq
}

View File

@ -285,7 +285,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
diffApply.Destroy = false diffApply.Destroy = false
return true, nil return true, nil
}, },
Node: EvalNoop{}, Then: EvalNoop{},
}, },
&EvalIf{ &EvalIf{
@ -301,7 +301,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
return createBeforeDestroyEnabled, nil return createBeforeDestroyEnabled, nil
}, },
Node: &EvalDeposeState{ Then: &EvalDeposeState{
Name: n.stateId(), Name: n.stateId(),
}, },
}, },
@ -382,7 +382,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
failure := tainted || err != nil failure := tainted || err != nil
return createBeforeDestroyEnabled && failure, nil return createBeforeDestroyEnabled && failure, nil
}, },
Node: &EvalUndeposeState{ Then: &EvalUndeposeState{
Name: n.stateId(), Name: n.stateId(),
}, },
}, },
@ -395,14 +395,35 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
Diff: nil, Diff: nil,
}, },
&EvalWriteState{ &EvalIf{
Name: n.stateId(), If: func(ctx EvalContext) (bool, error) {
ResourceType: n.Resource.Type, return tainted, nil
Dependencies: n.DependentOn(), },
State: &state, Then: &EvalSequence{
Tainted: &tainted, Nodes: []EvalNode{
TaintedIndex: -1, &EvalWriteStateTainted{
TaintedClearPrimary: !n.Resource.Lifecycle.CreateBeforeDestroy, Name: n.stateId(),
ResourceType: n.Resource.Type,
Dependencies: n.DependentOn(),
State: &state,
Index: -1,
},
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
return !n.Resource.Lifecycle.CreateBeforeDestroy, nil
},
Then: &EvalClearPrimaryState{
Name: n.stateId(),
},
},
},
},
Else: &EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Dependencies: n.DependentOn(),
State: &state,
},
}, },
&EvalApplyPost{ &EvalApplyPost{
Info: info, Info: info,
@ -480,18 +501,26 @@ func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
return true, EvalEarlyExitError{} return true, EvalEarlyExitError{}
}, },
Node: EvalNoop{}, Then: EvalNoop{},
}, },
&EvalGetProvider{ &EvalGetProvider{
Name: n.ProvidedBy()[0], Name: n.ProvidedBy()[0],
Output: &provider, Output: &provider,
}, },
&EvalReadState{ &EvalIf{
Name: n.stateId(), If: func(ctx EvalContext) (bool, error) {
Output: &state, return n.Resource.Lifecycle.CreateBeforeDestroy, nil
Tainted: n.Resource.Lifecycle.CreateBeforeDestroy, },
TaintedIndex: -1, Then: &EvalReadStateTainted{
Name: n.stateId(),
Output: &state,
Index: -1,
},
Else: &EvalReadState{
Name: n.stateId(),
Output: &state,
},
}, },
&EvalRequireState{ &EvalRequireState{
State: &state, State: &state,

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
) )
// TraintedTransformer is a GraphTransformer that adds tainted resources // TaintedTransformer is a GraphTransformer that adds tainted resources
// to the graph. // to the graph.
type TaintedTransformer struct { type TaintedTransformer struct {
// State is the global state. We'll automatically find the correct // State is the global state. We'll automatically find the correct
@ -14,12 +14,6 @@ type TaintedTransformer struct {
// View, if non-empty, is the ModuleState.View used around the state // View, if non-empty, is the ModuleState.View used around the state
// to find tainted resources. // to find tainted resources.
View string View string
// Deposed, if set to true, assumes that the last tainted index
// represents a "deposed" resource, or a resource that was previously
// a primary but is now tainted since it is demoted.
Deposed bool
DeposedInclude bool
} }
func (t *TaintedTransformer) Transform(g *Graph) error { func (t *TaintedTransformer) Transform(g *Graph) error {
@ -43,17 +37,6 @@ func (t *TaintedTransformer) Transform(g *Graph) error {
} }
tainted := rs.Tainted tainted := rs.Tainted
// If we expect a deposed resource, then shuffle a bit
if t.Deposed {
if t.DeposedInclude {
// Only include the deposed resource
tainted = rs.Tainted[len(rs.Tainted)-1:]
} else {
// Exclude the deposed resource
tainted = rs.Tainted[:len(rs.Tainted)-1]
}
}
for i, _ := range tainted { for i, _ := range tainted {
// Add the graph node and make the connection from any untainted // Add the graph node and make the connection from any untainted
// resources with this name to the tainted resource, so that // resources with this name to the tainted resource, so that
@ -88,7 +71,6 @@ func (n *graphNodeTaintedResource) ProvidedBy() []string {
func (n *graphNodeTaintedResource) EvalTree() EvalNode { func (n *graphNodeTaintedResource) EvalTree() EvalNode {
var provider ResourceProvider var provider ResourceProvider
var state *InstanceState var state *InstanceState
tainted := true
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
@ -105,11 +87,10 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
Name: n.ProvidedBy()[0], Name: n.ProvidedBy()[0],
Output: &provider, Output: &provider,
}, },
&EvalReadState{ &EvalReadStateTainted{
Name: n.ResourceName, Name: n.ResourceName,
Tainted: true, Index: n.Index,
TaintedIndex: n.Index, Output: &state,
Output: &state,
}, },
&EvalRefresh{ &EvalRefresh{
Info: info, Info: info,
@ -117,12 +98,11 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
State: &state, State: &state,
Output: &state, Output: &state,
}, },
&EvalWriteState{ &EvalWriteStateTainted{
Name: n.ResourceName, Name: n.ResourceName,
ResourceType: n.ResourceType, ResourceType: n.ResourceType,
State: &state, State: &state,
Tainted: &tainted, Index: n.Index,
TaintedIndex: n.Index,
}, },
}, },
}, },
@ -138,11 +118,10 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
Name: n.ProvidedBy()[0], Name: n.ProvidedBy()[0],
Output: &provider, Output: &provider,
}, },
&EvalReadState{ &EvalReadStateTainted{
Name: n.ResourceName, Name: n.ResourceName,
Tainted: true, Index: n.Index,
TaintedIndex: n.Index, Output: &state,
Output: &state,
}, },
&EvalDiffDestroy{ &EvalDiffDestroy{
Info: info, Info: info,
@ -156,12 +135,11 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
Provider: &provider, Provider: &provider,
Output: &state, Output: &state,
}, },
&EvalWriteState{ &EvalWriteStateTainted{
Name: n.ResourceName, Name: n.ResourceName,
ResourceType: n.ResourceType, ResourceType: n.ResourceType,
State: &state, State: &state,
Tainted: &tainted, Index: n.Index,
TaintedIndex: n.Index,
}, },
&EvalUpdateStateHook{}, &EvalUpdateStateHook{},
}, },

View File

@ -1,3 +1,3 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'middleman-hashicorp', github: 'hashicorp/middleman-hashicorp' gem 'middleman-hashicorp', git: 'https://github.com/hashicorp/middleman-hashicorp'

View File

@ -1,19 +1,19 @@
GIT GIT
remote: git://github.com/hashicorp/middleman-hashicorp.git remote: https://github.com/hashicorp/middleman-hashicorp
revision: 30c15f93fb501041cff97c490b60ddc96c8314c9 revision: 783fe9517dd02badb85e5ddfeda4d8e35bbd05a8
specs: specs:
middleman-hashicorp (0.1.0) middleman-hashicorp (0.1.0)
bootstrap-sass (~> 3.2) bootstrap-sass (~> 3.3)
builder (~> 3.2) builder (~> 3.2)
less (~> 2.6) less (~> 2.6)
middleman (~> 3.3) middleman (~> 3.3)
middleman-livereload (~> 3.3) middleman-livereload (~> 3.4)
middleman-minify-html (~> 3.4) middleman-minify-html (~> 3.4)
middleman-syntax (~> 2.0) middleman-syntax (~> 2.0)
rack-contrib (~> 1.1) rack-contrib (~> 1.2)
rack-rewrite (~> 1.5) rack-rewrite (~> 1.5)
rack-ssl-enforcer (~> 0.2) rack-ssl-enforcer (~> 0.2)
redcarpet (~> 3.1) redcarpet (~> 3.2)
therubyracer (~> 0.12) therubyracer (~> 0.12)
thin (~> 1.6) thin (~> 1.6)
@ -26,7 +26,7 @@ GEM
minitest (~> 5.1) minitest (~> 5.1)
thread_safe (~> 0.1) thread_safe (~> 0.1)
tzinfo (~> 1.1) tzinfo (~> 1.1)
autoprefixer-rails (5.0.0.2) autoprefixer-rails (5.1.7)
execjs execjs
json json
bootstrap-sass (3.3.3) bootstrap-sass (3.3.3)
@ -35,11 +35,11 @@ GEM
builder (3.2.2) builder (3.2.2)
celluloid (0.16.0) celluloid (0.16.0)
timers (~> 4.0.0) timers (~> 4.0.0)
chunky_png (1.3.3) chunky_png (1.3.4)
coffee-script (2.3.0) coffee-script (2.3.0)
coffee-script-source coffee-script-source
execjs execjs
coffee-script-source (1.8.0) coffee-script-source (1.9.1)
commonjs (0.2.7) commonjs (0.2.7)
compass (1.0.3) compass (1.0.3)
chunky_png (~> 1.2) chunky_png (~> 1.2)
@ -58,8 +58,8 @@ GEM
eventmachine (>= 0.12.9) eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0) http_parser.rb (~> 0.6.0)
erubis (2.7.0) erubis (2.7.0)
eventmachine (1.0.4) eventmachine (1.0.7)
execjs (2.2.2) execjs (2.4.0)
ffi (1.9.6) ffi (1.9.6)
haml (4.0.6) haml (4.0.6)
tilt tilt
@ -69,9 +69,9 @@ GEM
uber (~> 0.0.4) uber (~> 0.0.4)
htmlcompressor (0.1.2) htmlcompressor (0.1.2)
http_parser.rb (0.6.0) http_parser.rb (0.6.0)
i18n (0.6.11) i18n (0.7.0)
json (1.8.2) json (1.8.2)
kramdown (1.5.0) kramdown (1.6.0)
less (2.6.0) less (2.6.0)
commonjs (~> 0.2.7) commonjs (~> 0.2.7)
libv8 (3.16.14.7) libv8 (3.16.14.7)
@ -79,23 +79,23 @@ GEM
celluloid (>= 0.15.2) celluloid (>= 0.15.2)
rb-fsevent (>= 0.9.3) rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9) rb-inotify (>= 0.9)
middleman (3.3.7) middleman (3.3.10)
coffee-script (~> 2.2) coffee-script (~> 2.2)
compass (>= 1.0.0, < 2.0.0) compass (>= 1.0.0, < 2.0.0)
compass-import-once (= 1.0.5) compass-import-once (= 1.0.5)
execjs (~> 2.0) execjs (~> 2.0)
haml (>= 4.0.5) haml (>= 4.0.5)
kramdown (~> 1.2) kramdown (~> 1.2)
middleman-core (= 3.3.7) middleman-core (= 3.3.10)
middleman-sprockets (>= 3.1.2) middleman-sprockets (>= 3.1.2)
sass (>= 3.4.0, < 4.0) sass (>= 3.4.0, < 4.0)
uglifier (~> 2.5) uglifier (~> 2.5)
middleman-core (3.3.7) middleman-core (3.3.10)
activesupport (~> 4.1.0) activesupport (~> 4.1.0)
bundler (~> 1.1) bundler (~> 1.1)
erubis erubis
hooks (~> 0.3) hooks (~> 0.3)
i18n (~> 0.6.9) i18n (~> 0.7.0)
listen (>= 2.7.9, < 3.0) listen (>= 2.7.9, < 3.0)
padrino-helpers (~> 0.12.3) padrino-helpers (~> 0.12.3)
rack (>= 1.4.5, < 2.0) rack (>= 1.4.5, < 2.0)
@ -109,7 +109,7 @@ GEM
middleman-minify-html (3.4.0) middleman-minify-html (3.4.0)
htmlcompressor (~> 0.1.0) htmlcompressor (~> 0.1.0)
middleman-core (>= 3.2) middleman-core (>= 3.2)
middleman-sprockets (3.4.1) middleman-sprockets (3.4.2)
middleman-core (>= 3.3) middleman-core (>= 3.3)
sprockets (~> 2.12.1) sprockets (~> 2.12.1)
sprockets-helpers (~> 1.1.0) sprockets-helpers (~> 1.1.0)
@ -118,12 +118,12 @@ GEM
middleman-core (~> 3.2) middleman-core (~> 3.2)
rouge (~> 1.0) rouge (~> 1.0)
minitest (5.5.1) minitest (5.5.1)
multi_json (1.10.1) multi_json (1.11.0)
padrino-helpers (0.12.4) padrino-helpers (0.12.5)
i18n (~> 0.6, >= 0.6.7) i18n (~> 0.6, >= 0.6.7)
padrino-support (= 0.12.4) padrino-support (= 0.12.5)
tilt (~> 1.4.1) tilt (~> 1.4.1)
padrino-support (0.12.4) padrino-support (0.12.5)
activesupport (>= 3.1) activesupport (>= 3.1)
rack (1.6.0) rack (1.6.0)
rack-contrib (1.2.0) rack-contrib (1.2.0)
@ -139,8 +139,8 @@ GEM
ffi (>= 0.5.0) ffi (>= 0.5.0)
redcarpet (3.2.2) redcarpet (3.2.2)
ref (1.0.5) ref (1.0.5)
rouge (1.7.7) rouge (1.8.0)
sass (3.4.10) sass (3.4.13)
sprockets (2.12.3) sprockets (2.12.3)
hike (~> 1.2) hike (~> 1.2)
multi_json (~> 1.0) multi_json (~> 1.0)
@ -166,7 +166,7 @@ GEM
tzinfo (1.2.2) tzinfo (1.2.2)
thread_safe (~> 0.1) thread_safe (~> 0.1)
uber (0.0.13) uber (0.0.13)
uglifier (2.7.0) uglifier (2.7.1)
execjs (>= 0.3.0) execjs (>= 0.3.0)
json (>= 1.8.0) json (>= 1.8.0)

View File

@ -31,11 +31,10 @@ Available commands are:
init Initializes Terraform configuration from a module init Initializes Terraform configuration from a module
output Read an output from a state file output Read an output from a state file
plan Generate and show an execution plan plan Generate and show an execution plan
pull Refreshes the local state copy from the remote server
push Uploads the the local state to the remote server
refresh Update local state file against real resources refresh Update local state file against real resources
remote Configures remote state management remote Configure remote state storage
show Inspect Terraform state or plan show Inspect Terraform state or plan
taint Manually mark a resource for recreation
version Prints the Terraform version version Prints the Terraform version
``` ```

View File

@ -1,23 +0,0 @@
---
layout: "docs"
page_title: "Command: pull"
sidebar_current: "docs-commands-pull"
description: |-
The `terraform pull` refreshes the cached state file from the
remote server when remote state storage is enabled.
---
# Command: pull
The `terraform pull` refreshes the cached state file from the
remote server when remote state storage is enabled. The [`remote`
command](/docs/commands/remote.html) should be used to enable
remote state storage.
## Usage
Usage: `terraform pull`
The `pull` command is invoked without options to refresh the
cache copy of the state.

View File

@ -1,27 +0,0 @@
---
layout: "docs"
page_title: "Command: push"
sidebar_current: "docs-commands-push"
description: |-
The `terraform push` command is used to push a cached local copy
of the state to a remote storage server.
---
# Command: push
The `terraform push` uploads the cached state file to the
remote server when remote state storage is enabled. The [`remote`
command](/docs/commands/remote.html) should be used to enable
remote state storage.
Uploading is typically done automatically when running a Terraform
command that modifies state, but this can be used to retry uploads
if a transient failure occurs.
## Usage
Usage: `terraform push`
The `push` command is invoked without options to upload the
local cached state to the remote storage server.

View File

@ -0,0 +1,81 @@
---
layout: "docs"
page_title: "Command: remote config"
sidebar_current: "docs-commands-remote-config"
description: |-
The `terraform remote config` command is used to configure Terraform to make
use of remote state storage, change remote storage configuration, or
to disable it.
---
# Command: remote config
The `terraform remote config` command is used to configure use of remote
state storage. By default, Terraform persists its state only to a local
disk. When remote state storage is enabled, Terraform will automatically
fetch the latest state from the remote server when necessary and if any
updates are made, the newest state is persisted back to the remote server.
In this mode, users do not need to durably store the state using version
control or shared storaged.
## Usage
Usage: `terraform remote config [options]`
The `remote config` command can be used to enable remote storage, change
configuration or disable the use of remote storage. Terraform supports multiple types
of storage backends, specified by using the `-backend` flag. By default,
Atlas is assumed to be the storage backend. Each backend expects different,
configuration arguments documented below.
When remote storage is enabled, an existing local state file can be migrated.
By default, `remote config` will look for the "terraform.tfstate" file, but that
can be specified by the `-state` flag. If no state file exists, a blank
state will be configured.
When remote storage is disabled, the existing remote state is migrated
to a local file. This defaults to the `-state` path during restore.
The following backends are supported:
* Atlas - Stores the state in Atlas. Requires the `-name` and `-access-token` flag.
The `-address` flag can optionally be provided.
* Consul - Stores the state in the KV store at a given path.
Requires the `path` flag. The `-address` and `-access-token`
flag can optionally be provided. Address is assumed to be the
local agent if not provided.
* HTTP - Stores the state using a simple REST client. State will be fetched
via GET, updated via POST, and purged with DELETE. Requires the `-address` flag.
The command-line flags are all optional. The list of available flags are:
* `-address=url` - URL of the remote storage server. Required for HTTP backend,
optional for Atlas and Consul.
* `-access-token=token` - Authentication token for state storage server.
Required for Atlas backend, optional for Consul.
* `-backend=Atlas` - Specifies the type of remote backend. Must be one
of Atlas, Consul, or HTTP. Defaults to Atlas.
* `-backup=path` - Path to backup the existing state file before
modifying. Defaults to the "-state" path with ".backup" extension.
Set to "-" to disable backup.
* `-disable` - Disables remote state management and migrates the state
to the `-state` path.
* `-name=name` - Name of the state file in the state storage server.
Required for Atlas backend.
* `-path=path` - Path of the remote state in Consul. Required for the
Consul backend.
* `-pull=true` - Controls if the remote state is pulled before disabling.
This defaults to true to ensure the latest state is cached before disabling.
* `-state=path` - Path to read state. Defaults to "terraform.tfstate"
unless remote state is enabled.

View File

@ -0,0 +1,23 @@
---
layout: "docs"
page_title: "Command: remote pull"
sidebar_current: "docs-commands-remote-pull"
description: |-
The `terraform remote pull` refreshes the cached state file from the
remote server when remote state storage is enabled.
---
# Command: remote pull
The `terraform remote pull` refreshes the cached state file from the
remote server when remote state storage is enabled. The [`remote config`
command](/docs/commands/remote-config.html) should be used to enable
remote state storage.
## Usage
Usage: `terraform remote pull`
The `remote pull` command is invoked without options to refresh the
cache copy of the state.

View File

@ -0,0 +1,27 @@
---
layout: "docs"
page_title: "Command: remote push"
sidebar_current: "docs-commands-remote-push"
description: |-
The `terraform remote push` command is used to push a cached local copy
of the state to a remote storage server.
---
# Command: remote push
The `terraform remote push` uploads the cached state file to the
remote server when remote state storage is enabled. The [`remote config`
command](/docs/commands/remote-config.html) should be used to enable
remote state storage.
Uploading is typically done automatically when running a Terraform
command that modifies state, but this can be used to retry uploads
if a transient failure occurs.
## Usage
Usage: `terraform remote push`
The `remote push` command is invoked without options to upload the
local cached state to the remote storage server.

Some files were not shown because too many files have changed in this diff Show More