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:
- go test ./...
- make vet
#- go test -race ./...
branches:

View File

@ -1,5 +1,12 @@
## 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:
* **New provider: `dme` (DNSMadeEasy)** [GH-855]
@ -16,11 +23,14 @@ FEATURES:
IMPROVEMENTS:
* **New config function: `format`** - Format a string using `sprintf`
format. [GH-1096]
* **New config function: `replace`** - Search and replace string values.
Search can be a regular expression. See documentation for more
info. [GH-1029]
* **New config function: `split`** - Split a value based on a delimiter.
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
change. This will lower the amount of state changing on things
like refresh.
@ -39,9 +49,16 @@ BUG FIXES:
"resource.0" would ignore the latter completely. [GH-1086]
* providers/aws: manually deleted VPC removes it from the state
* 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: 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)

View File

@ -12,6 +12,9 @@ bin: generate
dev: generate
@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: generate
TF_ACC= go test $(TEST) $(TESTARGS) -timeout=30s -parallel=4

View File

@ -2,11 +2,14 @@ package aws
import (
"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) {
entries := make([]ec2.NetworkAclEntry, 0, len(configured))
func expandNetworkAclEntries(configured []interface{}, entryType string) ([]ec2.NetworkACLEntry, error) {
entries := make([]ec2.NetworkACLEntry, 0, len(configured))
for _, eRaw := range configured {
data := eRaw.(map[string]interface{})
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)
}
p := extractProtocolInteger(data["protocol"].(string))
e := ec2.NetworkAclEntry{
Protocol: p,
PortRange: ec2.PortRange{
From: data["from_port"].(int),
To: data["to_port"].(int),
e := ec2.NetworkACLEntry{
Protocol: aws.String(strconv.Itoa(p)),
PortRange: &ec2.PortRange{
From: aws.Integer(data["from_port"].(int)),
To: aws.Integer(data["to_port"].(int)),
},
Egress: (entryType == "egress"),
RuleAction: data["action"].(string),
RuleNumber: data["rule_no"].(int),
CidrBlock: data["cidr_block"].(string),
Egress: aws.Boolean((entryType == "egress")),
RuleAction: aws.String(data["action"].(string)),
RuleNumber: aws.Integer(data["rule_no"].(int)),
CIDRBlock: aws.String(data["cidr_block"].(string)),
}
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))
for _, entry := range list {
entries = append(entries, map[string]interface{}{
"from_port": entry.PortRange.From,
"to_port": entry.PortRange.To,
"action": entry.RuleAction,
"rule_no": entry.RuleNumber,
"protocol": extractProtocolString(entry.Protocol),
"cidr_block": entry.CidrBlock,
"from_port": *entry.PortRange.From,
"to_port": *entry.PortRange.To,
"action": *entry.RuleAction,
"rule_no": *entry.RuleNumber,
"protocol": *entry.Protocol,
"cidr_block": *entry.CIDRBlock,
})
}
return entries

View File

@ -4,10 +4,11 @@ import (
"reflect"
"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{}{
map[string]interface{}{
"protocol": "tcp",
@ -28,30 +29,28 @@ func Test_expandNetworkAclEntry(t *testing.T) {
}
expanded, _ := expandNetworkAclEntries(input, "egress")
expected := []ec2.NetworkAclEntry{
ec2.NetworkAclEntry{
Protocol: 6,
PortRange: ec2.PortRange{
From: 22,
To: 22,
expected := []ec2.NetworkACLEntry{
ec2.NetworkACLEntry{
Protocol: aws.String("6"),
PortRange: &ec2.PortRange{
From: aws.Integer(22),
To: aws.Integer(22),
},
RuleAction: "deny",
RuleNumber: 1,
CidrBlock: "0.0.0.0/0",
Egress: true,
IcmpCode: ec2.IcmpCode{Code: 0, Type: 0},
RuleAction: aws.String("deny"),
RuleNumber: aws.Integer(1),
CIDRBlock: aws.String("0.0.0.0/0"),
Egress: aws.Boolean(true),
},
ec2.NetworkAclEntry{
Protocol: 6,
PortRange: ec2.PortRange{
From: 443,
To: 443,
ec2.NetworkACLEntry{
Protocol: aws.String("6"),
PortRange: &ec2.PortRange{
From: aws.Integer(443),
To: aws.Integer(443),
},
RuleAction: "deny",
RuleNumber: 2,
CidrBlock: "0.0.0.0/0",
Egress: true,
IcmpCode: ec2.IcmpCode{Code: 0, Type: 0},
RuleAction: aws.String("deny"),
RuleNumber: aws.Integer(2),
CIDRBlock: aws.String("0.0.0.0/0"),
Egress: aws.Boolean(true),
},
}
@ -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{
ec2.NetworkAclEntry{
Protocol: 6,
PortRange: ec2.PortRange{
From: 22,
To: 22,
apiInput := []ec2.NetworkACLEntry{
ec2.NetworkACLEntry{
Protocol: aws.String("tcp"),
PortRange: &ec2.PortRange{
From: aws.Integer(22),
To: aws.Integer(22),
},
RuleAction: "deny",
RuleNumber: 1,
CidrBlock: "0.0.0.0/0",
RuleAction: aws.String("deny"),
RuleNumber: aws.Integer(1),
CIDRBlock: aws.String("0.0.0.0/0"),
},
ec2.NetworkAclEntry{
Protocol: 6,
PortRange: ec2.PortRange{
From: 443,
To: 443,
ec2.NetworkACLEntry{
Protocol: aws.String("tcp"),
PortRange: &ec2.PortRange{
From: aws.Integer(443),
To: aws.Integer(443),
},
RuleAction: "deny",
RuleNumber: 2,
CidrBlock: "0.0.0.0/0",
RuleAction: aws.String("deny"),
RuleNumber: aws.Integer(2),
CIDRBlock: aws.String("0.0.0.0/0"),
},
}
flattened := flattenNetworkAclEntries(apiInput)

View File

@ -156,7 +156,6 @@ func resourceAwsDbInstance() *schema.Resource {
"final_snapshot_identifier": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"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("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("status", *v.DBInstanceStatus)
d.Set("storage_encrypted", *v.StorageEncrypted)

View File

@ -6,9 +6,10 @@ import (
"strings"
"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/schema"
"github.com/mitchellh/goamz/ec2"
)
func resourceAwsEip() *schema.Resource {
@ -59,7 +60,7 @@ func resourceAwsEip() *schema.Resource {
}
func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
// By default, we're not in a VPC
domainOpt := ""
@ -67,12 +68,12 @@ func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error {
domainOpt = "vpc"
}
allocOpts := ec2.AllocateAddress{
Domain: domainOpt,
allocOpts := &ec2.AllocateAddressRequest{
Domain: aws.String(domainOpt),
}
log.Printf("[DEBUG] EIP create configuration: %#v", allocOpts)
allocResp, err := ec2conn.AllocateAddress(&allocOpts)
allocResp, err := ec2conn.AllocateAddress(allocOpts)
if err != nil {
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
log.Printf("[DEBUG] EIP Allocate: %#v", allocResp)
if d.Get("domain").(string) == "vpc" {
d.SetId(allocResp.AllocationId)
d.SetId(*allocResp.AllocationID)
} 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)
}
func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
domain := resourceAwsEipDomain(d)
id := d.Id()
@ -113,9 +114,13 @@ func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
"[DEBUG] EIP describe configuration: %#v, %#v (domain: %s)",
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 ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAllocationID.NotFound" {
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidAllocationID.NotFound" {
d.SetId("")
return nil
}
@ -125,8 +130,8 @@ func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
// Verify AWS returned our EIP
if len(describeAddresses.Addresses) != 1 ||
describeAddresses.Addresses[0].AllocationId != id ||
describeAddresses.Addresses[0].PublicIp != id {
*describeAddresses.Addresses[0].AllocationID != id ||
*describeAddresses.Addresses[0].PublicIP != id {
if err != nil {
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]
d.Set("association_id", address.AssociationId)
d.Set("instance", address.InstanceId)
d.Set("public_ip", address.PublicIp)
d.Set("private_ip", address.PrivateIpAddress)
d.Set("association_id", address.AssociationID)
d.Set("instance", address.InstanceID)
d.Set("private_ip", address.PrivateIPAddress)
d.Set("public_ip", address.PublicIP)
return nil
}
func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
domain := resourceAwsEipDomain(d)
@ -151,22 +156,22 @@ func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
if v, ok := d.GetOk("instance"); ok {
instanceId := v.(string)
assocOpts := ec2.AssociateAddress{
InstanceId: instanceId,
PublicIp: d.Id(),
assocOpts := &ec2.AssociateAddressRequest{
InstanceID: aws.String(instanceId),
PublicIP: aws.String(d.Id()),
}
// more unique ID conditionals
if domain == "vpc" {
assocOpts = ec2.AssociateAddress{
InstanceId: instanceId,
AllocationId: d.Id(),
PublicIp: "",
assocOpts = &ec2.AssociateAddressRequest{
InstanceID: aws.String(instanceId),
AllocationID: aws.String(d.Id()),
PublicIP: aws.String(""),
}
}
log.Printf("[DEBUG] EIP associate configuration: %#v (domain: %v)", assocOpts, domain)
_, err := ec2conn.AssociateAddress(&assocOpts)
_, err := ec2conn.AssociateAddress(assocOpts)
if err != nil {
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 {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
if err := resourceAwsEipRead(d, meta); err != nil {
return err
@ -192,9 +197,13 @@ func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error {
var err error
switch resourceAwsEipDomain(d) {
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":
_, err = ec2conn.DisassociateAddressClassic(d.Get("public_ip").(string))
err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressRequest{
PublicIP: aws.String(d.Get("public_ip").(string)),
})
}
if err != nil {
return err
@ -209,16 +218,20 @@ func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error {
log.Printf(
"[DEBUG] EIP release (destroy) address allocation: %v",
d.Id())
_, err = ec2conn.ReleaseAddress(d.Id())
err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressRequest{
AllocationID: aws.String(d.Id()),
})
case "standard":
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 {
return nil
}
if _, ok := err.(*ec2.Error); !ok {
if _, ok := err.(aws.APIError); !ok {
return resource.RetryError{Err: err}
}

View File

@ -5,9 +5,10 @@ import (
"strings"
"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/terraform"
"github.com/mitchellh/goamz/ec2"
)
func TestAccAWSEIP_normal(t *testing.T) {
@ -57,24 +58,28 @@ func TestAccAWSEIP_instance(t *testing.T) {
}
func testAccCheckAWSEIPDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_eip" {
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 len(describe.Addresses) != 0 &&
describe.Addresses[0].PublicIp == rs.Primary.ID {
*describe.Addresses[0].PublicIP == rs.Primary.ID {
return fmt.Errorf("EIP still exists")
}
}
// Verify the error
providerErr, ok := err.(*ec2.Error)
providerErr, ok := err.(aws.APIError)
if !ok {
return err
}
@ -89,7 +94,7 @@ func testAccCheckAWSEIPDestroy(s *terraform.State) error {
func testAccCheckAWSEIPAttributes(conf *ec2.Address) resource.TestCheckFunc {
return func(s *terraform.State) error {
if conf.PublicIp == "" {
if *conf.PublicIP == "" {
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")
}
conn := testAccProvider.Meta().(*AWSClient).ec2conn
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
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 {
return err
}
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")
}
*res = describe.Addresses[0]
} 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 {
return err
}
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")
}
*res = describe.Addresses[0]

View File

@ -6,14 +6,14 @@ import (
"encoding/hex"
"fmt"
"log"
"strconv"
"strings"
"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/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
)
func resourceAwsInstance() *schema.Resource {
@ -186,6 +186,13 @@ func resourceAwsInstance() *schema.Resource {
Computed: true,
ForceNew: true,
},
"iops": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
},
},
},
Set: resourceAwsInstanceBlockDevicesHash,
@ -231,6 +238,13 @@ func resourceAwsInstance() *schema.Resource {
Computed: 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 {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
// Figure out user data
userData := ""
@ -247,38 +261,84 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
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
if v := d.Get("associate_public_ip_address"); v != nil {
associatePublicIPAddress = v.(bool)
}
// Build the creation struct
runOpts := &ec2.RunInstances{
ImageId: d.Get("ami").(string),
AvailZone: d.Get("availability_zone").(string),
InstanceType: d.Get("instance_type").(string),
KeyName: d.Get("key_name").(string),
SubnetId: d.Get("subnet_id").(string),
PrivateIPAddress: d.Get("private_ip").(string),
AssociatePublicIpAddress: associatePublicIPAddress,
UserData: []byte(userData),
EbsOptimized: d.Get("ebs_optimized").(bool),
IamInstanceProfile: d.Get("iam_instance_profile").(string),
Tenancy: d.Get("tenancy").(string),
// check for non-default Subnet, and cast it to a String
var hasSubnet bool
subnet, hasSubnet := d.GetOk("subnet_id")
subnetID := subnet.(string)
if hasSubnet && associatePublicIPAddress {
// If we have a non-default VPC / Subnet specified, we can flag
// AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided.
// You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise
// you get: Network interfaces and an instance-level subnet ID may not be specified on the same request
// You also need to attach Security Groups to the NetworkInterface instead of the instance,
// to avoid: Network interfaces and an instance-level security groups may not be specified on
// 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 {
// 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() {
str := v.(string)
var g ec2.SecurityGroup
if runOpts.SubnetId != "" {
g.Id = str
} else {
g.Name = str
groups = append(groups, str)
}
runOpts.SecurityGroups = append(runOpts.SecurityGroups, g)
if runOpts.SubnetID != nil &&
*runOpts.SubnetID != "" {
runOpts.SecurityGroupIDs = groups
} else {
runOpts.SecurityGroups = groups
}
}
@ -297,21 +357,27 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
}
if len(blockDevices) > 0 {
runOpts.BlockDevices = make([]ec2.BlockDeviceMapping, len(blockDevices))
runOpts.BlockDeviceMappings = make([]ec2.BlockDeviceMapping, len(blockDevices))
for i, v := range blockDevices {
bd := v.(map[string]interface{})
runOpts.BlockDevices[i].DeviceName = bd["device_name"].(string)
runOpts.BlockDevices[i].VolumeType = bd["volume_type"].(string)
runOpts.BlockDevices[i].VolumeSize = int64(bd["volume_size"].(int))
runOpts.BlockDevices[i].DeleteOnTermination = bd["delete_on_termination"].(bool)
if v, ok := bd["virtual_name"].(string); ok {
runOpts.BlockDevices[i].VirtualName = v
runOpts.BlockDeviceMappings[i].DeviceName = aws.String(bd["device_name"].(string))
runOpts.BlockDeviceMappings[i].EBS = &ec2.EBSBlockDevice{
VolumeType: aws.String(bd["volume_type"].(string)),
VolumeSize: aws.Integer(bd["volume_size"].(int)),
DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
}
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 {
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]
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
d.SetId(instance.InstanceId)
d.SetId(*instance.InstanceID)
// Wait for the instance to become running so we can get some attributes
// that aren't available until later.
log.Printf(
"[DEBUG] Waiting for instance (%s) to become running",
instance.InstanceId)
*instance.InstanceID)
stateConf := &resource.StateChangeConf{
Pending: []string{"pending"},
Target: "running",
Refresh: InstanceStateRefreshFunc(ec2conn, instance.InstanceId),
Refresh: InstanceStateRefreshFunc(ec2conn, *instance.InstanceID),
Timeout: 10 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
@ -348,16 +414,18 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
if err != nil {
return fmt.Errorf(
"Error waiting for instance (%s) to become ready: %s",
instance.InstanceId, err)
*instance.InstanceID, err)
}
instance = instanceRaw.(*ec2.Instance)
// Initialize the connection info
if instance.PublicIPAddress != nil {
d.SetConnInfo(map[string]string{
"type": "ssh",
"host": instance.PublicIpAddress,
"host": *instance.PublicIPAddress,
})
}
// Set our attributes
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 {
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 the instance was not found, return nil so that we can show
// 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("")
return nil
}
@ -393,28 +463,33 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
instance := &resp.Reservations[0].Instances[0]
// If the instance is terminated, then it is gone
if instance.State.Name == "terminated" {
if *instance.State.Name == "terminated" {
d.SetId("")
return nil
}
d.Set("availability_zone", instance.AvailZone)
d.Set("availability_zone", instance.Placement.AvailabilityZone)
d.Set("key_name", instance.KeyName)
d.Set("public_dns", instance.DNSName)
d.Set("public_ip", instance.PublicIpAddress)
d.Set("public_dns", instance.PublicDNSName)
d.Set("public_ip", instance.PublicIPAddress)
d.Set("private_dns", instance.PrivateDNSName)
d.Set("private_ip", instance.PrivateIpAddress)
d.Set("subnet_id", instance.SubnetId)
d.Set("ebs_optimized", instance.EbsOptimized)
d.Set("tags", tagsToMap(instance.Tags))
d.Set("tenancy", instance.Tenancy)
d.Set("private_ip", instance.PrivateIPAddress)
d.Set("subnet_id", instance.SubnetID)
if len(instance.NetworkInterfaces) > 0 {
d.Set("subnet_id", instance.NetworkInterfaces[0].SubnetID)
} 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
// 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
// all-name list of security groups, we use names. Or, if we had any
// IDs, we use IDs.
useID := instance.SubnetId != ""
useID := *instance.SubnetID != ""
if v := d.Get("security_groups"); v != nil {
match := false
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))
for i, sg := range instance.SecurityGroups {
if useID {
sgs[i] = sg.Id
sgs[i] = *sg.GroupID
} else {
sgs[i] = sg.Name
sgs[i] = *sg.GroupName
}
}
d.Set("security_groups", sgs)
blockDevices := make(map[string]ec2.BlockDevice)
for _, bd := range instance.BlockDevices {
blockDevices[bd.VolumeId] = bd
blockDevices := make(map[string]ec2.InstanceBlockDeviceMapping)
for _, bd := range instance.BlockDeviceMappings {
blockDevices[*bd.EBS.VolumeID] = bd
}
volIDs := make([]string, 0, len(blockDevices))
for volID := range blockDevices {
volIDs = append(volIDs, volID)
for _, vol := range blockDevices {
volIDs = append(volIDs, *vol.EBS.VolumeID)
}
volResp, err := ec2conn.Volumes(volIDs, ec2.NewFilter())
volResp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesRequest{
VolumeIDs: volIDs,
})
if err != nil {
return err
}
@ -456,27 +533,25 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
nonRootBlockDevices := make([]map[string]interface{}, 0)
rootBlockDevice := make([]interface{}, 0, 1)
for _, vol := range volResp.Volumes {
volSize, err := strconv.Atoi(vol.Size)
if err != nil {
return err
}
blockDevice := make(map[string]interface{})
blockDevice["device_name"] = blockDevices[vol.VolumeId].DeviceName
blockDevice["volume_type"] = vol.VolumeType
blockDevice["volume_size"] = volSize
blockDevice["device_name"] = *blockDevices[*vol.VolumeID].DeviceName
blockDevice["volume_type"] = *vol.VolumeType
blockDevice["volume_size"] = *vol.Size
if vol.IOPS != nil {
blockDevice["iops"] = *vol.IOPS
}
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
// can't put invalid keys into this map.
if blockDevice["device_name"] == instance.RootDeviceName {
if blockDevice["device_name"] == *instance.RootDeviceName {
rootBlockDevice = []interface{}{blockDevice}
continue
}
blockDevice["snapshot_id"] = vol.SnapshotId
blockDevice["encrypted"] = vol.Encrypted
blockDevice["snapshot_id"] = *vol.SnapshotID
blockDevice["encrypted"] = *vol.Encrypted
nonRootBlockDevices = append(nonRootBlockDevices, blockDevice)
}
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 {
ec2conn := meta.(*AWSClient).ec2conn
opts := new(ec2.ModifyInstance)
opts.SetSourceDestCheck = true
opts.SourceDestCheck = d.Get("source_dest_check").(bool)
ec2conn := meta.(*AWSClient).awsEC2conn
opts := new(ec2.ModifyInstanceAttributeRequest)
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
}
// TODO(mitchellh): wait for the attributes we modified to
// persist the change...
if err := setTags(ec2conn, d); err != nil {
if err := setTagsSDK(ec2conn, d); err != nil {
return err
} else {
d.SetPartial("tags")
@ -510,10 +589,13 @@ func resourceAwsInstanceUpdate(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())
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)
}
@ -545,9 +627,11 @@ func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error {
// an EC2 instance.
func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc {
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 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.
resp = nil
} else {
@ -563,7 +647,7 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRe
}
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"
"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/schema"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/goamz/ec2"
)
func TestAccAWSInstance_normal(t *testing.T) {
var v ec2.Instance
testCheck := func(*terraform.State) error {
if v.AvailZone != "us-west-2a" {
return fmt.Errorf("bad availability zone: %#v", v.AvailZone)
if *v.Placement.AvailabilityZone != "us-west-2a" {
return fmt.Errorf("bad availability zone: %#v", *v.Placement.AvailabilityZone)
}
if len(v.SecurityGroups) == 0 {
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)
}
@ -73,9 +74,9 @@ func TestAccAWSInstance_blockDevices(t *testing.T) {
return func(*terraform.State) error {
// Map out the block devices by name, which should be unique.
blockDevices := make(map[string]ec2.BlockDevice)
for _, blockDevice := range v.BlockDevices {
blockDevices[blockDevice.DeviceName] = blockDevice
blockDevices := make(map[string]ec2.InstanceBlockDeviceMapping)
for _, blockDevice := range v.BlockDeviceMappings {
blockDevices[*blockDevice.DeviceName] = blockDevice
}
// 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")
}
// Check if the third block device exists.
if _, ok := blockDevices["/dev/sdc"]; !ok {
fmt.Errorf("block device doesn't exist: /dev/sdc")
}
return nil
}
}
@ -114,11 +120,22 @@ func TestAccAWSInstance_blockDevices(t *testing.T) {
resource.TestCheckResourceAttr(
"aws_instance.foo", "root_block_device.0.volume_type", "gp2"),
resource.TestCheckResourceAttr(
"aws_instance.foo", "block_device.#", "1"),
"aws_instance.foo", "block_device.#", "2"),
resource.TestCheckResourceAttr(
"aws_instance.foo", "block_device.172787947.device_name", "/dev/sdb"),
resource.TestCheckResourceAttr(
"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(),
),
},
@ -131,8 +148,8 @@ func TestAccAWSInstance_sourceDestCheck(t *testing.T) {
testCheck := func(enabled bool) resource.TestCheckFunc {
return func(*terraform.State) error {
if v.SourceDestCheck != enabled {
return fmt.Errorf("bad source_dest_check: %#v", v.SourceDestCheck)
if *v.SourceDestCheck != enabled {
return fmt.Errorf("bad source_dest_check: %#v", *v.SourceDestCheck)
}
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
resource.Test(t, resource.TestCase{
@ -202,9 +219,9 @@ func TestAccInstance_tags(t *testing.T) {
Config: testAccCheckInstanceConfigTags,
Check: resource.ComposeTestCheckFunc(
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
testAccCheckTags(&v.Tags, "#", ""),
testAccCheckTagsSDK(&v.Tags, "#", ""),
),
},
@ -212,21 +229,21 @@ func TestAccInstance_tags(t *testing.T) {
Config: testAccCheckInstanceConfigTagsUpdate,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
testAccCheckTags(&v.Tags, "foo", ""),
testAccCheckTags(&v.Tags, "bar", "baz"),
testAccCheckTagsSDK(&v.Tags, "foo", ""),
testAccCheckTagsSDK(&v.Tags, "bar", "baz"),
),
},
},
})
}
func TestAccInstance_privateIP(t *testing.T) {
func TestAccAWSInstance_privateIP(t *testing.T) {
var v ec2.Instance
testCheckPrivateIP := func() resource.TestCheckFunc {
return func(*terraform.State) error {
if v.PrivateIpAddress != "10.1.1.42" {
return fmt.Errorf("bad private IP: %s", v.PrivateIpAddress)
if *v.PrivateIPAddress != "10.1.1.42" {
return fmt.Errorf("bad private IP: %s", *v.PrivateIPAddress)
}
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
testCheckPrivateIP := func() resource.TestCheckFunc {
return func(*terraform.State) error {
if v.PrivateIpAddress != "10.1.1.42" {
return fmt.Errorf("bad private IP: %s", v.PrivateIpAddress)
if *v.PrivateIPAddress != "10.1.1.42" {
return fmt.Errorf("bad private IP: %s", *v.PrivateIPAddress)
}
return nil
@ -279,7 +296,7 @@ func TestAccInstance_associatePublicIPAndPrivateIP(t *testing.T) {
}
func testAccCheckInstanceDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_instance" {
@ -287,8 +304,9 @@ func testAccCheckInstanceDestroy(s *terraform.State) error {
}
// Try to find the resource
resp, err := conn.Instances(
[]string{rs.Primary.ID}, ec2.NewFilter())
resp, err := conn.DescribeInstances(&ec2.DescribeInstancesRequest{
InstanceIDs: []string{rs.Primary.ID},
})
if err == nil {
if len(resp.Reservations) > 0 {
return fmt.Errorf("still exist.")
@ -298,7 +316,7 @@ func testAccCheckInstanceDestroy(s *terraform.State) error {
}
// Verify the error is what we want
ec2err, ok := err.(*ec2.Error)
ec2err, ok := err.(aws.APIError)
if !ok {
return err
}
@ -321,9 +339,10 @@ func testAccCheckInstanceExists(n string, i *ec2.Instance) resource.TestCheckFun
return fmt.Errorf("No ID is set")
}
conn := testAccProvider.Meta().(*AWSClient).ec2conn
resp, err := conn.Instances(
[]string{rs.Primary.ID}, ec2.NewFilter())
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
resp, err := conn.DescribeInstances(&ec2.DescribeInstancesRequest{
InstanceIDs: []string{rs.Primary.ID},
})
if err != nil {
return err
}
@ -391,6 +410,12 @@ resource "aws_instance" "foo" {
device_name = "/dev/sdb"
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"
"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/schema"
"github.com/mitchellh/goamz/ec2"
)
func resourceAwsInternetGateway() *schema.Resource {
@ -28,7 +29,7 @@ func resourceAwsInternetGateway() *schema.Resource {
}
func resourceAwsInternetGatewayCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
// Create the gateway
log.Printf("[DEBUG] Creating internet gateway")
@ -38,16 +39,21 @@ func resourceAwsInternetGatewayCreate(d *schema.ResourceData, meta interface{})
}
// Get the ID and store it
ig := &resp.InternetGateway
d.SetId(ig.InternetGatewayId)
ig := resp.InternetGateway
d.SetId(*ig.InternetGatewayID)
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
return resourceAwsInternetGatewayAttach(d, meta)
}
func resourceAwsInternetGatewayRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
igRaw, _, err := IGStateRefreshFunc(ec2conn, d.Id())()
if err != nil {
@ -64,10 +70,10 @@ func resourceAwsInternetGatewayRead(d *schema.ResourceData, meta interface{}) er
// Gateway exists but not attached to the VPC
d.Set("vpc_id", "")
} 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
}
@ -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
}
@ -97,7 +103,7 @@ func resourceAwsInternetGatewayUpdate(d *schema.ResourceData, meta interface{})
}
func resourceAwsInternetGatewayDelete(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
// Detach if it is attached
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())
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 {
return nil
}
ec2err, ok := err.(*ec2.Error)
ec2err, ok := err.(aws.APIError)
if !ok {
return err
}
@ -129,7 +137,7 @@ func resourceAwsInternetGatewayDelete(d *schema.ResourceData, meta interface{})
}
func resourceAwsInternetGatewayAttach(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
if d.Get("vpc_id").(string) == "" {
log.Printf(
@ -143,7 +151,10 @@ func resourceAwsInternetGatewayAttach(d *schema.ResourceData, meta interface{})
d.Id(),
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 {
return err
}
@ -171,7 +182,7 @@ func resourceAwsInternetGatewayAttach(d *schema.ResourceData, meta interface{})
}
func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
// Get the old VPC ID to detach from
vpcID, _ := d.GetChange("vpc_id")
@ -189,9 +200,12 @@ func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{})
vpcID.(string))
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 {
ec2err, ok := err.(*ec2.Error)
ec2err, ok := err.(aws.APIError)
if ok {
if ec2err.Code == "InvalidInternetGatewayID.NotFound" {
err = nil
@ -232,9 +246,11 @@ func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{})
// an internet gateway.
func IGStateRefreshFunc(ec2conn *ec2.EC2, id string) resource.StateRefreshFunc {
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 {
ec2err, ok := err.(*ec2.Error)
ec2err, ok := err.(aws.APIError)
if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
resp = nil
} else {
@ -256,16 +272,18 @@ func IGStateRefreshFunc(ec2conn *ec2.EC2, id string) resource.StateRefreshFunc {
// IGAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used
// 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
return func() (interface{}, string, error) {
if start.IsZero() {
start = time.Now()
}
resp, err := conn.DescribeInternetGateways([]string{id}, ec2.NewFilter())
resp, err := ec2conn.DescribeInternetGateways(&ec2.DescribeInternetGatewaysRequest{
InternetGatewayIDs: []string{id},
})
if err != nil {
ec2err, ok := err.(*ec2.Error)
ec2err, ok := err.(aws.APIError)
if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
resp = nil
} else {
@ -291,6 +309,6 @@ func IGAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string) resourc
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"
"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/terraform"
"github.com/mitchellh/goamz/ec2"
)
func TestAccAWSInternetGateway(t *testing.T) {
@ -20,8 +21,8 @@ func TestAccAWSInternetGateway(t *testing.T) {
return fmt.Errorf("IG B is not attached")
}
id1 := v.Attachments[0].VpcId
id2 := v2.Attachments[0].VpcId
id1 := v.Attachments[0].VPCID
id2 := v2.Attachments[0].VPCID
if id1 == id2 {
return fmt.Errorf("Both attachment IDs are the same")
}
@ -97,6 +98,7 @@ func TestAccInternetGateway_tags(t *testing.T) {
Config: testAccCheckInternetGatewayConfigTags,
Check: resource.ComposeTestCheckFunc(
testAccCheckInternetGatewayExists("aws_internet_gateway.foo", &v),
testAccCheckTagsSDK(&v.Tags, "foo", "bar"),
),
},
@ -104,8 +106,8 @@ func TestAccInternetGateway_tags(t *testing.T) {
Config: testAccCheckInternetGatewayConfigTagsUpdate,
Check: resource.ComposeTestCheckFunc(
testAccCheckInternetGatewayExists("aws_internet_gateway.foo", &v),
testAccCheckTags(&v.Tags, "foo", ""),
testAccCheckTags(&v.Tags, "bar", "baz"),
testAccCheckTagsSDK(&v.Tags, "foo", ""),
testAccCheckTagsSDK(&v.Tags, "bar", "baz"),
),
},
},
@ -113,7 +115,7 @@ func TestAccInternetGateway_tags(t *testing.T) {
}
func testAccCheckInternetGatewayDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_internet_gateway" {
@ -121,8 +123,9 @@ func testAccCheckInternetGatewayDestroy(s *terraform.State) error {
}
// Try to find the resource
resp, err := conn.DescribeInternetGateways(
[]string{rs.Primary.ID}, ec2.NewFilter())
resp, err := ec2conn.DescribeInternetGateways(&ec2.DescribeInternetGatewaysRequest{
InternetGatewayIDs: []string{rs.Primary.ID},
})
if err == nil {
if len(resp.InternetGateways) > 0 {
return fmt.Errorf("still exists")
@ -132,7 +135,7 @@ func testAccCheckInternetGatewayDestroy(s *terraform.State) error {
}
// Verify the error is what we want
ec2err, ok := err.(*ec2.Error)
ec2err, ok := err.(aws.APIError)
if !ok {
return err
}
@ -155,9 +158,10 @@ func testAccCheckInternetGatewayExists(n string, ig *ec2.InternetGateway) resour
return fmt.Errorf("No ID is set")
}
conn := testAccProvider.Meta().(*AWSClient).ec2conn
resp, err := conn.DescribeInternetGateways(
[]string{rs.Primary.ID}, ec2.NewFilter())
ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
resp, err := ec2conn.DescribeInternetGateways(&ec2.DescribeInternetGatewaysRequest{
InternetGatewayIDs: []string{rs.Primary.ID},
})
if err != nil {
return err
}

View File

@ -1,9 +1,13 @@
package aws
import (
"encoding/base64"
"fmt"
"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 {
@ -33,42 +37,50 @@ func resourceAwsKeyPair() *schema.Resource {
}
func resourceAwsKeyPairCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
keyName := d.Get("key_name").(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 {
return fmt.Errorf("Error import KeyPair: %s", err)
}
d.SetId(resp.KeyName)
d.SetId(*resp.KeyName)
return nil
}
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 {
return fmt.Errorf("Error retrieving KeyPair: %s", err)
}
for _, keyPair := range resp.Keys {
if keyPair.Name == d.Id() {
d.Set("key_name", keyPair.Name)
d.Set("fingerprint", keyPair.Fingerprint)
for _, keyPair := range resp.KeyPairs {
if *keyPair.KeyName == d.Id() {
d.Set("key_name", keyPair.KeyName)
d.Set("fingerprint", keyPair.KeyFingerprint)
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 {
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
}

View File

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

View File

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

View File

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

View File

@ -6,10 +6,11 @@ import (
"log"
"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/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
)
func resourceAwsNetworkAcl() *schema.Resource {
@ -108,32 +109,34 @@ func resourceAwsNetworkAcl() *schema.Resource {
func resourceAwsNetworkAclCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
// Create the Network Acl
createOpts := &ec2.CreateNetworkAcl{
VpcId: d.Get("vpc_id").(string),
createOpts := &ec2.CreateNetworkACLRequest{
VPCID: aws.String(d.Get("vpc_id").(string)),
}
log.Printf("[DEBUG] Network Acl create config: %#v", createOpts)
resp, err := ec2conn.CreateNetworkAcl(createOpts)
resp, err := ec2conn.CreateNetworkACL(createOpts)
if err != nil {
return fmt.Errorf("Error creating network acl: %s", err)
}
// Get the ID and store it
networkAcl := &resp.NetworkAcl
d.SetId(networkAcl.NetworkAclId)
log.Printf("[INFO] Network Acl ID: %s", networkAcl.NetworkAclId)
networkAcl := resp.NetworkACL
d.SetId(*networkAcl.NetworkACLID)
log.Printf("[INFO] Network Acl ID: %s", *networkAcl.NetworkACLID)
// Update rules and subnet association once acl is created
return resourceAwsNetworkAclUpdate(d, meta)
}
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 {
return err
@ -142,29 +145,29 @@ func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error {
return nil
}
networkAcl := &resp.NetworkAcls[0]
var ingressEntries []ec2.NetworkAclEntry
var egressEntries []ec2.NetworkAclEntry
networkAcl := &resp.NetworkACLs[0]
var ingressEntries []ec2.NetworkACLEntry
var egressEntries []ec2.NetworkACLEntry
// separate the ingress and egress rules
for _, e := range networkAcl.EntrySet {
if e.Egress == true {
for _, e := range networkAcl.Entries {
if *e.Egress == true {
egressEntries = append(egressEntries, e)
} else {
ingressEntries = append(ingressEntries, e)
}
}
d.Set("vpc_id", networkAcl.VpcId)
d.Set("vpc_id", networkAcl.VPCID)
d.Set("ingress", ingressEntries)
d.Set("egress", egressEntries)
d.Set("tags", tagsToMap(networkAcl.Tags))
d.Set("tags", tagsToMapSDK(networkAcl.Tags))
return nil
}
func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
d.Partial(true)
if d.HasChange("ingress") {
@ -190,13 +193,16 @@ func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error
if err != nil {
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 {
return err
}
}
if err := setTags(ec2conn, d); err != nil {
if err := setTagsSDK(ec2conn, d); err != nil {
return err
} else {
d.SetPartial("tags")
@ -226,7 +232,11 @@ func updateNetworkAclEntries(d *schema.ResourceData, entryType string, ec2conn *
}
for _, remove := range toBeDeleted {
// 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 {
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 {
// 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 {
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 {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
log.Printf("[INFO] Deleting Network Acl: %s", d.Id())
return resource.Retry(5*time.Minute, func() error {
if _, err := ec2conn.DeleteNetworkAcl(d.Id()); err != nil {
ec2err := err.(*ec2.Error)
err := ec2conn.DeleteNetworkACL(&ec2.DeleteNetworkACLRequest{
NetworkACLID: aws.String(d.Id()),
})
if err != nil {
ec2err := err.(aws.APIError)
switch ec2err.Code {
case "InvalidNetworkAclID.NotFound":
return nil
@ -267,7 +288,10 @@ func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error
if err != nil {
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}
default:
// 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())
}
func getDefaultNetworkAcl(vpc_id string, ec2conn *ec2.EC2) (defaultAcl *ec2.NetworkAcl, err error) {
filter := ec2.NewFilter()
filter.Add("default", "true")
filter.Add("vpc-id", vpc_id)
resp, err := ec2conn.NetworkAcls([]string{}, filter)
func getDefaultNetworkAcl(vpc_id string, ec2conn *ec2.EC2) (defaultAcl *ec2.NetworkACL, err error) {
resp, err := ec2conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
NetworkACLIDs: []string{},
Filters: []ec2.Filter{
ec2.Filter{
Name: aws.String("default"),
Values: []string{"true"},
},
ec2.Filter{
Name: aws.String("vpc-id"),
Values: []string{vpc_id},
},
},
})
if err != nil {
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) {
filter := ec2.NewFilter()
filter.Add("association.subnet-id", subnetId)
resp, err := ec2conn.NetworkAcls([]string{}, filter)
func findNetworkAclAssociation(subnetId string, ec2conn *ec2.EC2) (networkAclAssociation *ec2.NetworkACLAssociation, err error) {
resp, err := ec2conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
NetworkACLIDs: []string{},
Filters: []ec2.Filter{
ec2.Filter{
Name: aws.String("association.subnet-id"),
Values: []string{subnetId},
},
},
})
if err != nil {
return nil, err
}
for _, association := range resp.NetworkAcls[0].AssociationSet {
if association.SubnetId == subnetId {
for _, association := range resp.NetworkACLs[0].Associations {
if *association.SubnetID == subnetId {
return &association, nil
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -4,9 +4,10 @@ import (
"fmt"
"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/terraform"
"github.com/mitchellh/goamz/ec2"
)
func TestAccAWSRouteTableAssociation(t *testing.T) {
@ -37,7 +38,7 @@ func TestAccAWSRouteTableAssociation(t *testing.T) {
}
func testAccCheckRouteTableAssociationDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_route_table_association" {
@ -45,11 +46,12 @@ func testAccCheckRouteTableAssociationDestroy(s *terraform.State) error {
}
// Try to find the resource
resp, err := conn.DescribeRouteTables(
[]string{rs.Primary.Attributes["route_table_Id"]}, ec2.NewFilter())
resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
RouteTableIDs: []string{rs.Primary.Attributes["route_table_id"]},
})
if err != nil {
// Verify the error is what we want
ec2err, ok := err.(*ec2.Error)
ec2err, ok := err.(aws.APIError)
if !ok {
return err
}
@ -62,7 +64,7 @@ func testAccCheckRouteTableAssociationDestroy(s *terraform.State) error {
rt := resp.RouteTables[0]
if len(rt.Associations) > 0 {
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")
}
conn := testAccProvider.Meta().(*AWSClient).ec2conn
resp, err := conn.DescribeRouteTables(
[]string{rs.Primary.Attributes["route_table_id"]}, ec2.NewFilter())
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
RouteTableIDs: []string{rs.Primary.Attributes["route_table_id"]},
})
if err != nil {
return err
}

View File

@ -4,9 +4,10 @@ import (
"fmt"
"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/terraform"
"github.com/mitchellh/goamz/ec2"
)
func TestAccAWSRouteTable_normal(t *testing.T) {
@ -19,7 +20,7 @@ func TestAccAWSRouteTable_normal(t *testing.T) {
routes := make(map[string]ec2.Route)
for _, r := range v.Routes {
routes[r.DestinationCidrBlock] = r
routes[*r.DestinationCIDRBlock] = r
}
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)
for _, r := range v.Routes {
routes[r.DestinationCidrBlock] = r
routes[*r.DestinationCIDRBlock] = r
}
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)
for _, r := range v.Routes {
routes[r.DestinationCidrBlock] = r
routes[*r.DestinationCIDRBlock] = r
}
if _, ok := routes["10.1.0.0/16"]; !ok {
@ -133,7 +134,7 @@ func TestAccAWSRouteTable_tags(t *testing.T) {
Config: testAccRouteTableConfigTags,
Check: resource.ComposeTestCheckFunc(
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,
Check: resource.ComposeTestCheckFunc(
testAccCheckRouteTableExists("aws_route_table.foo", &route_table),
testAccCheckTags(&route_table.Tags, "foo", ""),
testAccCheckTags(&route_table.Tags, "bar", "baz"),
testAccCheckTagsSDK(&route_table.Tags, "foo", ""),
testAccCheckTagsSDK(&route_table.Tags, "bar", "baz"),
),
},
},
@ -150,7 +151,7 @@ func TestAccAWSRouteTable_tags(t *testing.T) {
}
func testAccCheckRouteTableDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_route_table" {
@ -158,8 +159,9 @@ func testAccCheckRouteTableDestroy(s *terraform.State) error {
}
// Try to find the resource
resp, err := conn.DescribeRouteTables(
[]string{rs.Primary.ID}, ec2.NewFilter())
resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
RouteTableIDs: []string{rs.Primary.ID},
})
if err == nil {
if len(resp.RouteTables) > 0 {
return fmt.Errorf("still exist.")
@ -169,7 +171,7 @@ func testAccCheckRouteTableDestroy(s *terraform.State) error {
}
// Verify the error is what we want
ec2err, ok := err.(*ec2.Error)
ec2err, ok := err.(aws.APIError)
if !ok {
return err
}
@ -192,9 +194,10 @@ func testAccCheckRouteTableExists(n string, v *ec2.RouteTable) resource.TestChec
return fmt.Errorf("No ID is set")
}
conn := testAccProvider.Meta().(*AWSClient).ec2conn
resp, err := conn.DescribeRouteTables(
[]string{rs.Primary.ID}, ec2.NewFilter())
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
RouteTableIDs: []string{rs.Primary.ID},
})
if err != nil {
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
testCheck := func(*terraform.State) error {
@ -218,7 +224,7 @@ func TestAccAWSRouteTable_vpcPeering(t *testing.T) {
routes := make(map[string]ec2.Route)
for _, r := range v.Routes {
routes[r.DestinationCidrBlock] = r
routes[*r.DestinationCIDRBlock] = r
}
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 = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
@ -359,7 +368,7 @@ resource "aws_route_table" "foo" {
route {
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"
"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/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
)
func resourceAwsSecurityGroup() *schema.Resource {
@ -141,18 +142,18 @@ func resourceAwsSecurityGroup() *schema.Resource {
}
func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
securityGroupOpts := ec2.SecurityGroup{
Name: d.Get("name").(string),
securityGroupOpts := &ec2.CreateSecurityGroupRequest{
GroupName: aws.String(d.Get("name").(string)),
}
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 {
securityGroupOpts.Description = v.(string)
securityGroupOpts.Description = aws.String(v.(string))
}
log.Printf(
@ -162,7 +163,7 @@ func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) er
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())
@ -186,7 +187,7 @@ func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) er
}
func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
if err != nil {
@ -197,24 +198,23 @@ func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) erro
return nil
}
sg := sgRaw.(*ec2.SecurityGroupInfo)
sg := sgRaw.(ec2.SecurityGroup)
ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPerms)
egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermsEgress)
ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissions)
egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissionsEgress)
d.Set("description", sg.Description)
d.Set("name", sg.Name)
d.Set("vpc_id", sg.VpcId)
d.Set("owner_id", sg.OwnerId)
d.Set("name", sg.GroupName)
d.Set("vpc_id", sg.VPCID)
d.Set("owner_id", sg.OwnerID)
d.Set("ingress", ingressRules)
d.Set("egress", egressRules)
d.Set("tags", tagsToMap(sg.Tags))
d.Set("tags", tagsToMapSDK(sg.Tags))
return nil
}
func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
if err != nil {
@ -224,7 +224,8 @@ func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) er
d.SetId("")
return nil
}
group := sgRaw.(*ec2.SecurityGroupInfo).SecurityGroup
group := sgRaw.(ec2.SecurityGroup)
err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group)
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
}
@ -248,14 +249,16 @@ func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) er
}
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())
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 {
ec2err, ok := err.(*ec2.Error)
ec2err, ok := err.(aws.APIError)
if !ok {
return err
}
@ -313,34 +316,45 @@ func resourceAwsSecurityGroupRuleHash(v interface{}) int {
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{})
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]
if !ok {
m = make(map[string]interface{})
ruleMap[k] = m
}
m["from_port"] = perm.FromPort
m["to_port"] = perm.ToPort
m["protocol"] = perm.Protocol
m["from_port"] = fromPort
m["to_port"] = toPort
m["protocol"] = *perm.IPProtocol
if len(perm.SourceIPs) > 0 {
if len(perm.IPRanges) > 0 {
raw, ok := m["cidr_blocks"]
if !ok {
raw = make([]string, 0, len(perm.SourceIPs))
raw = make([]string, 0, len(perm.IPRanges))
}
list := raw.([]string)
list = append(list, perm.SourceIPs...)
for _, ip := range perm.IPRanges {
list = append(list, *ip.CIDRIP)
}
m["cidr_blocks"] = list
}
var groups []string
if len(perm.SourceGroups) > 0 {
groups = flattenSecurityGroups(perm.SourceGroups)
if len(perm.UserIDGroupPairs) > 0 {
groups = flattenSecurityGroupsSDK(perm.UserIDGroupPairs)
}
for i, id := range groups {
if id == d.Id() {
@ -364,7 +378,6 @@ func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []
for _, m := range ruleMap {
rules = append(rules, m)
}
return rules
}
@ -383,6 +396,7 @@ func resourceAwsSecurityGroupUpdateRules(
os := o.(*schema.Set)
ns := n.(*schema.Set)
// TODO: re-munge this when test is updated
remove := expandIPPerms(d.Id(), os.Difference(ns).List())
add := expandIPPerms(d.Id(), ns.Difference(os).List())
@ -396,34 +410,53 @@ func resourceAwsSecurityGroupUpdateRules(
// not have service issues.
if len(remove) > 0 || len(add) > 0 {
ec2conn := meta.(*AWSClient).ec2conn
ec2conn := meta.(*AWSClient).awsEC2conn
var err error
if len(remove) > 0 {
// Revoke the old rules
revoke := ec2conn.RevokeSecurityGroup
log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v",
group, ruleset, remove)
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",
group, ruleset, remove)
if _, err := revoke(group, remove); err != nil {
if err != nil {
return fmt.Errorf(
"Error revoking security group %s rules: %s",
"Error authorizing security group %s rules: %s",
ruleset, err)
}
}
if len(add) > 0 {
log.Printf("[DEBUG] Authorizing security group %#v %s rule: %#v",
group, ruleset, add)
// Authorize the new rules
authorize := ec2conn.AuthorizeSecurityGroup
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",
group, ruleset, add)
if _, err := authorize(group, add); err != nil {
if err != nil {
return fmt.Errorf(
"Error authorizing security group %s rules: %s",
ruleset, err)
@ -431,7 +464,6 @@ func resourceAwsSecurityGroupUpdateRules(
}
}
}
return nil
}
@ -439,10 +471,12 @@ func resourceAwsSecurityGroupUpdateRules(
// a security group.
func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
sgs := []ec2.SecurityGroup{ec2.SecurityGroup{Id: id}}
resp, err := conn.SecurityGroups(sgs, nil)
req := &ec2.DescribeSecurityGroupsRequest{
GroupIDs: []string{id},
}
resp, err := conn.DescribeSecurityGroups(req)
if err != nil {
if ec2err, ok := err.(*ec2.Error); ok {
if ec2err, ok := err.(aws.APIError); ok {
if ec2err.Code == "InvalidSecurityGroupID.NotFound" ||
ec2err.Code == "InvalidGroup.NotFound" {
resp = nil
@ -460,7 +494,7 @@ func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return nil, "", nil
}
group := &resp.Groups[0]
group := resp.SecurityGroups[0]
return group, "exists", nil
}
}

View File

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

View File

@ -68,10 +68,10 @@ func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error {
// Get the ID and store it
subnet := resp.Subnet
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
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{
Pending: []string{"pending"},
Target: "available",

View File

@ -15,11 +15,11 @@ func TestAccAWSSubnet(t *testing.T) {
testCheck := func(*terraform.State) error {
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 {
return fmt.Errorf("bad MapPublicIpOnLaunch: %t", v.MapPublicIPOnLaunch)
return fmt.Errorf("bad MapPublicIpOnLaunch: %t", *v.MapPublicIPOnLaunch)
}
return nil

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import (
"strings"
"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/rds"
"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
// security group rules and returns EC2 API compatible objects
func expandIPPerms(id string, configured []interface{}) []ec2.IPPerm {
perms := make([]ec2.IPPerm, len(configured))
func expandIPPerms(id string, configured []interface{}) []awsEC2.IPPermission {
perms := make([]awsEC2.IPPermission, len(configured))
for i, mRaw := range configured {
var perm ec2.IPPerm
var perm awsEC2.IPPermission
m := mRaw.(map[string]interface{})
perm.FromPort = m["from_port"].(int)
perm.ToPort = m["to_port"].(int)
perm.Protocol = m["protocol"].(string)
perm.FromPort = aws.Integer(m["from_port"].(int))
perm.ToPort = aws.Integer(m["to_port"].(int))
perm.IPProtocol = aws.String(m["protocol"].(string))
var groups []string
if raw, ok := m["security_groups"]; ok {
@ -61,25 +62,25 @@ func expandIPPerms(id string, configured []interface{}) []ec2.IPPerm {
}
if len(groups) > 0 {
perm.SourceGroups = make([]ec2.UserSecurityGroup, len(groups))
perm.UserIDGroupPairs = make([]awsEC2.UserIDGroupPair, len(groups))
for i, name := range groups {
ownerId, id := "", name
if items := strings.Split(id, "/"); len(items) > 1 {
ownerId, id = items[0], items[1]
}
perm.SourceGroups[i] = ec2.UserSecurityGroup{
Id: id,
OwnerId: ownerId,
perm.UserIDGroupPairs[i] = awsEC2.UserIDGroupPair{
GroupID: aws.String(id),
UserID: aws.String(ownerId),
}
}
}
if raw, ok := m["cidr_blocks"]; ok {
list := raw.([]interface{})
perm.SourceIPs = make([]string, len(list))
perm.IPRanges = make([]awsEC2.IPRange, len(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
}
// 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()
// can handle
func flattenHealthCheck(check *elb.HealthCheck) []map[string]interface{} {
@ -162,6 +138,15 @@ func flattenSecurityGroups(list []ec2.UserSecurityGroup) []string {
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
func flattenInstances(list []elb.Instance) []string {
result := make([]string, 0, len(list))

View File

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

View File

@ -30,7 +30,7 @@ func Provider() terraform.ResourceProvider {
"timeout": &schema.Schema{
Type: schema.TypeInt,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_TIMEOUT", 180),
DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_TIMEOUT", 300),
},
},
@ -46,6 +46,9 @@ func Provider() terraform.ResourceProvider {
"cloudstack_nic": resourceCloudStackNIC(),
"cloudstack_port_forward": resourceCloudStackPortForward(),
"cloudstack_vpc": resourceCloudStackVPC(),
"cloudstack_vpn_connection": resourceCloudStackVPNConnection(),
"cloudstack_vpn_customer_gateway": resourceCloudStackVPNCustomerGateway(),
"cloudstack_vpn_gateway": resourceCloudStackVPNGateway(),
},
ConfigureFunc: providerConfigure,

View File

@ -51,7 +51,8 @@ var CLOUDSTACK_NETWORK_1_OFFERING = ""
var CLOUDSTACK_NETWORK_1_IPADDRESS = ""
var CLOUDSTACK_NETWORK_2 = ""
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_NETWORK_CIDR = ""
var CLOUDSTACK_VPC_NETWORK_OFFERING = ""

View File

@ -95,18 +95,18 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{})
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
zone, _, err := cs.Zone.GetZoneByName(d.Get("zone").(string))
if err != nil {
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
p := cs.VirtualMachine.NewDeployVirtualMachineParams(serviceofferingid, templateid, zone.Id)

View File

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

View File

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

View File

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

View File

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

View File

@ -72,8 +72,8 @@ func testAccCheckCloudStackVPCAttributes(
return fmt.Errorf("Bad display text: %s", vpc.Displaytext)
}
if vpc.Cidr != CLOUDSTACK_VPC_CIDR {
return fmt.Errorf("Bad VPC offering: %s", vpc.Cidr)
if vpc.Cidr != CLOUDSTACK_VPC_CIDR_1 {
return fmt.Errorf("Bad VPC CIDR: %s", vpc.Cidr)
}
return nil
@ -113,6 +113,6 @@ resource "cloudstack_vpc" "foo" {
vpc_offering = "%s"
zone = "%s"
}`,
CLOUDSTACK_VPC_CIDR,
CLOUDSTACK_VPC_CIDR_1,
CLOUDSTACK_VPC_OFFERING,
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)
case "vpc":
uuid, err = cs.VPC.GetVPCID(value)
case "template":
uuid, err = cs.Template.GetTemplateID(value, "executable")
case "network":
uuid, err = cs.Network.GetNetworkID(value)
case "zone":
@ -71,6 +69,22 @@ func retrieveUUID(cs *cloudstack.CloudStackClient, name, value string) (uuid str
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 {
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)

View File

@ -21,6 +21,7 @@ func Provider() terraform.ResourceProvider {
"digitalocean_domain": resourceDigitalOceanDomain(),
"digitalocean_droplet": resourceDigitalOceanDroplet(),
"digitalocean_record": resourceDigitalOceanRecord(),
"digitalocean_ssh_key": resourceDigitalOceanSSHKey(),
},
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)
// Set the service accounts
for i, serviceAccount := range instance.ServiceAccounts {
prefix := fmt.Sprintf("service_account.%d", i)
d.Set(prefix+".email", serviceAccount.Email)
d.Set(prefix+".scopes.#", len(serviceAccount.Scopes))
for j, scope := range serviceAccount.Scopes {
d.Set(fmt.Sprintf("%s.scopes.%d", prefix, j), scope)
serviceAccounts := make([]map[string]interface{}, 0, 1)
for _, serviceAccount := range instance.ServiceAccounts {
scopes := make([]string, len(serviceAccount.Scopes))
for i, scope := range serviceAccount.Scopes {
scopes[i] = scope
}
serviceAccounts = append(serviceAccounts, map[string]interface{}{
"email": serviceAccount.Email,
"scopes": scopes,
})
}
d.Set("service_account", serviceAccounts)
networksCount := d.Get("network.#").(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.
externalIP := ""
internalIP := ""
networks := make([]map[string]interface{}, 0, 1)
if networksCount > 0 {
// TODO: Remove this when realizing deprecation of .network
for i, iface := range instance.NetworkInterfaces {
prefix := fmt.Sprintf("network.%d", i)
d.Set(prefix+".name", iface.Name)
log.Printf(prefix+".name = %s", iface.Name)
for _, iface := range instance.NetworkInterfaces {
var natIP string
for _, config := range iface.AccessConfigs {
if config.Type == "ONE_TO_ONE_NAT" {
@ -524,23 +526,28 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
if 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 {
for i, iface := range instance.NetworkInterfaces {
prefix := fmt.Sprintf("network_interface.%d", i)
d.Set(prefix+".name", iface.Name)
for _, iface := range instance.NetworkInterfaces {
// The first non-empty ip is left in natIP
var natIP string
for j, config := range iface.AccessConfigs {
acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j)
d.Set(acPrefix+".nat_ip", config.NatIP)
accessConfigs := make(
[]map[string]interface{}, 0, len(iface.AccessConfigs))
for _, config := range iface.AccessConfigs {
accessConfigs = append(accessConfigs, map[string]interface{}{
"nat_ip": config.NatIP,
})
if natIP == "" {
natIP = config.NatIP
}
@ -550,13 +557,18 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
externalIP = natIP
}
d.Set(prefix+".address", iface.NetworkIP)
if internalIP == "" {
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
// 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
InfoColor string
ErrorColor string
WarnColor string
Ui cli.Ui
}
@ -33,6 +34,10 @@ func (u *ColorizeUi) Error(message string) {
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 {
if color == "" {
return message

View File

@ -33,9 +33,9 @@ func validateContext(ctx *terraform.Context, ui cli.Ui) bool {
"fix these before continuing.\n")
if len(ws) > 0 {
ui.Output("Warnings:\n")
ui.Warn("Warnings:\n")
for _, w := range ws {
ui.Output(fmt.Sprintf(" * %s", w))
ui.Warn(fmt.Sprintf(" * %s", w))
}
if len(es) > 0 {
@ -44,13 +44,16 @@ func validateContext(ctx *terraform.Context, ui cli.Ui) bool {
}
if len(es) > 0 {
ui.Output("Errors:\n")
ui.Error("Errors:\n")
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 true

View File

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

View File

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

View File

@ -1,339 +1,57 @@
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
}
// RemoteCommand is a Command implementation that is used to
// enable and disable remote state management
type RemoteCommand struct {
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)
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 {
if len(args) == 0 {
c.Ui.Error(c.Help())
return 1
}
// Show help if given no inputs
if !c.conf.disableRemote && c.remoteConf.Type == "atlas" && len(config) == 0 {
cmdFlags.Usage()
switch args[0] {
case "config":
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
}
// 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 {
helpText := `
Usage: terraform remote [options]
Usage: terraform remote <subcommand> [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.
Configure remote state storage with Terraform.
Options:
Available subcommands:
-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.
config Configure the remote storage settings.
pull Sync the remote storage by downloading to local storage.
push Sync the remote storage by uploading the local storage.
`
return strings.TrimSpace(helpText)
}
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"
)
type PullCommand struct {
type RemotePullCommand struct {
Meta
}
func (c *PullCommand) Run(args []string) int {
func (c *RemotePullCommand) Run(args []string) int {
args = c.Meta.process(args, false)
cmdFlags := flag.NewFlagSet("pull", flag.ContinueOnError)
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
@ -67,7 +67,7 @@ func (c *PullCommand) Run(args []string) int {
return 0
}
func (c *PullCommand) Help() string {
func (c *RemotePullCommand) Help() string {
helpText := `
Usage: terraform pull [options]
@ -77,6 +77,6 @@ Usage: terraform pull [options]
return strings.TrimSpace(helpText)
}
func (c *PullCommand) Synopsis() string {
func (c *RemotePullCommand) Synopsis() string {
return "Refreshes the local state copy from the remote server"
}

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ import (
)
// Test disabling remote management
func TestRemote_disable(t *testing.T) {
func TestRemoteConfig_disable(t *testing.T) {
tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd)
@ -39,7 +39,7 @@ func TestRemote_disable(t *testing.T) {
}
ui := new(cli.MockUi)
c := &RemoteCommand{
c := &RemoteConfigCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
@ -68,7 +68,7 @@ func TestRemote_disable(t *testing.T) {
}
// Test disabling remote management without pulling
func TestRemote_disable_noPull(t *testing.T) {
func TestRemoteConfig_disable_noPull(t *testing.T) {
tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd)
@ -94,7 +94,7 @@ func TestRemote_disable_noPull(t *testing.T) {
}
ui := new(cli.MockUi)
c := &RemoteCommand{
c := &RemoteConfigCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
@ -122,12 +122,12 @@ func TestRemote_disable_noPull(t *testing.T) {
}
// 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)
defer testFixCwd(t, tmp, cwd)
ui := new(cli.MockUi)
c := &RemoteCommand{
c := &RemoteConfigCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
@ -141,7 +141,7 @@ func TestRemote_disable_notEnabled(t *testing.T) {
}
// 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)
defer testFixCwd(t, tmp, cwd)
@ -171,7 +171,7 @@ func TestRemote_disable_otherState(t *testing.T) {
}
ui := new(cli.MockUi)
c := &RemoteCommand{
c := &RemoteConfigCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
@ -185,7 +185,7 @@ func TestRemote_disable_otherState(t *testing.T) {
}
// 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)
defer testFixCwd(t, tmp, cwd)
@ -215,7 +215,7 @@ func TestRemote_managedAndNonManaged(t *testing.T) {
}
ui := new(cli.MockUi)
c := &RemoteCommand{
c := &RemoteConfigCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
@ -229,12 +229,12 @@ func TestRemote_managedAndNonManaged(t *testing.T) {
}
// Test initializing blank state
func TestRemote_initBlank(t *testing.T) {
func TestRemoteConfig_initBlank(t *testing.T) {
tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd)
ui := new(cli.MockUi)
c := &RemoteCommand{
c := &RemoteConfigCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
@ -269,12 +269,12 @@ func TestRemote_initBlank(t *testing.T) {
}
// Test initializing without remote settings
func TestRemote_initBlank_missingRemote(t *testing.T) {
func TestRemoteConfig_initBlank_missingRemote(t *testing.T) {
tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd)
ui := new(cli.MockUi)
c := &RemoteCommand{
c := &RemoteConfigCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
@ -288,7 +288,7 @@ func TestRemote_initBlank_missingRemote(t *testing.T) {
}
// Test updating remote config
func TestRemote_updateRemote(t *testing.T) {
func TestRemoteConfig_updateRemote(t *testing.T) {
tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd)
@ -310,7 +310,7 @@ func TestRemote_updateRemote(t *testing.T) {
}
ui := new(cli.MockUi)
c := &RemoteCommand{
c := &RemoteConfigCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
@ -345,7 +345,7 @@ func TestRemote_updateRemote(t *testing.T) {
}
// Test enabling remote state
func TestRemote_enableRemote(t *testing.T) {
func TestRemoteConfig_enableRemote(t *testing.T) {
tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd)
@ -365,7 +365,7 @@ func TestRemote_enableRemote(t *testing.T) {
}
ui := new(cli.MockUi)
c := &RemoteCommand{
c := &RemoteConfigCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,

View File

@ -80,18 +80,6 @@ func init() {
}, 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) {
return &command.RefreshCommand{
Meta: meta,

View File

@ -17,6 +17,7 @@ var Funcs map[string]ast.Function
func init() {
Funcs = map[string]ast.Function{
"file": interpolationFuncFile(),
"format": interpolationFuncFormat(),
"join": interpolationFuncJoin(),
"element": interpolationFuncElement(),
"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
// multi-variable values to be joined by some character.
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) {
testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{

View File

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

View File

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

View File

@ -174,6 +174,10 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
// Verify the args
for i, expected := range function.ArgTypes {
if expected == ast.TypeAny {
continue
}
if args[i] != expected {
cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i])
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 function.Variadic {
if function.Variadic && function.VariadicType != ast.TypeAny {
args = args[len(function.ArgTypes):]
for i, t := range args {
if t != function.VariadicType {

View File

@ -3,6 +3,7 @@ package schema
import (
"errors"
"fmt"
"strconv"
"github.com/hashicorp/terraform/terraform"
)
@ -24,6 +25,31 @@ type Resource struct {
// resource.
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 only optional operation is Update. If Update is not implemented,
@ -69,6 +95,10 @@ type DeleteFunc func(*ResourceData, interface{}) error
// See Resource documentation.
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.
func (r *Resource) Apply(
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)
if err != nil {
return s, err
@ -169,6 +207,13 @@ func (r *Resource) Refresh(
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
}
@ -189,3 +234,10 @@ func (r *Resource) InternalValidate() error {
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 (
"fmt"
"reflect"
"strconv"
"testing"
"github.com/hashicorp/terraform/terraform"
@ -478,3 +479,218 @@ func TestResourceRefresh_noExists(t *testing.T) {
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.
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
@ -877,7 +892,7 @@ func (m schemaMap) validate(
raw, err = schema.DefaultFunc()
if err != nil {
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
@ -886,7 +901,7 @@ func (m schemaMap) validate(
if !ok {
if schema.Required {
return nil, []error{fmt.Errorf(
"%s: required field is not set", k)}
"%q: required field is not set", k)}
}
return nil, nil
@ -895,7 +910,7 @@ func (m schemaMap) validate(
if !schema.Required && !schema.Optional {
// This is a computed-only field
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)
@ -1066,16 +1081,30 @@ func (m schemaMap) validateType(
raw interface{},
schema *Schema,
c *terraform.ResourceConfig) ([]string, []error) {
var ws []string
var es []error
switch schema.Type {
case TypeSet:
fallthrough
case TypeList:
return m.validateList(k, raw, schema, c)
ws, es = m.validateList(k, raw, schema, c)
case TypeMap:
return m.validateMap(k, raw, schema, c)
ws, es = m.validateMap(k, raw, schema, c)
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.

View File

@ -2583,15 +2583,15 @@ func TestSchemaMap_InternalValidate(t *testing.T) {
}
func TestSchemaMap_Validate(t *testing.T) {
cases := []struct {
cases := map[string]struct {
Schema map[string]*Schema
Config map[string]interface{}
Vars map[string]string
Warn bool
Err bool
Errors []error
Warnings []string
}{
// #0 Good
{
"Good": {
Schema: map[string]*Schema{
"availability_zone": &Schema{
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{
"size": &Schema{
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{
"availability_zone": &Schema{
Type: TypeString,
@ -2638,8 +2636,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #3 Invalid type
{
"Invalid basic type": {
Schema: map[string]*Schema{
"port": &Schema{
Type: TypeInt,
@ -2654,8 +2651,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #4
{
"Invalid complex type": {
Schema: map[string]*Schema{
"user_data": &Schema{
Type: TypeString,
@ -2674,8 +2670,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #5 Bad type, interpolated
{
"Bad type, interpolated": {
Schema: map[string]*Schema{
"size": &Schema{
Type: TypeInt,
@ -2694,8 +2689,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #6 Required but has DefaultFunc
{
"Required but has DefaultFunc": {
Schema: map[string]*Schema{
"availability_zone": &Schema{
Type: TypeString,
@ -2709,8 +2703,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Config: nil,
},
// #7 Required but has DefaultFunc return nil
{
"Required but has DefaultFunc return nil": {
Schema: map[string]*Schema{
"availability_zone": &Schema{
Type: TypeString,
@ -2726,8 +2719,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #8 Optional sub-resource
{
"Optional sub-resource": {
Schema: map[string]*Schema{
"ingress": &Schema{
Type: TypeList,
@ -2747,8 +2739,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: false,
},
// #9 Not a list
{
"Not a list": {
Schema: map[string]*Schema{
"ingress": &Schema{
Type: TypeList,
@ -2770,8 +2761,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #10 Required sub-resource field
{
"Required sub-resource field": {
Schema: map[string]*Schema{
"ingress": &Schema{
Type: TypeList,
@ -2795,8 +2785,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #11 Good sub-resource
{
"Good sub-resource": {
Schema: map[string]*Schema{
"ingress": &Schema{
Type: TypeList,
@ -2823,8 +2812,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: false,
},
// #12 Invalid/unknown field
{
"Invalid/unknown field": {
Schema: map[string]*Schema{
"availability_zone": &Schema{
Type: TypeString,
@ -2841,8 +2829,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #13 Computed field set
{
"Computed field set": {
Schema: map[string]*Schema{
"availability_zone": &Schema{
Type: TypeString,
@ -2857,8 +2844,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #14 Not a set
{
"Not a set": {
Schema: map[string]*Schema{
"ports": &Schema{
Type: TypeSet,
@ -2877,8 +2863,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #15 Maps
{
"Maps": {
Schema: map[string]*Schema{
"user_data": &Schema{
Type: TypeMap,
@ -2893,8 +2878,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #16
{
"Good map: data surrounded by extra slice": {
Schema: map[string]*Schema{
"user_data": &Schema{
Type: TypeMap,
@ -2911,8 +2895,7 @@ func TestSchemaMap_Validate(t *testing.T) {
},
},
// #17
{
"Good map": {
Schema: map[string]*Schema{
"user_data": &Schema{
Type: TypeMap,
@ -2927,8 +2910,7 @@ func TestSchemaMap_Validate(t *testing.T) {
},
},
// #18
{
"Bad map: just a slice": {
Schema: map[string]*Schema{
"user_data": &Schema{
Type: TypeMap,
@ -2945,8 +2927,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #19
{
"Good set: config has slice with single interpolated value": {
Schema: map[string]*Schema{
"security_groups": &Schema{
Type: TypeSet,
@ -2967,8 +2948,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: false,
},
// #20
{
"Bad set: config has single interpolated value": {
Schema: map[string]*Schema{
"security_groups": &Schema{
Type: TypeSet,
@ -2986,8 +2966,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #21 Bad, subresource should not allow unknown elements
{
"Bad, subresource should not allow unknown elements": {
Schema: map[string]*Schema{
"ingress": &Schema{
Type: TypeList,
@ -3015,8 +2994,7 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: true,
},
// #22 Bad, subresource should not allow invalid types
{
"Bad, subresource should not allow invalid types": {
Schema: map[string]*Schema{
"ingress": &Schema{
Type: TypeList,
@ -3042,9 +3020,74 @@ func TestSchemaMap_Validate(t *testing.T) {
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)
if err != nil {
t.Fatalf("err: %s", err)
@ -3063,18 +3106,24 @@ func TestSchemaMap_Validate(t *testing.T) {
ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
if (len(es) > 0) != tc.Err {
if len(es) == 0 {
t.Errorf("%d: no errors", i)
t.Errorf("%q: no errors", tn)
}
for _, e := range es {
t.Errorf("%d: err: %s", i, e)
t.Errorf("%q: err: %s", tn, e)
}
t.FailNow()
}
if (len(ws) > 0) != tc.Warn {
t.Fatalf("%d: ws: %#v", i, ws)
if !reflect.DeepEqual(ws, tc.Warnings) {
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"
"time"
"code.google.com/p/go.crypto/ssh"
"golang.org/x/crypto/ssh"
)
// RemoteCmd represents a remote command being prepared or run.

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import (
"log"
"time"
"code.google.com/p/go.crypto/ssh"
"golang.org/x/crypto/ssh"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/go-homedir"
"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_OS=${XC_OS:-linux darwin windows freebsd openbsd}
# Install dependencies
echo "==> Getting dependencies..."
go get ./...
# Install dependencies unless running in quick mode
if [ "${TF_QUICKDEV}x" == "x" ]; then
echo "==> Getting dependencies..."
go get ./...
fi
# Delete the old dir
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,
"consul": consulFactory,
"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) {
m := testModule(t, "apply-provisioner-resource-ref")
p := testProvider("aws")
@ -5343,6 +5473,15 @@ func testProvisioner() *MockResourceProvisioner {
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 = `
root: root
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.
type EvalIf struct {
If func(EvalContext) (bool, error)
Node EvalNode
Then EvalNode
Else EvalNode
}
// TODO: test
@ -14,7 +15,11 @@ func (n *EvalIf) Eval(ctx EvalContext) (interface{}, error) {
}
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

View File

@ -5,16 +5,77 @@ import (
)
// 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 {
Name string
Tainted bool
TaintedIndex int
Output **InstanceState
}
// TODO: test
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()
// 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.
rs := mod.Resources[n.Name]
rs := mod.Resources[resourceName]
if rs == nil {
return nil, nil
}
var result *InstanceState
if !n.Tainted {
// Return the primary
result = rs.Primary
} 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]
}
// Use the delegate function to get the instance state from the resource state
is, err := readerFn(rs)
if err != nil {
return nil, err
}
// Write the result to the output pointer
if n.Output != nil {
*n.Output = result
if output != nil {
*output = is
}
return result, nil
return is, nil
}
// EvalRequireState is an EvalNode implementation that early exits
@ -98,20 +149,85 @@ func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) {
return nil, nil
}
// EvalWriteState is an EvalNode implementation that reads the
// InstanceState for a specific resource out of the state.
// EvalWriteState is an EvalNode implementation that writes the
// primary InstanceState for a specific resource into the state.
type EvalWriteState struct {
Name string
ResourceType string
Dependencies []string
State **InstanceState
Tainted *bool
TaintedIndex int
TaintedClearPrimary bool
}
// TODO: test
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()
if state == nil {
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.
rs := mod.Resources[n.Name]
rs := mod.Resources[resourceName]
if rs == nil {
rs = &ResourceState{}
rs.init()
mod.Resources[n.Name] = rs
mod.Resources[resourceName] = rs
}
rs.Type = n.ResourceType
rs.Dependencies = n.Dependencies
rs.Type = resourceType
rs.Dependencies = dependencies
if n.Tainted != nil && *n.Tainted {
if n.TaintedIndex != -1 {
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
if err := writerFn(rs); err != nil {
return nil, err
}
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
// 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
// the old state of the to-be-destroyed resource.
type EvalDeposeState struct {
@ -188,8 +324,8 @@ func (n *EvalDeposeState) Eval(ctx EvalContext) (interface{}, error) {
return nil, nil
}
// Depose to the tainted
rs.Tainted = append(rs.Tainted, rs.Primary)
// Depose
rs.Deposed = append(rs.Deposed, rs.Primary)
rs.Primary = nil
return nil, nil
@ -221,15 +357,15 @@ func (n *EvalUndeposeState) Eval(ctx EvalContext) (interface{}, error) {
return nil, nil
}
// If we don't have any tainted, then we don't have anything to do
if len(rs.Tainted) == 0 {
// If we don't have any desposed resource, then we don't have anything to do
if len(rs.Deposed) == 0 {
return nil, nil
}
// Undepose to the tainted
idx := len(rs.Tainted) - 1
rs.Primary = rs.Tainted[idx]
rs.Tainted[idx] = nil
// Undepose
idx := len(rs.Deposed) - 1
rs.Primary = rs.Deposed[idx]
rs.Deposed[idx] = nil
return nil, nil
}

View File

@ -66,3 +66,163 @@ func TestEvalUpdateStateHook(t *testing.T) {
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(),
})
if n.Resource.Lifecycle.CreateBeforeDestroy {
// If we're only destroying tainted resources, then we only
// want to find tainted resources and destroy them here.
steps = append(steps, &TaintedTransformer{
steps = append(steps, &DeposedTransformer{
State: state,
View: n.Resource.Id(),
Deposed: n.Resource.Lifecycle.CreateBeforeDestroy,
DeposedInclude: true,
})
}
case DestroyTainted:
// If we're only destroying tainted resources, then we only
// want to find tainted resources and destroy them here.
steps = append(steps, &TaintedTransformer{
State: state,
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 {
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)
}
}
@ -548,7 +548,12 @@ func (m *ModuleState) String() string {
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))
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))
}
for idx, t := range rs.Deposed {
buf.WriteString(fmt.Sprintf(" Deposed ID %d = %s\n", idx+1, t.ID))
}
if len(rs.Dependencies) > 0 {
buf.WriteString(fmt.Sprintf("\n Dependencies:\n"))
for _, dep := range rs.Dependencies {
@ -644,6 +653,16 @@ type ResourceState struct {
// However, in pathological cases, it is possible for the number
// of instances to accumulate.
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.
@ -744,6 +763,12 @@ func (r *ResourceState) deepcopy() *ResourceState {
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
}
@ -762,6 +787,19 @@ func (r *ResourceState) prune() {
}
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() {
@ -794,6 +832,11 @@ type InstanceState struct {
// that is necessary for the Terraform run to complete, but is not
// persisted to a state file.
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() {

View File

@ -443,9 +443,9 @@ aws_instance.bar:
`
const testTerraformApplyErrorDestroyCreateBeforeDestroyStr = `
aws_instance.bar: (1 tainted)
aws_instance.bar: (1 deposed)
ID = foo
Tainted ID 1 = bar
Deposed ID 1 = bar
`
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
return true, nil
},
Node: EvalNoop{},
Then: EvalNoop{},
},
&EvalIf{
@ -301,7 +301,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
return createBeforeDestroyEnabled, nil
},
Node: &EvalDeposeState{
Then: &EvalDeposeState{
Name: n.stateId(),
},
},
@ -382,7 +382,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
failure := tainted || err != nil
return createBeforeDestroyEnabled && failure, nil
},
Node: &EvalUndeposeState{
Then: &EvalUndeposeState{
Name: n.stateId(),
},
},
@ -395,14 +395,35 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
Diff: nil,
},
&EvalWriteState{
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
return tainted, nil
},
Then: &EvalSequence{
Nodes: []EvalNode{
&EvalWriteStateTainted{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Dependencies: n.DependentOn(),
State: &state,
Tainted: &tainted,
TaintedIndex: -1,
TaintedClearPrimary: !n.Resource.Lifecycle.CreateBeforeDestroy,
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{
Info: info,
@ -480,18 +501,26 @@ func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
return true, EvalEarlyExitError{}
},
Node: EvalNoop{},
Then: EvalNoop{},
},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
return n.Resource.Lifecycle.CreateBeforeDestroy, nil
},
Then: &EvalReadStateTainted{
Name: n.stateId(),
Output: &state,
Tainted: n.Resource.Lifecycle.CreateBeforeDestroy,
TaintedIndex: -1,
Index: -1,
},
Else: &EvalReadState{
Name: n.stateId(),
Output: &state,
},
},
&EvalRequireState{
State: &state,

View File

@ -4,7 +4,7 @@ import (
"fmt"
)
// TraintedTransformer is a GraphTransformer that adds tainted resources
// TaintedTransformer is a GraphTransformer that adds tainted resources
// to the graph.
type TaintedTransformer struct {
// 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
// to find tainted resources.
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 {
@ -43,17 +37,6 @@ func (t *TaintedTransformer) Transform(g *Graph) error {
}
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 {
// Add the graph node and make the connection from any untainted
// resources with this name to the tainted resource, so that
@ -88,7 +71,6 @@ func (n *graphNodeTaintedResource) ProvidedBy() []string {
func (n *graphNodeTaintedResource) EvalTree() EvalNode {
var provider ResourceProvider
var state *InstanceState
tainted := true
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
@ -105,10 +87,9 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
&EvalReadStateTainted{
Name: n.ResourceName,
Tainted: true,
TaintedIndex: n.Index,
Index: n.Index,
Output: &state,
},
&EvalRefresh{
@ -117,12 +98,11 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
State: &state,
Output: &state,
},
&EvalWriteState{
&EvalWriteStateTainted{
Name: n.ResourceName,
ResourceType: n.ResourceType,
State: &state,
Tainted: &tainted,
TaintedIndex: n.Index,
Index: n.Index,
},
},
},
@ -138,10 +118,9 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
&EvalReadStateTainted{
Name: n.ResourceName,
Tainted: true,
TaintedIndex: n.Index,
Index: n.Index,
Output: &state,
},
&EvalDiffDestroy{
@ -156,12 +135,11 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
Provider: &provider,
Output: &state,
},
&EvalWriteState{
&EvalWriteStateTainted{
Name: n.ResourceName,
ResourceType: n.ResourceType,
State: &state,
Tainted: &tainted,
TaintedIndex: n.Index,
Index: n.Index,
},
&EvalUpdateStateHook{},
},

View File

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

View File

@ -31,11 +31,10 @@ Available commands are:
init Initializes Terraform configuration from a module
output Read an output from a state file
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
remote Configures remote state management
remote Configure remote state storage
show Inspect Terraform state or plan
taint Manually mark a resource for recreation
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