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:
commit
ddc2d8de2e
|
@ -10,6 +10,7 @@ install: make updatedeps
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- go test ./...
|
- go test ./...
|
||||||
|
- make vet
|
||||||
#- go test -race ./...
|
#- go test -race ./...
|
||||||
|
|
||||||
branches:
|
branches:
|
||||||
|
|
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -1,5 +1,12 @@
|
||||||
## 0.4.0 (unreleased)
|
## 0.4.0 (unreleased)
|
||||||
|
|
||||||
|
BACKWARDS INCOMPATIBILITIES:
|
||||||
|
|
||||||
|
* Commands `terraform push` and `terraform pull` are now nested under
|
||||||
|
the `remote` command: `terraform remote push` and `terraform remote pull`.
|
||||||
|
The old `remote` functionality is now at `terraform remote config`. This
|
||||||
|
consolidates all remote state management under one command.
|
||||||
|
|
||||||
FEATURES:
|
FEATURES:
|
||||||
|
|
||||||
* **New provider: `dme` (DNSMadeEasy)** [GH-855]
|
* **New provider: `dme` (DNSMadeEasy)** [GH-855]
|
||||||
|
@ -16,11 +23,14 @@ FEATURES:
|
||||||
|
|
||||||
IMPROVEMENTS:
|
IMPROVEMENTS:
|
||||||
|
|
||||||
|
* **New config function: `format`** - Format a string using `sprintf`
|
||||||
|
format. [GH-1096]
|
||||||
* **New config function: `replace`** - Search and replace string values.
|
* **New config function: `replace`** - Search and replace string values.
|
||||||
Search can be a regular expression. See documentation for more
|
Search can be a regular expression. See documentation for more
|
||||||
info. [GH-1029]
|
info. [GH-1029]
|
||||||
* **New config function: `split`** - Split a value based on a delimiter.
|
* **New config function: `split`** - Split a value based on a delimiter.
|
||||||
This is useful for faking lists as parameters to modules.
|
This is useful for faking lists as parameters to modules.
|
||||||
|
* **New resource: `digitalocean_ssh_key`** [GH-1074]
|
||||||
* core: The serial of the state is only updated if there is an actual
|
* core: The serial of the state is only updated if there is an actual
|
||||||
change. This will lower the amount of state changing on things
|
change. This will lower the amount of state changing on things
|
||||||
like refresh.
|
like refresh.
|
||||||
|
@ -39,9 +49,16 @@ BUG FIXES:
|
||||||
"resource.0" would ignore the latter completely. [GH-1086]
|
"resource.0" would ignore the latter completely. [GH-1086]
|
||||||
* providers/aws: manually deleted VPC removes it from the state
|
* providers/aws: manually deleted VPC removes it from the state
|
||||||
* providers/aws: `source_dest_check` regression fixed (now works). [GH-1020]
|
* providers/aws: `source_dest_check` regression fixed (now works). [GH-1020]
|
||||||
|
* providers/aws: Longer wait times for DB instances.
|
||||||
|
* providers/aws: Longer wait times for route53 records (30 mins). [GH-1164]
|
||||||
* providers/digitalocean: Waits until droplet is ready to be destroyed [GH-1057]
|
* providers/digitalocean: Waits until droplet is ready to be destroyed [GH-1057]
|
||||||
* providers/digitalocean: More lenient about 404's while waiting [GH-1062]
|
* providers/digitalocean: More lenient about 404's while waiting [GH-1062]
|
||||||
* providers/aws: Longer wait times for DB instances
|
* providers/google: Network data in state was not being stored. [GH-1095]
|
||||||
|
|
||||||
|
PLUGIN CHANGES:
|
||||||
|
|
||||||
|
* New `helper/schema` fields for resources: `Deprecated` and `Removed` allow
|
||||||
|
plugins to generate warning or error messages when a given attribute is used.
|
||||||
|
|
||||||
## 0.3.7 (February 19, 2015)
|
## 0.3.7 (February 19, 2015)
|
||||||
|
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -12,6 +12,9 @@ bin: generate
|
||||||
dev: generate
|
dev: generate
|
||||||
@TF_DEV=1 sh -c "'$(CURDIR)/scripts/build.sh'"
|
@TF_DEV=1 sh -c "'$(CURDIR)/scripts/build.sh'"
|
||||||
|
|
||||||
|
quickdev: generate
|
||||||
|
@TF_QUICKDEV=1 TF_DEV=1 sh -c "'$(CURDIR)/scripts/build.sh'"
|
||||||
|
|
||||||
# test runs the unit tests and vets the code
|
# test runs the unit tests and vets the code
|
||||||
test: generate
|
test: generate
|
||||||
TF_ACC= go test $(TEST) $(TESTARGS) -timeout=30s -parallel=4
|
TF_ACC= go test $(TEST) $(TESTARGS) -timeout=30s -parallel=4
|
||||||
|
|
|
@ -2,11 +2,14 @@ package aws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func expandNetworkAclEntries(configured []interface{}, entryType string) ([]ec2.NetworkAclEntry, error) {
|
func expandNetworkAclEntries(configured []interface{}, entryType string) ([]ec2.NetworkACLEntry, error) {
|
||||||
entries := make([]ec2.NetworkAclEntry, 0, len(configured))
|
entries := make([]ec2.NetworkACLEntry, 0, len(configured))
|
||||||
for _, eRaw := range configured {
|
for _, eRaw := range configured {
|
||||||
data := eRaw.(map[string]interface{})
|
data := eRaw.(map[string]interface{})
|
||||||
protocol := data["protocol"].(string)
|
protocol := data["protocol"].(string)
|
||||||
|
@ -15,16 +18,16 @@ func expandNetworkAclEntries(configured []interface{}, entryType string) ([]ec2.
|
||||||
return nil, fmt.Errorf("Invalid Protocol %s for rule %#v", protocol, data)
|
return nil, fmt.Errorf("Invalid Protocol %s for rule %#v", protocol, data)
|
||||||
}
|
}
|
||||||
p := extractProtocolInteger(data["protocol"].(string))
|
p := extractProtocolInteger(data["protocol"].(string))
|
||||||
e := ec2.NetworkAclEntry{
|
e := ec2.NetworkACLEntry{
|
||||||
Protocol: p,
|
Protocol: aws.String(strconv.Itoa(p)),
|
||||||
PortRange: ec2.PortRange{
|
PortRange: &ec2.PortRange{
|
||||||
From: data["from_port"].(int),
|
From: aws.Integer(data["from_port"].(int)),
|
||||||
To: data["to_port"].(int),
|
To: aws.Integer(data["to_port"].(int)),
|
||||||
},
|
},
|
||||||
Egress: (entryType == "egress"),
|
Egress: aws.Boolean((entryType == "egress")),
|
||||||
RuleAction: data["action"].(string),
|
RuleAction: aws.String(data["action"].(string)),
|
||||||
RuleNumber: data["rule_no"].(int),
|
RuleNumber: aws.Integer(data["rule_no"].(int)),
|
||||||
CidrBlock: data["cidr_block"].(string),
|
CIDRBlock: aws.String(data["cidr_block"].(string)),
|
||||||
}
|
}
|
||||||
entries = append(entries, e)
|
entries = append(entries, e)
|
||||||
}
|
}
|
||||||
|
@ -33,17 +36,17 @@ func expandNetworkAclEntries(configured []interface{}, entryType string) ([]ec2.
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func flattenNetworkAclEntries(list []ec2.NetworkAclEntry) []map[string]interface{} {
|
func flattenNetworkAclEntries(list []ec2.NetworkACLEntry) []map[string]interface{} {
|
||||||
entries := make([]map[string]interface{}, 0, len(list))
|
entries := make([]map[string]interface{}, 0, len(list))
|
||||||
|
|
||||||
for _, entry := range list {
|
for _, entry := range list {
|
||||||
entries = append(entries, map[string]interface{}{
|
entries = append(entries, map[string]interface{}{
|
||||||
"from_port": entry.PortRange.From,
|
"from_port": *entry.PortRange.From,
|
||||||
"to_port": entry.PortRange.To,
|
"to_port": *entry.PortRange.To,
|
||||||
"action": entry.RuleAction,
|
"action": *entry.RuleAction,
|
||||||
"rule_no": entry.RuleNumber,
|
"rule_no": *entry.RuleNumber,
|
||||||
"protocol": extractProtocolString(entry.Protocol),
|
"protocol": *entry.Protocol,
|
||||||
"cidr_block": entry.CidrBlock,
|
"cidr_block": *entry.CIDRBlock,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return entries
|
return entries
|
||||||
|
|
|
@ -4,10 +4,11 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mitchellh/goamz/ec2"
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_expandNetworkAclEntry(t *testing.T) {
|
func Test_expandNetworkACLEntry(t *testing.T) {
|
||||||
input := []interface{}{
|
input := []interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"protocol": "tcp",
|
"protocol": "tcp",
|
||||||
|
@ -28,30 +29,28 @@ func Test_expandNetworkAclEntry(t *testing.T) {
|
||||||
}
|
}
|
||||||
expanded, _ := expandNetworkAclEntries(input, "egress")
|
expanded, _ := expandNetworkAclEntries(input, "egress")
|
||||||
|
|
||||||
expected := []ec2.NetworkAclEntry{
|
expected := []ec2.NetworkACLEntry{
|
||||||
ec2.NetworkAclEntry{
|
ec2.NetworkACLEntry{
|
||||||
Protocol: 6,
|
Protocol: aws.String("6"),
|
||||||
PortRange: ec2.PortRange{
|
PortRange: &ec2.PortRange{
|
||||||
From: 22,
|
From: aws.Integer(22),
|
||||||
To: 22,
|
To: aws.Integer(22),
|
||||||
},
|
},
|
||||||
RuleAction: "deny",
|
RuleAction: aws.String("deny"),
|
||||||
RuleNumber: 1,
|
RuleNumber: aws.Integer(1),
|
||||||
CidrBlock: "0.0.0.0/0",
|
CIDRBlock: aws.String("0.0.0.0/0"),
|
||||||
Egress: true,
|
Egress: aws.Boolean(true),
|
||||||
IcmpCode: ec2.IcmpCode{Code: 0, Type: 0},
|
|
||||||
},
|
},
|
||||||
ec2.NetworkAclEntry{
|
ec2.NetworkACLEntry{
|
||||||
Protocol: 6,
|
Protocol: aws.String("6"),
|
||||||
PortRange: ec2.PortRange{
|
PortRange: &ec2.PortRange{
|
||||||
From: 443,
|
From: aws.Integer(443),
|
||||||
To: 443,
|
To: aws.Integer(443),
|
||||||
},
|
},
|
||||||
RuleAction: "deny",
|
RuleAction: aws.String("deny"),
|
||||||
RuleNumber: 2,
|
RuleNumber: aws.Integer(2),
|
||||||
CidrBlock: "0.0.0.0/0",
|
CIDRBlock: aws.String("0.0.0.0/0"),
|
||||||
Egress: true,
|
Egress: aws.Boolean(true),
|
||||||
IcmpCode: ec2.IcmpCode{Code: 0, Type: 0},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,28 +63,28 @@ func Test_expandNetworkAclEntry(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_flattenNetworkAclEntry(t *testing.T) {
|
func Test_flattenNetworkACLEntry(t *testing.T) {
|
||||||
|
|
||||||
apiInput := []ec2.NetworkAclEntry{
|
apiInput := []ec2.NetworkACLEntry{
|
||||||
ec2.NetworkAclEntry{
|
ec2.NetworkACLEntry{
|
||||||
Protocol: 6,
|
Protocol: aws.String("tcp"),
|
||||||
PortRange: ec2.PortRange{
|
PortRange: &ec2.PortRange{
|
||||||
From: 22,
|
From: aws.Integer(22),
|
||||||
To: 22,
|
To: aws.Integer(22),
|
||||||
},
|
},
|
||||||
RuleAction: "deny",
|
RuleAction: aws.String("deny"),
|
||||||
RuleNumber: 1,
|
RuleNumber: aws.Integer(1),
|
||||||
CidrBlock: "0.0.0.0/0",
|
CIDRBlock: aws.String("0.0.0.0/0"),
|
||||||
},
|
},
|
||||||
ec2.NetworkAclEntry{
|
ec2.NetworkACLEntry{
|
||||||
Protocol: 6,
|
Protocol: aws.String("tcp"),
|
||||||
PortRange: ec2.PortRange{
|
PortRange: &ec2.PortRange{
|
||||||
From: 443,
|
From: aws.Integer(443),
|
||||||
To: 443,
|
To: aws.Integer(443),
|
||||||
},
|
},
|
||||||
RuleAction: "deny",
|
RuleAction: aws.String("deny"),
|
||||||
RuleNumber: 2,
|
RuleNumber: aws.Integer(2),
|
||||||
CidrBlock: "0.0.0.0/0",
|
CIDRBlock: aws.String("0.0.0.0/0"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
flattened := flattenNetworkAclEntries(apiInput)
|
flattened := flattenNetworkAclEntries(apiInput)
|
||||||
|
|
|
@ -156,7 +156,6 @@ func resourceAwsDbInstance() *schema.Resource {
|
||||||
"final_snapshot_identifier": &schema.Schema{
|
"final_snapshot_identifier": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"db_subnet_group_name": &schema.Schema{
|
"db_subnet_group_name": &schema.Schema{
|
||||||
|
@ -324,7 +323,7 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
d.Set("parameter_group_name", *v.DBParameterGroups[0].DBParameterGroupName)
|
d.Set("parameter_group_name", *v.DBParameterGroups[0].DBParameterGroupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Set("address", *v.Endpoint.Port)
|
d.Set("address", *v.Endpoint.Address)
|
||||||
d.Set("endpoint", fmt.Sprintf("%s:%d", *v.Endpoint.Address, *v.Endpoint.Port))
|
d.Set("endpoint", fmt.Sprintf("%s:%d", *v.Endpoint.Address, *v.Endpoint.Port))
|
||||||
d.Set("status", *v.DBInstanceStatus)
|
d.Set("status", *v.DBInstanceStatus)
|
||||||
d.Set("storage_encrypted", *v.StorageEncrypted)
|
d.Set("storage_encrypted", *v.StorageEncrypted)
|
||||||
|
|
|
@ -6,9 +6,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceAwsEip() *schema.Resource {
|
func resourceAwsEip() *schema.Resource {
|
||||||
|
@ -59,7 +60,7 @@ func resourceAwsEip() *schema.Resource {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
// By default, we're not in a VPC
|
// By default, we're not in a VPC
|
||||||
domainOpt := ""
|
domainOpt := ""
|
||||||
|
@ -67,12 +68,12 @@ func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
domainOpt = "vpc"
|
domainOpt = "vpc"
|
||||||
}
|
}
|
||||||
|
|
||||||
allocOpts := ec2.AllocateAddress{
|
allocOpts := &ec2.AllocateAddressRequest{
|
||||||
Domain: domainOpt,
|
Domain: aws.String(domainOpt),
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] EIP create configuration: %#v", allocOpts)
|
log.Printf("[DEBUG] EIP create configuration: %#v", allocOpts)
|
||||||
allocResp, err := ec2conn.AllocateAddress(&allocOpts)
|
allocResp, err := ec2conn.AllocateAddress(allocOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error creating EIP: %s", err)
|
return fmt.Errorf("Error creating EIP: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -86,17 +87,17 @@ func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
// it defaults to using the public IP
|
// it defaults to using the public IP
|
||||||
log.Printf("[DEBUG] EIP Allocate: %#v", allocResp)
|
log.Printf("[DEBUG] EIP Allocate: %#v", allocResp)
|
||||||
if d.Get("domain").(string) == "vpc" {
|
if d.Get("domain").(string) == "vpc" {
|
||||||
d.SetId(allocResp.AllocationId)
|
d.SetId(*allocResp.AllocationID)
|
||||||
} else {
|
} else {
|
||||||
d.SetId(allocResp.PublicIp)
|
d.SetId(*allocResp.PublicIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] EIP ID: %s (domain: %v)", d.Id(), allocResp.Domain)
|
log.Printf("[INFO] EIP ID: %s (domain: %v)", d.Id(), *allocResp.Domain)
|
||||||
return resourceAwsEipUpdate(d, meta)
|
return resourceAwsEipUpdate(d, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
domain := resourceAwsEipDomain(d)
|
domain := resourceAwsEipDomain(d)
|
||||||
id := d.Id()
|
id := d.Id()
|
||||||
|
@ -113,9 +114,13 @@ func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
"[DEBUG] EIP describe configuration: %#v, %#v (domain: %s)",
|
"[DEBUG] EIP describe configuration: %#v, %#v (domain: %s)",
|
||||||
assocIds, publicIps, domain)
|
assocIds, publicIps, domain)
|
||||||
|
|
||||||
describeAddresses, err := ec2conn.Addresses(publicIps, assocIds, nil)
|
req := &ec2.DescribeAddressesRequest{
|
||||||
|
AllocationIDs: assocIds,
|
||||||
|
PublicIPs: publicIps,
|
||||||
|
}
|
||||||
|
describeAddresses, err := ec2conn.DescribeAddresses(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAllocationID.NotFound" {
|
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidAllocationID.NotFound" {
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -125,8 +130,8 @@ func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
// Verify AWS returned our EIP
|
// Verify AWS returned our EIP
|
||||||
if len(describeAddresses.Addresses) != 1 ||
|
if len(describeAddresses.Addresses) != 1 ||
|
||||||
describeAddresses.Addresses[0].AllocationId != id ||
|
*describeAddresses.Addresses[0].AllocationID != id ||
|
||||||
describeAddresses.Addresses[0].PublicIp != id {
|
*describeAddresses.Addresses[0].PublicIP != id {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to find EIP: %#v", describeAddresses.Addresses)
|
return fmt.Errorf("Unable to find EIP: %#v", describeAddresses.Addresses)
|
||||||
}
|
}
|
||||||
|
@ -134,16 +139,16 @@ func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
address := describeAddresses.Addresses[0]
|
address := describeAddresses.Addresses[0]
|
||||||
|
|
||||||
d.Set("association_id", address.AssociationId)
|
d.Set("association_id", address.AssociationID)
|
||||||
d.Set("instance", address.InstanceId)
|
d.Set("instance", address.InstanceID)
|
||||||
d.Set("public_ip", address.PublicIp)
|
d.Set("private_ip", address.PrivateIPAddress)
|
||||||
d.Set("private_ip", address.PrivateIpAddress)
|
d.Set("public_ip", address.PublicIP)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
domain := resourceAwsEipDomain(d)
|
domain := resourceAwsEipDomain(d)
|
||||||
|
|
||||||
|
@ -151,22 +156,22 @@ func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
if v, ok := d.GetOk("instance"); ok {
|
if v, ok := d.GetOk("instance"); ok {
|
||||||
instanceId := v.(string)
|
instanceId := v.(string)
|
||||||
|
|
||||||
assocOpts := ec2.AssociateAddress{
|
assocOpts := &ec2.AssociateAddressRequest{
|
||||||
InstanceId: instanceId,
|
InstanceID: aws.String(instanceId),
|
||||||
PublicIp: d.Id(),
|
PublicIP: aws.String(d.Id()),
|
||||||
}
|
}
|
||||||
|
|
||||||
// more unique ID conditionals
|
// more unique ID conditionals
|
||||||
if domain == "vpc" {
|
if domain == "vpc" {
|
||||||
assocOpts = ec2.AssociateAddress{
|
assocOpts = &ec2.AssociateAddressRequest{
|
||||||
InstanceId: instanceId,
|
InstanceID: aws.String(instanceId),
|
||||||
AllocationId: d.Id(),
|
AllocationID: aws.String(d.Id()),
|
||||||
PublicIp: "",
|
PublicIP: aws.String(""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] EIP associate configuration: %#v (domain: %v)", assocOpts, domain)
|
log.Printf("[DEBUG] EIP associate configuration: %#v (domain: %v)", assocOpts, domain)
|
||||||
_, err := ec2conn.AssociateAddress(&assocOpts)
|
_, err := ec2conn.AssociateAddress(assocOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failure associating instances: %s", err)
|
return fmt.Errorf("Failure associating instances: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -176,7 +181,7 @@ func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
if err := resourceAwsEipRead(d, meta); err != nil {
|
if err := resourceAwsEipRead(d, meta); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -192,9 +197,13 @@ func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
var err error
|
var err error
|
||||||
switch resourceAwsEipDomain(d) {
|
switch resourceAwsEipDomain(d) {
|
||||||
case "vpc":
|
case "vpc":
|
||||||
_, err = ec2conn.DisassociateAddress(d.Get("association_id").(string))
|
err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressRequest{
|
||||||
|
AssociationID: aws.String(d.Get("association_id").(string)),
|
||||||
|
})
|
||||||
case "standard":
|
case "standard":
|
||||||
_, err = ec2conn.DisassociateAddressClassic(d.Get("public_ip").(string))
|
err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressRequest{
|
||||||
|
PublicIP: aws.String(d.Get("public_ip").(string)),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -209,16 +218,20 @@ func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"[DEBUG] EIP release (destroy) address allocation: %v",
|
"[DEBUG] EIP release (destroy) address allocation: %v",
|
||||||
d.Id())
|
d.Id())
|
||||||
_, err = ec2conn.ReleaseAddress(d.Id())
|
err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressRequest{
|
||||||
|
AllocationID: aws.String(d.Id()),
|
||||||
|
})
|
||||||
case "standard":
|
case "standard":
|
||||||
log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id())
|
log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id())
|
||||||
_, err = ec2conn.ReleasePublicAddress(d.Id())
|
err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressRequest{
|
||||||
|
PublicIP: aws.String(d.Id()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if _, ok := err.(*ec2.Error); !ok {
|
if _, ok := err.(aws.APIError); !ok {
|
||||||
return resource.RetryError{Err: err}
|
return resource.RetryError{Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAWSEIP_normal(t *testing.T) {
|
func TestAccAWSEIP_normal(t *testing.T) {
|
||||||
|
@ -57,24 +58,28 @@ func TestAccAWSEIP_instance(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckAWSEIPDestroy(s *terraform.State) error {
|
func testAccCheckAWSEIPDestroy(s *terraform.State) error {
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
if rs.Type != "aws_eip" {
|
if rs.Type != "aws_eip" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
describe, err := conn.Addresses([]string{rs.Primary.ID}, []string{}, nil)
|
req := &ec2.DescribeAddressesRequest{
|
||||||
|
AllocationIDs: []string{},
|
||||||
|
PublicIPs: []string{rs.Primary.ID},
|
||||||
|
}
|
||||||
|
describe, err := conn.DescribeAddresses(req)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if len(describe.Addresses) != 0 &&
|
if len(describe.Addresses) != 0 &&
|
||||||
describe.Addresses[0].PublicIp == rs.Primary.ID {
|
*describe.Addresses[0].PublicIP == rs.Primary.ID {
|
||||||
return fmt.Errorf("EIP still exists")
|
return fmt.Errorf("EIP still exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the error
|
// Verify the error
|
||||||
providerErr, ok := err.(*ec2.Error)
|
providerErr, ok := err.(aws.APIError)
|
||||||
if !ok {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -89,7 +94,7 @@ func testAccCheckAWSEIPDestroy(s *terraform.State) error {
|
||||||
|
|
||||||
func testAccCheckAWSEIPAttributes(conf *ec2.Address) resource.TestCheckFunc {
|
func testAccCheckAWSEIPAttributes(conf *ec2.Address) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
if conf.PublicIp == "" {
|
if *conf.PublicIP == "" {
|
||||||
return fmt.Errorf("empty public_ip")
|
return fmt.Errorf("empty public_ip")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,28 +113,36 @@ func testAccCheckAWSEIPExists(n string, res *ec2.Address) resource.TestCheckFunc
|
||||||
return fmt.Errorf("No EIP ID is set")
|
return fmt.Errorf("No EIP ID is set")
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
if strings.Contains(rs.Primary.ID, "eipalloc") {
|
if strings.Contains(rs.Primary.ID, "eipalloc") {
|
||||||
describe, err := conn.Addresses([]string{}, []string{rs.Primary.ID}, nil)
|
req := &ec2.DescribeAddressesRequest{
|
||||||
|
AllocationIDs: []string{rs.Primary.ID},
|
||||||
|
PublicIPs: []string{},
|
||||||
|
}
|
||||||
|
describe, err := conn.DescribeAddresses(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(describe.Addresses) != 1 ||
|
if len(describe.Addresses) != 1 ||
|
||||||
describe.Addresses[0].AllocationId != rs.Primary.ID {
|
*describe.Addresses[0].AllocationID != rs.Primary.ID {
|
||||||
return fmt.Errorf("EIP not found")
|
return fmt.Errorf("EIP not found")
|
||||||
}
|
}
|
||||||
*res = describe.Addresses[0]
|
*res = describe.Addresses[0]
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
describe, err := conn.Addresses([]string{rs.Primary.ID}, []string{}, nil)
|
req := &ec2.DescribeAddressesRequest{
|
||||||
|
AllocationIDs: []string{},
|
||||||
|
PublicIPs: []string{rs.Primary.ID},
|
||||||
|
}
|
||||||
|
describe, err := conn.DescribeAddresses(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(describe.Addresses) != 1 ||
|
if len(describe.Addresses) != 1 ||
|
||||||
describe.Addresses[0].PublicIp != rs.Primary.ID {
|
*describe.Addresses[0].PublicIP != rs.Primary.ID {
|
||||||
return fmt.Errorf("EIP not found")
|
return fmt.Errorf("EIP not found")
|
||||||
}
|
}
|
||||||
*res = describe.Addresses[0]
|
*res = describe.Addresses[0]
|
||||||
|
|
|
@ -6,14 +6,14 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/hashcode"
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceAwsInstance() *schema.Resource {
|
func resourceAwsInstance() *schema.Resource {
|
||||||
|
@ -186,6 +186,13 @@ func resourceAwsInstance() *schema.Resource {
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"iops": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Set: resourceAwsInstanceBlockDevicesHash,
|
Set: resourceAwsInstanceBlockDevicesHash,
|
||||||
|
@ -231,6 +238,13 @@ func resourceAwsInstance() *schema.Resource {
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"iops": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -239,7 +253,7 @@ func resourceAwsInstance() *schema.Resource {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
// Figure out user data
|
// Figure out user data
|
||||||
userData := ""
|
userData := ""
|
||||||
|
@ -247,38 +261,84 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
userData = v.(string)
|
userData = v.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
placement := &ec2.Placement{
|
||||||
|
AvailabilityZone: aws.String(d.Get("availability_zone").(string)),
|
||||||
|
Tenancy: aws.String(d.Get("tenancy").(string)),
|
||||||
|
}
|
||||||
|
|
||||||
|
iam := &ec2.IAMInstanceProfileSpecification{
|
||||||
|
Name: aws.String(d.Get("iam_instance_profile").(string)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the creation struct
|
||||||
|
runOpts := &ec2.RunInstancesRequest{
|
||||||
|
ImageID: aws.String(d.Get("ami").(string)),
|
||||||
|
Placement: placement,
|
||||||
|
InstanceType: aws.String(d.Get("instance_type").(string)),
|
||||||
|
MaxCount: aws.Integer(1),
|
||||||
|
MinCount: aws.Integer(1),
|
||||||
|
UserData: aws.String(userData),
|
||||||
|
EBSOptimized: aws.Boolean(d.Get("ebs_optimized").(bool)),
|
||||||
|
IAMInstanceProfile: iam,
|
||||||
|
}
|
||||||
|
|
||||||
associatePublicIPAddress := false
|
associatePublicIPAddress := false
|
||||||
if v := d.Get("associate_public_ip_address"); v != nil {
|
if v := d.Get("associate_public_ip_address"); v != nil {
|
||||||
associatePublicIPAddress = v.(bool)
|
associatePublicIPAddress = v.(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the creation struct
|
// check for non-default Subnet, and cast it to a String
|
||||||
runOpts := &ec2.RunInstances{
|
var hasSubnet bool
|
||||||
ImageId: d.Get("ami").(string),
|
subnet, hasSubnet := d.GetOk("subnet_id")
|
||||||
AvailZone: d.Get("availability_zone").(string),
|
subnetID := subnet.(string)
|
||||||
InstanceType: d.Get("instance_type").(string),
|
|
||||||
KeyName: d.Get("key_name").(string),
|
if hasSubnet && associatePublicIPAddress {
|
||||||
SubnetId: d.Get("subnet_id").(string),
|
// If we have a non-default VPC / Subnet specified, we can flag
|
||||||
PrivateIPAddress: d.Get("private_ip").(string),
|
// AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided.
|
||||||
AssociatePublicIpAddress: associatePublicIPAddress,
|
// You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise
|
||||||
UserData: []byte(userData),
|
// you get: Network interfaces and an instance-level subnet ID may not be specified on the same request
|
||||||
EbsOptimized: d.Get("ebs_optimized").(bool),
|
// You also need to attach Security Groups to the NetworkInterface instead of the instance,
|
||||||
IamInstanceProfile: d.Get("iam_instance_profile").(string),
|
// to avoid: Network interfaces and an instance-level security groups may not be specified on
|
||||||
Tenancy: d.Get("tenancy").(string),
|
// the same request
|
||||||
|
ni := ec2.InstanceNetworkInterfaceSpecification{
|
||||||
|
AssociatePublicIPAddress: aws.Boolean(associatePublicIPAddress),
|
||||||
|
DeviceIndex: aws.Integer(0),
|
||||||
|
SubnetID: aws.String(subnetID),
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := d.GetOk("private_ip"); ok {
|
||||||
|
ni.PrivateIPAddress = aws.String(v.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
runOpts.NetworkInterfaces = []ec2.InstanceNetworkInterfaceSpecification{ni}
|
||||||
|
} else {
|
||||||
|
if subnetID != "" {
|
||||||
|
runOpts.SubnetID = aws.String(subnetID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := d.GetOk("private_ip"); ok {
|
||||||
|
runOpts.PrivateIPAddress = aws.String(v.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := d.GetOk("key_name"); ok {
|
||||||
|
runOpts.KeyName = aws.String(v.(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := d.Get("security_groups"); v != nil {
|
if v := d.Get("security_groups"); v != nil {
|
||||||
|
// Security group names.
|
||||||
|
// For a nondefault VPC, you must use security group IDs instead.
|
||||||
|
// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html
|
||||||
|
var groups []string
|
||||||
for _, v := range v.(*schema.Set).List() {
|
for _, v := range v.(*schema.Set).List() {
|
||||||
str := v.(string)
|
str := v.(string)
|
||||||
|
groups = append(groups, str)
|
||||||
var g ec2.SecurityGroup
|
|
||||||
if runOpts.SubnetId != "" {
|
|
||||||
g.Id = str
|
|
||||||
} else {
|
|
||||||
g.Name = str
|
|
||||||
}
|
}
|
||||||
|
if runOpts.SubnetID != nil &&
|
||||||
runOpts.SecurityGroups = append(runOpts.SecurityGroups, g)
|
*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 {
|
if len(blockDevices) > 0 {
|
||||||
runOpts.BlockDevices = make([]ec2.BlockDeviceMapping, len(blockDevices))
|
runOpts.BlockDeviceMappings = make([]ec2.BlockDeviceMapping, len(blockDevices))
|
||||||
for i, v := range blockDevices {
|
for i, v := range blockDevices {
|
||||||
bd := v.(map[string]interface{})
|
bd := v.(map[string]interface{})
|
||||||
runOpts.BlockDevices[i].DeviceName = bd["device_name"].(string)
|
runOpts.BlockDeviceMappings[i].DeviceName = aws.String(bd["device_name"].(string))
|
||||||
runOpts.BlockDevices[i].VolumeType = bd["volume_type"].(string)
|
runOpts.BlockDeviceMappings[i].EBS = &ec2.EBSBlockDevice{
|
||||||
runOpts.BlockDevices[i].VolumeSize = int64(bd["volume_size"].(int))
|
VolumeType: aws.String(bd["volume_type"].(string)),
|
||||||
runOpts.BlockDevices[i].DeleteOnTermination = bd["delete_on_termination"].(bool)
|
VolumeSize: aws.Integer(bd["volume_size"].(int)),
|
||||||
if v, ok := bd["virtual_name"].(string); ok {
|
DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
|
||||||
runOpts.BlockDevices[i].VirtualName = v
|
|
||||||
}
|
}
|
||||||
if v, ok := bd["snapshot_id"].(string); ok {
|
|
||||||
runOpts.BlockDevices[i].SnapshotId = v
|
if v, ok := bd["virtual_name"].(string); ok {
|
||||||
|
runOpts.BlockDeviceMappings[i].VirtualName = aws.String(v)
|
||||||
|
}
|
||||||
|
if v, ok := bd["snapshot_id"].(string); ok && v != "" {
|
||||||
|
runOpts.BlockDeviceMappings[i].EBS.SnapshotID = aws.String(v)
|
||||||
}
|
}
|
||||||
if v, ok := bd["encrypted"].(bool); ok {
|
if v, ok := bd["encrypted"].(bool); ok {
|
||||||
runOpts.BlockDevices[i].Encrypted = v
|
runOpts.BlockDeviceMappings[i].EBS.Encrypted = aws.Boolean(v)
|
||||||
|
}
|
||||||
|
if v, ok := bd["iops"].(int); ok && v > 0 {
|
||||||
|
runOpts.BlockDeviceMappings[i].EBS.IOPS = aws.Integer(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,21 +390,21 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
instance := &runResp.Instances[0]
|
instance := &runResp.Instances[0]
|
||||||
log.Printf("[INFO] Instance ID: %s", instance.InstanceId)
|
log.Printf("[INFO] Instance ID: %s", *instance.InstanceID)
|
||||||
|
|
||||||
// Store the resulting ID so we can look this up later
|
// Store the resulting ID so we can look this up later
|
||||||
d.SetId(instance.InstanceId)
|
d.SetId(*instance.InstanceID)
|
||||||
|
|
||||||
// Wait for the instance to become running so we can get some attributes
|
// Wait for the instance to become running so we can get some attributes
|
||||||
// that aren't available until later.
|
// that aren't available until later.
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"[DEBUG] Waiting for instance (%s) to become running",
|
"[DEBUG] Waiting for instance (%s) to become running",
|
||||||
instance.InstanceId)
|
*instance.InstanceID)
|
||||||
|
|
||||||
stateConf := &resource.StateChangeConf{
|
stateConf := &resource.StateChangeConf{
|
||||||
Pending: []string{"pending"},
|
Pending: []string{"pending"},
|
||||||
Target: "running",
|
Target: "running",
|
||||||
Refresh: InstanceStateRefreshFunc(ec2conn, instance.InstanceId),
|
Refresh: InstanceStateRefreshFunc(ec2conn, *instance.InstanceID),
|
||||||
Timeout: 10 * time.Minute,
|
Timeout: 10 * time.Minute,
|
||||||
Delay: 10 * time.Second,
|
Delay: 10 * time.Second,
|
||||||
MinTimeout: 3 * time.Second,
|
MinTimeout: 3 * time.Second,
|
||||||
|
@ -348,16 +414,18 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Error waiting for instance (%s) to become ready: %s",
|
"Error waiting for instance (%s) to become ready: %s",
|
||||||
instance.InstanceId, err)
|
*instance.InstanceID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
instance = instanceRaw.(*ec2.Instance)
|
instance = instanceRaw.(*ec2.Instance)
|
||||||
|
|
||||||
// Initialize the connection info
|
// Initialize the connection info
|
||||||
|
if instance.PublicIPAddress != nil {
|
||||||
d.SetConnInfo(map[string]string{
|
d.SetConnInfo(map[string]string{
|
||||||
"type": "ssh",
|
"type": "ssh",
|
||||||
"host": instance.PublicIpAddress,
|
"host": *instance.PublicIPAddress,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Set our attributes
|
// Set our attributes
|
||||||
if err := resourceAwsInstanceRead(d, meta); err != nil {
|
if err := resourceAwsInstanceRead(d, meta); err != nil {
|
||||||
|
@ -369,13 +437,15 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
resp, err := ec2conn.Instances([]string{d.Id()}, ec2.NewFilter())
|
resp, err := ec2conn.DescribeInstances(&ec2.DescribeInstancesRequest{
|
||||||
|
InstanceIDs: []string{d.Id()},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If the instance was not found, return nil so that we can show
|
// If the instance was not found, return nil so that we can show
|
||||||
// that the instance is gone.
|
// that the instance is gone.
|
||||||
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
|
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -393,28 +463,33 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
instance := &resp.Reservations[0].Instances[0]
|
instance := &resp.Reservations[0].Instances[0]
|
||||||
|
|
||||||
// If the instance is terminated, then it is gone
|
// If the instance is terminated, then it is gone
|
||||||
if instance.State.Name == "terminated" {
|
if *instance.State.Name == "terminated" {
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Set("availability_zone", instance.AvailZone)
|
d.Set("availability_zone", instance.Placement.AvailabilityZone)
|
||||||
d.Set("key_name", instance.KeyName)
|
d.Set("key_name", instance.KeyName)
|
||||||
d.Set("public_dns", instance.DNSName)
|
d.Set("public_dns", instance.PublicDNSName)
|
||||||
d.Set("public_ip", instance.PublicIpAddress)
|
d.Set("public_ip", instance.PublicIPAddress)
|
||||||
d.Set("private_dns", instance.PrivateDNSName)
|
d.Set("private_dns", instance.PrivateDNSName)
|
||||||
d.Set("private_ip", instance.PrivateIpAddress)
|
d.Set("private_ip", instance.PrivateIPAddress)
|
||||||
d.Set("subnet_id", instance.SubnetId)
|
d.Set("subnet_id", instance.SubnetID)
|
||||||
d.Set("ebs_optimized", instance.EbsOptimized)
|
if len(instance.NetworkInterfaces) > 0 {
|
||||||
d.Set("tags", tagsToMap(instance.Tags))
|
d.Set("subnet_id", instance.NetworkInterfaces[0].SubnetID)
|
||||||
d.Set("tenancy", instance.Tenancy)
|
} else {
|
||||||
|
d.Set("subnet_id", instance.SubnetID)
|
||||||
|
}
|
||||||
|
d.Set("ebs_optimized", instance.EBSOptimized)
|
||||||
|
d.Set("tags", tagsToMapSDK(instance.Tags))
|
||||||
|
d.Set("tenancy", instance.Placement.Tenancy)
|
||||||
|
|
||||||
// Determine whether we're referring to security groups with
|
// Determine whether we're referring to security groups with
|
||||||
// IDs or names. We use a heuristic to figure this out. By default,
|
// IDs or names. We use a heuristic to figure this out. By default,
|
||||||
// we use IDs if we're in a VPC. However, if we previously had an
|
// we use IDs if we're in a VPC. However, if we previously had an
|
||||||
// all-name list of security groups, we use names. Or, if we had any
|
// all-name list of security groups, we use names. Or, if we had any
|
||||||
// IDs, we use IDs.
|
// IDs, we use IDs.
|
||||||
useID := instance.SubnetId != ""
|
useID := *instance.SubnetID != ""
|
||||||
if v := d.Get("security_groups"); v != nil {
|
if v := d.Get("security_groups"); v != nil {
|
||||||
match := false
|
match := false
|
||||||
for _, v := range v.(*schema.Set).List() {
|
for _, v := range v.(*schema.Set).List() {
|
||||||
|
@ -431,24 +506,26 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
sgs := make([]string, len(instance.SecurityGroups))
|
sgs := make([]string, len(instance.SecurityGroups))
|
||||||
for i, sg := range instance.SecurityGroups {
|
for i, sg := range instance.SecurityGroups {
|
||||||
if useID {
|
if useID {
|
||||||
sgs[i] = sg.Id
|
sgs[i] = *sg.GroupID
|
||||||
} else {
|
} else {
|
||||||
sgs[i] = sg.Name
|
sgs[i] = *sg.GroupName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.Set("security_groups", sgs)
|
d.Set("security_groups", sgs)
|
||||||
|
|
||||||
blockDevices := make(map[string]ec2.BlockDevice)
|
blockDevices := make(map[string]ec2.InstanceBlockDeviceMapping)
|
||||||
for _, bd := range instance.BlockDevices {
|
for _, bd := range instance.BlockDeviceMappings {
|
||||||
blockDevices[bd.VolumeId] = bd
|
blockDevices[*bd.EBS.VolumeID] = bd
|
||||||
}
|
}
|
||||||
|
|
||||||
volIDs := make([]string, 0, len(blockDevices))
|
volIDs := make([]string, 0, len(blockDevices))
|
||||||
for volID := range blockDevices {
|
for _, vol := range blockDevices {
|
||||||
volIDs = append(volIDs, volID)
|
volIDs = append(volIDs, *vol.EBS.VolumeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
volResp, err := ec2conn.Volumes(volIDs, ec2.NewFilter())
|
volResp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesRequest{
|
||||||
|
VolumeIDs: volIDs,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -456,27 +533,25 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
nonRootBlockDevices := make([]map[string]interface{}, 0)
|
nonRootBlockDevices := make([]map[string]interface{}, 0)
|
||||||
rootBlockDevice := make([]interface{}, 0, 1)
|
rootBlockDevice := make([]interface{}, 0, 1)
|
||||||
for _, vol := range volResp.Volumes {
|
for _, vol := range volResp.Volumes {
|
||||||
volSize, err := strconv.Atoi(vol.Size)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
blockDevice := make(map[string]interface{})
|
blockDevice := make(map[string]interface{})
|
||||||
blockDevice["device_name"] = blockDevices[vol.VolumeId].DeviceName
|
blockDevice["device_name"] = *blockDevices[*vol.VolumeID].DeviceName
|
||||||
blockDevice["volume_type"] = vol.VolumeType
|
blockDevice["volume_type"] = *vol.VolumeType
|
||||||
blockDevice["volume_size"] = volSize
|
blockDevice["volume_size"] = *vol.Size
|
||||||
|
if vol.IOPS != nil {
|
||||||
|
blockDevice["iops"] = *vol.IOPS
|
||||||
|
}
|
||||||
blockDevice["delete_on_termination"] =
|
blockDevice["delete_on_termination"] =
|
||||||
blockDevices[vol.VolumeId].DeleteOnTermination
|
*blockDevices[*vol.VolumeID].EBS.DeleteOnTermination
|
||||||
|
|
||||||
// If this is the root device, save it. We stop here since we
|
// If this is the root device, save it. We stop here since we
|
||||||
// can't put invalid keys into this map.
|
// can't put invalid keys into this map.
|
||||||
if blockDevice["device_name"] == instance.RootDeviceName {
|
if blockDevice["device_name"] == *instance.RootDeviceName {
|
||||||
rootBlockDevice = []interface{}{blockDevice}
|
rootBlockDevice = []interface{}{blockDevice}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
blockDevice["snapshot_id"] = vol.SnapshotId
|
blockDevice["snapshot_id"] = *vol.SnapshotID
|
||||||
blockDevice["encrypted"] = vol.Encrypted
|
blockDevice["encrypted"] = *vol.Encrypted
|
||||||
nonRootBlockDevices = append(nonRootBlockDevices, blockDevice)
|
nonRootBlockDevices = append(nonRootBlockDevices, blockDevice)
|
||||||
}
|
}
|
||||||
d.Set("block_device", nonRootBlockDevices)
|
d.Set("block_device", nonRootBlockDevices)
|
||||||
|
@ -486,21 +561,25 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
opts := new(ec2.ModifyInstance)
|
opts := new(ec2.ModifyInstanceAttributeRequest)
|
||||||
|
|
||||||
opts.SetSourceDestCheck = true
|
|
||||||
opts.SourceDestCheck = d.Get("source_dest_check").(bool)
|
|
||||||
|
|
||||||
log.Printf("[INFO] Modifying instance %s: %#v", d.Id(), opts)
|
log.Printf("[INFO] Modifying instance %s: %#v", d.Id(), opts)
|
||||||
if _, err := ec2conn.ModifyInstance(d.Id(), opts); err != nil {
|
err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeRequest{
|
||||||
|
InstanceID: aws.String(d.Id()),
|
||||||
|
SourceDestCheck: &ec2.AttributeBooleanValue{
|
||||||
|
Value: aws.Boolean(d.Get("source_dest_check").(bool)),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(mitchellh): wait for the attributes we modified to
|
// TODO(mitchellh): wait for the attributes we modified to
|
||||||
// persist the change...
|
// persist the change...
|
||||||
|
|
||||||
if err := setTags(ec2conn, d); err != nil {
|
if err := setTagsSDK(ec2conn, d); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
d.SetPartial("tags")
|
d.SetPartial("tags")
|
||||||
|
@ -510,10 +589,13 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
log.Printf("[INFO] Terminating instance: %s", d.Id())
|
log.Printf("[INFO] Terminating instance: %s", d.Id())
|
||||||
if _, err := ec2conn.TerminateInstances([]string{d.Id()}); err != nil {
|
req := &ec2.TerminateInstancesRequest{
|
||||||
|
InstanceIDs: []string{d.Id()},
|
||||||
|
}
|
||||||
|
if _, err := ec2conn.TerminateInstances(req); err != nil {
|
||||||
return fmt.Errorf("Error terminating instance: %s", err)
|
return fmt.Errorf("Error terminating instance: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -545,9 +627,11 @@ func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
// an EC2 instance.
|
// an EC2 instance.
|
||||||
func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc {
|
func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc {
|
||||||
return func() (interface{}, string, error) {
|
return func() (interface{}, string, error) {
|
||||||
resp, err := conn.Instances([]string{instanceID}, ec2.NewFilter())
|
resp, err := conn.DescribeInstances(&ec2.DescribeInstancesRequest{
|
||||||
|
InstanceIDs: []string{instanceID},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
|
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
|
||||||
// Set this to nil as if we didn't find anything.
|
// Set this to nil as if we didn't find anything.
|
||||||
resp = nil
|
resp = nil
|
||||||
} else {
|
} else {
|
||||||
|
@ -563,7 +647,7 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRe
|
||||||
}
|
}
|
||||||
|
|
||||||
i := &resp.Reservations[0].Instances[0]
|
i := &resp.Reservations[0].Instances[0]
|
||||||
return i, i.State.Name, nil
|
return i, *i.State.Name, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,24 +5,25 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAWSInstance_normal(t *testing.T) {
|
func TestAccAWSInstance_normal(t *testing.T) {
|
||||||
var v ec2.Instance
|
var v ec2.Instance
|
||||||
|
|
||||||
testCheck := func(*terraform.State) error {
|
testCheck := func(*terraform.State) error {
|
||||||
if v.AvailZone != "us-west-2a" {
|
if *v.Placement.AvailabilityZone != "us-west-2a" {
|
||||||
return fmt.Errorf("bad availability zone: %#v", v.AvailZone)
|
return fmt.Errorf("bad availability zone: %#v", *v.Placement.AvailabilityZone)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(v.SecurityGroups) == 0 {
|
if len(v.SecurityGroups) == 0 {
|
||||||
return fmt.Errorf("no security groups: %#v", v.SecurityGroups)
|
return fmt.Errorf("no security groups: %#v", v.SecurityGroups)
|
||||||
}
|
}
|
||||||
if v.SecurityGroups[0].Name != "tf_test_foo" {
|
if *v.SecurityGroups[0].GroupName != "tf_test_foo" {
|
||||||
return fmt.Errorf("no security groups: %#v", v.SecurityGroups)
|
return fmt.Errorf("no security groups: %#v", v.SecurityGroups)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,9 +74,9 @@ func TestAccAWSInstance_blockDevices(t *testing.T) {
|
||||||
return func(*terraform.State) error {
|
return func(*terraform.State) error {
|
||||||
|
|
||||||
// Map out the block devices by name, which should be unique.
|
// Map out the block devices by name, which should be unique.
|
||||||
blockDevices := make(map[string]ec2.BlockDevice)
|
blockDevices := make(map[string]ec2.InstanceBlockDeviceMapping)
|
||||||
for _, blockDevice := range v.BlockDevices {
|
for _, blockDevice := range v.BlockDeviceMappings {
|
||||||
blockDevices[blockDevice.DeviceName] = blockDevice
|
blockDevices[*blockDevice.DeviceName] = blockDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the root block device exists.
|
// Check if the root block device exists.
|
||||||
|
@ -88,6 +89,11 @@ func TestAccAWSInstance_blockDevices(t *testing.T) {
|
||||||
fmt.Errorf("block device doesn't exist: /dev/sdb")
|
fmt.Errorf("block device doesn't exist: /dev/sdb")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the third block device exists.
|
||||||
|
if _, ok := blockDevices["/dev/sdc"]; !ok {
|
||||||
|
fmt.Errorf("block device doesn't exist: /dev/sdc")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,11 +120,22 @@ func TestAccAWSInstance_blockDevices(t *testing.T) {
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "root_block_device.0.volume_type", "gp2"),
|
"aws_instance.foo", "root_block_device.0.volume_type", "gp2"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "block_device.#", "1"),
|
"aws_instance.foo", "block_device.#", "2"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "block_device.172787947.device_name", "/dev/sdb"),
|
"aws_instance.foo", "block_device.172787947.device_name", "/dev/sdb"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "block_device.172787947.volume_size", "9"),
|
"aws_instance.foo", "block_device.172787947.volume_size", "9"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"aws_instance.foo", "block_device.172787947.iops", "0"),
|
||||||
|
// Check provisioned SSD device
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"aws_instance.foo", "block_device.3336996981.volume_type", "io1"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"aws_instance.foo", "block_device.3336996981.device_name", "/dev/sdc"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"aws_instance.foo", "block_device.3336996981.volume_size", "10"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"aws_instance.foo", "block_device.3336996981.iops", "100"),
|
||||||
testCheck(),
|
testCheck(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -131,8 +148,8 @@ func TestAccAWSInstance_sourceDestCheck(t *testing.T) {
|
||||||
|
|
||||||
testCheck := func(enabled bool) resource.TestCheckFunc {
|
testCheck := func(enabled bool) resource.TestCheckFunc {
|
||||||
return func(*terraform.State) error {
|
return func(*terraform.State) error {
|
||||||
if v.SourceDestCheck != enabled {
|
if *v.SourceDestCheck != enabled {
|
||||||
return fmt.Errorf("bad source_dest_check: %#v", v.SourceDestCheck)
|
return fmt.Errorf("bad source_dest_check: %#v", *v.SourceDestCheck)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -190,7 +207,7 @@ func TestAccAWSInstance_vpc(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccInstance_tags(t *testing.T) {
|
func TestAccAWSInstance_tags(t *testing.T) {
|
||||||
var v ec2.Instance
|
var v ec2.Instance
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
|
@ -202,9 +219,9 @@ func TestAccInstance_tags(t *testing.T) {
|
||||||
Config: testAccCheckInstanceConfigTags,
|
Config: testAccCheckInstanceConfigTags,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckInstanceExists("aws_instance.foo", &v),
|
testAccCheckInstanceExists("aws_instance.foo", &v),
|
||||||
testAccCheckTags(&v.Tags, "foo", "bar"),
|
testAccCheckTagsSDK(&v.Tags, "foo", "bar"),
|
||||||
// Guard against regression of https://github.com/hashicorp/terraform/issues/914
|
// Guard against regression of https://github.com/hashicorp/terraform/issues/914
|
||||||
testAccCheckTags(&v.Tags, "#", ""),
|
testAccCheckTagsSDK(&v.Tags, "#", ""),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -212,21 +229,21 @@ func TestAccInstance_tags(t *testing.T) {
|
||||||
Config: testAccCheckInstanceConfigTagsUpdate,
|
Config: testAccCheckInstanceConfigTagsUpdate,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckInstanceExists("aws_instance.foo", &v),
|
testAccCheckInstanceExists("aws_instance.foo", &v),
|
||||||
testAccCheckTags(&v.Tags, "foo", ""),
|
testAccCheckTagsSDK(&v.Tags, "foo", ""),
|
||||||
testAccCheckTags(&v.Tags, "bar", "baz"),
|
testAccCheckTagsSDK(&v.Tags, "bar", "baz"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccInstance_privateIP(t *testing.T) {
|
func TestAccAWSInstance_privateIP(t *testing.T) {
|
||||||
var v ec2.Instance
|
var v ec2.Instance
|
||||||
|
|
||||||
testCheckPrivateIP := func() resource.TestCheckFunc {
|
testCheckPrivateIP := func() resource.TestCheckFunc {
|
||||||
return func(*terraform.State) error {
|
return func(*terraform.State) error {
|
||||||
if v.PrivateIpAddress != "10.1.1.42" {
|
if *v.PrivateIPAddress != "10.1.1.42" {
|
||||||
return fmt.Errorf("bad private IP: %s", v.PrivateIpAddress)
|
return fmt.Errorf("bad private IP: %s", *v.PrivateIPAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -249,13 +266,13 @@ func TestAccInstance_privateIP(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccInstance_associatePublicIPAndPrivateIP(t *testing.T) {
|
func TestAccAWSInstance_associatePublicIPAndPrivateIP(t *testing.T) {
|
||||||
var v ec2.Instance
|
var v ec2.Instance
|
||||||
|
|
||||||
testCheckPrivateIP := func() resource.TestCheckFunc {
|
testCheckPrivateIP := func() resource.TestCheckFunc {
|
||||||
return func(*terraform.State) error {
|
return func(*terraform.State) error {
|
||||||
if v.PrivateIpAddress != "10.1.1.42" {
|
if *v.PrivateIPAddress != "10.1.1.42" {
|
||||||
return fmt.Errorf("bad private IP: %s", v.PrivateIpAddress)
|
return fmt.Errorf("bad private IP: %s", *v.PrivateIPAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -279,7 +296,7 @@ func TestAccInstance_associatePublicIPAndPrivateIP(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckInstanceDestroy(s *terraform.State) error {
|
func testAccCheckInstanceDestroy(s *terraform.State) error {
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
if rs.Type != "aws_instance" {
|
if rs.Type != "aws_instance" {
|
||||||
|
@ -287,8 +304,9 @@ func testAccCheckInstanceDestroy(s *terraform.State) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find the resource
|
// Try to find the resource
|
||||||
resp, err := conn.Instances(
|
resp, err := conn.DescribeInstances(&ec2.DescribeInstancesRequest{
|
||||||
[]string{rs.Primary.ID}, ec2.NewFilter())
|
InstanceIDs: []string{rs.Primary.ID},
|
||||||
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if len(resp.Reservations) > 0 {
|
if len(resp.Reservations) > 0 {
|
||||||
return fmt.Errorf("still exist.")
|
return fmt.Errorf("still exist.")
|
||||||
|
@ -298,7 +316,7 @@ func testAccCheckInstanceDestroy(s *terraform.State) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the error is what we want
|
// Verify the error is what we want
|
||||||
ec2err, ok := err.(*ec2.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if !ok {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -321,9 +339,10 @@ func testAccCheckInstanceExists(n string, i *ec2.Instance) resource.TestCheckFun
|
||||||
return fmt.Errorf("No ID is set")
|
return fmt.Errorf("No ID is set")
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
resp, err := conn.Instances(
|
resp, err := conn.DescribeInstances(&ec2.DescribeInstancesRequest{
|
||||||
[]string{rs.Primary.ID}, ec2.NewFilter())
|
InstanceIDs: []string{rs.Primary.ID},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -391,6 +410,12 @@ resource "aws_instance" "foo" {
|
||||||
device_name = "/dev/sdb"
|
device_name = "/dev/sdb"
|
||||||
volume_size = 9
|
volume_size = 9
|
||||||
}
|
}
|
||||||
|
block_device {
|
||||||
|
device_name = "/dev/sdc"
|
||||||
|
volume_size = 10
|
||||||
|
volume_type = "io1"
|
||||||
|
iops = 100
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,10 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceAwsInternetGateway() *schema.Resource {
|
func resourceAwsInternetGateway() *schema.Resource {
|
||||||
|
@ -28,7 +29,7 @@ func resourceAwsInternetGateway() *schema.Resource {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsInternetGatewayCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsInternetGatewayCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
// Create the gateway
|
// Create the gateway
|
||||||
log.Printf("[DEBUG] Creating internet gateway")
|
log.Printf("[DEBUG] Creating internet gateway")
|
||||||
|
@ -38,16 +39,21 @@ func resourceAwsInternetGatewayCreate(d *schema.ResourceData, meta interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the ID and store it
|
// Get the ID and store it
|
||||||
ig := &resp.InternetGateway
|
ig := resp.InternetGateway
|
||||||
d.SetId(ig.InternetGatewayId)
|
d.SetId(*ig.InternetGatewayID)
|
||||||
log.Printf("[INFO] InternetGateway ID: %s", d.Id())
|
log.Printf("[INFO] InternetGateway ID: %s", d.Id())
|
||||||
|
|
||||||
|
err = setTagsSDK(ec2conn, d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Attach the new gateway to the correct vpc
|
// Attach the new gateway to the correct vpc
|
||||||
return resourceAwsInternetGatewayAttach(d, meta)
|
return resourceAwsInternetGatewayAttach(d, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsInternetGatewayRead(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsInternetGatewayRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
igRaw, _, err := IGStateRefreshFunc(ec2conn, d.Id())()
|
igRaw, _, err := IGStateRefreshFunc(ec2conn, d.Id())()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -64,10 +70,10 @@ func resourceAwsInternetGatewayRead(d *schema.ResourceData, meta interface{}) er
|
||||||
// Gateway exists but not attached to the VPC
|
// Gateway exists but not attached to the VPC
|
||||||
d.Set("vpc_id", "")
|
d.Set("vpc_id", "")
|
||||||
} else {
|
} else {
|
||||||
d.Set("vpc_id", ig.Attachments[0].VpcId)
|
d.Set("vpc_id", ig.Attachments[0].VPCID)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Set("tags", tagsToMap(ig.Tags))
|
d.Set("tags", tagsToMapSDK(ig.Tags))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -85,9 +91,9 @@ func resourceAwsInternetGatewayUpdate(d *schema.ResourceData, meta interface{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
if err := setTags(ec2conn, d); err != nil {
|
if err := setTagsSDK(ec2conn, d); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +103,7 @@ func resourceAwsInternetGatewayUpdate(d *schema.ResourceData, meta interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsInternetGatewayDelete(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsInternetGatewayDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
// Detach if it is attached
|
// Detach if it is attached
|
||||||
if err := resourceAwsInternetGatewayDetach(d, meta); err != nil {
|
if err := resourceAwsInternetGatewayDetach(d, meta); err != nil {
|
||||||
|
@ -107,12 +113,14 @@ func resourceAwsInternetGatewayDelete(d *schema.ResourceData, meta interface{})
|
||||||
log.Printf("[INFO] Deleting Internet Gateway: %s", d.Id())
|
log.Printf("[INFO] Deleting Internet Gateway: %s", d.Id())
|
||||||
|
|
||||||
return resource.Retry(5*time.Minute, func() error {
|
return resource.Retry(5*time.Minute, func() error {
|
||||||
_, err := ec2conn.DeleteInternetGateway(d.Id())
|
err := ec2conn.DeleteInternetGateway(&ec2.DeleteInternetGatewayRequest{
|
||||||
|
InternetGatewayID: aws.String(d.Id()),
|
||||||
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ec2err, ok := err.(*ec2.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if !ok {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -129,7 +137,7 @@ func resourceAwsInternetGatewayDelete(d *schema.ResourceData, meta interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsInternetGatewayAttach(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsInternetGatewayAttach(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
if d.Get("vpc_id").(string) == "" {
|
if d.Get("vpc_id").(string) == "" {
|
||||||
log.Printf(
|
log.Printf(
|
||||||
|
@ -143,7 +151,10 @@ func resourceAwsInternetGatewayAttach(d *schema.ResourceData, meta interface{})
|
||||||
d.Id(),
|
d.Id(),
|
||||||
d.Get("vpc_id").(string))
|
d.Get("vpc_id").(string))
|
||||||
|
|
||||||
_, err := ec2conn.AttachInternetGateway(d.Id(), d.Get("vpc_id").(string))
|
err := ec2conn.AttachInternetGateway(&ec2.AttachInternetGatewayRequest{
|
||||||
|
InternetGatewayID: aws.String(d.Id()),
|
||||||
|
VPCID: aws.String(d.Get("vpc_id").(string)),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -171,7 +182,7 @@ func resourceAwsInternetGatewayAttach(d *schema.ResourceData, meta interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
// Get the old VPC ID to detach from
|
// Get the old VPC ID to detach from
|
||||||
vpcID, _ := d.GetChange("vpc_id")
|
vpcID, _ := d.GetChange("vpc_id")
|
||||||
|
@ -189,9 +200,12 @@ func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{})
|
||||||
vpcID.(string))
|
vpcID.(string))
|
||||||
|
|
||||||
wait := true
|
wait := true
|
||||||
_, err := ec2conn.DetachInternetGateway(d.Id(), vpcID.(string))
|
err := ec2conn.DetachInternetGateway(&ec2.DetachInternetGatewayRequest{
|
||||||
|
InternetGatewayID: aws.String(d.Id()),
|
||||||
|
VPCID: aws.String(vpcID.(string)),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ec2err, ok := err.(*ec2.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if ok {
|
if ok {
|
||||||
if ec2err.Code == "InvalidInternetGatewayID.NotFound" {
|
if ec2err.Code == "InvalidInternetGatewayID.NotFound" {
|
||||||
err = nil
|
err = nil
|
||||||
|
@ -232,9 +246,11 @@ func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{})
|
||||||
// an internet gateway.
|
// an internet gateway.
|
||||||
func IGStateRefreshFunc(ec2conn *ec2.EC2, id string) resource.StateRefreshFunc {
|
func IGStateRefreshFunc(ec2conn *ec2.EC2, id string) resource.StateRefreshFunc {
|
||||||
return func() (interface{}, string, error) {
|
return func() (interface{}, string, error) {
|
||||||
resp, err := ec2conn.DescribeInternetGateways([]string{id}, ec2.NewFilter())
|
resp, err := ec2conn.DescribeInternetGateways(&ec2.DescribeInternetGatewaysRequest{
|
||||||
|
InternetGatewayIDs: []string{id},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ec2err, ok := err.(*ec2.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
|
if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
|
||||||
resp = nil
|
resp = nil
|
||||||
} else {
|
} else {
|
||||||
|
@ -256,16 +272,18 @@ func IGStateRefreshFunc(ec2conn *ec2.EC2, id string) resource.StateRefreshFunc {
|
||||||
|
|
||||||
// IGAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used
|
// IGAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used
|
||||||
// watch the state of an internet gateway's attachment.
|
// watch the state of an internet gateway's attachment.
|
||||||
func IGAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string) resource.StateRefreshFunc {
|
func IGAttachStateRefreshFunc(ec2conn *ec2.EC2, id string, expected string) resource.StateRefreshFunc {
|
||||||
var start time.Time
|
var start time.Time
|
||||||
return func() (interface{}, string, error) {
|
return func() (interface{}, string, error) {
|
||||||
if start.IsZero() {
|
if start.IsZero() {
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := conn.DescribeInternetGateways([]string{id}, ec2.NewFilter())
|
resp, err := ec2conn.DescribeInternetGateways(&ec2.DescribeInternetGatewaysRequest{
|
||||||
|
InternetGatewayIDs: []string{id},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ec2err, ok := err.(*ec2.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
|
if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
|
||||||
resp = nil
|
resp = nil
|
||||||
} else {
|
} else {
|
||||||
|
@ -291,6 +309,6 @@ func IGAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string) resourc
|
||||||
return ig, "detached", nil
|
return ig, "detached", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return ig, ig.Attachments[0].State, nil
|
return ig, *ig.Attachments[0].State, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAWSInternetGateway(t *testing.T) {
|
func TestAccAWSInternetGateway(t *testing.T) {
|
||||||
|
@ -20,8 +21,8 @@ func TestAccAWSInternetGateway(t *testing.T) {
|
||||||
return fmt.Errorf("IG B is not attached")
|
return fmt.Errorf("IG B is not attached")
|
||||||
}
|
}
|
||||||
|
|
||||||
id1 := v.Attachments[0].VpcId
|
id1 := v.Attachments[0].VPCID
|
||||||
id2 := v2.Attachments[0].VpcId
|
id2 := v2.Attachments[0].VPCID
|
||||||
if id1 == id2 {
|
if id1 == id2 {
|
||||||
return fmt.Errorf("Both attachment IDs are the same")
|
return fmt.Errorf("Both attachment IDs are the same")
|
||||||
}
|
}
|
||||||
|
@ -97,6 +98,7 @@ func TestAccInternetGateway_tags(t *testing.T) {
|
||||||
Config: testAccCheckInternetGatewayConfigTags,
|
Config: testAccCheckInternetGatewayConfigTags,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckInternetGatewayExists("aws_internet_gateway.foo", &v),
|
testAccCheckInternetGatewayExists("aws_internet_gateway.foo", &v),
|
||||||
|
testAccCheckTagsSDK(&v.Tags, "foo", "bar"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -104,8 +106,8 @@ func TestAccInternetGateway_tags(t *testing.T) {
|
||||||
Config: testAccCheckInternetGatewayConfigTagsUpdate,
|
Config: testAccCheckInternetGatewayConfigTagsUpdate,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckInternetGatewayExists("aws_internet_gateway.foo", &v),
|
testAccCheckInternetGatewayExists("aws_internet_gateway.foo", &v),
|
||||||
testAccCheckTags(&v.Tags, "foo", ""),
|
testAccCheckTagsSDK(&v.Tags, "foo", ""),
|
||||||
testAccCheckTags(&v.Tags, "bar", "baz"),
|
testAccCheckTagsSDK(&v.Tags, "bar", "baz"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -113,7 +115,7 @@ func TestAccInternetGateway_tags(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckInternetGatewayDestroy(s *terraform.State) error {
|
func testAccCheckInternetGatewayDestroy(s *terraform.State) error {
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
if rs.Type != "aws_internet_gateway" {
|
if rs.Type != "aws_internet_gateway" {
|
||||||
|
@ -121,8 +123,9 @@ func testAccCheckInternetGatewayDestroy(s *terraform.State) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find the resource
|
// Try to find the resource
|
||||||
resp, err := conn.DescribeInternetGateways(
|
resp, err := ec2conn.DescribeInternetGateways(&ec2.DescribeInternetGatewaysRequest{
|
||||||
[]string{rs.Primary.ID}, ec2.NewFilter())
|
InternetGatewayIDs: []string{rs.Primary.ID},
|
||||||
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if len(resp.InternetGateways) > 0 {
|
if len(resp.InternetGateways) > 0 {
|
||||||
return fmt.Errorf("still exists")
|
return fmt.Errorf("still exists")
|
||||||
|
@ -132,7 +135,7 @@ func testAccCheckInternetGatewayDestroy(s *terraform.State) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the error is what we want
|
// Verify the error is what we want
|
||||||
ec2err, ok := err.(*ec2.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if !ok {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -155,9 +158,10 @@ func testAccCheckInternetGatewayExists(n string, ig *ec2.InternetGateway) resour
|
||||||
return fmt.Errorf("No ID is set")
|
return fmt.Errorf("No ID is set")
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
resp, err := conn.DescribeInternetGateways(
|
resp, err := ec2conn.DescribeInternetGateways(&ec2.DescribeInternetGatewaysRequest{
|
||||||
[]string{rs.Primary.ID}, ec2.NewFilter())
|
InternetGatewayIDs: []string{rs.Primary.ID},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
package aws
|
package aws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceAwsKeyPair() *schema.Resource {
|
func resourceAwsKeyPair() *schema.Resource {
|
||||||
|
@ -33,42 +37,50 @@ func resourceAwsKeyPair() *schema.Resource {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsKeyPairCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsKeyPairCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
keyName := d.Get("key_name").(string)
|
keyName := d.Get("key_name").(string)
|
||||||
publicKey := d.Get("public_key").(string)
|
publicKey := d.Get("public_key").(string)
|
||||||
resp, err := ec2conn.ImportKeyPair(keyName, publicKey)
|
req := &ec2.ImportKeyPairRequest{
|
||||||
|
KeyName: aws.String(keyName),
|
||||||
|
PublicKeyMaterial: []byte(base64.StdEncoding.EncodeToString([]byte(publicKey))),
|
||||||
|
}
|
||||||
|
resp, err := ec2conn.ImportKeyPair(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error import KeyPair: %s", err)
|
return fmt.Errorf("Error import KeyPair: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.SetId(resp.KeyName)
|
d.SetId(*resp.KeyName)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsKeyPairRead(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsKeyPairRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
resp, err := ec2conn.KeyPairs([]string{d.Id()}, nil)
|
req := &ec2.DescribeKeyPairsRequest{
|
||||||
|
KeyNames: []string{d.Id()},
|
||||||
|
}
|
||||||
|
resp, err := ec2conn.DescribeKeyPairs(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error retrieving KeyPair: %s", err)
|
return fmt.Errorf("Error retrieving KeyPair: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, keyPair := range resp.Keys {
|
for _, keyPair := range resp.KeyPairs {
|
||||||
if keyPair.Name == d.Id() {
|
if *keyPair.KeyName == d.Id() {
|
||||||
d.Set("key_name", keyPair.Name)
|
d.Set("key_name", keyPair.KeyName)
|
||||||
d.Set("fingerprint", keyPair.Fingerprint)
|
d.Set("fingerprint", keyPair.KeyFingerprint)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("Unable to find key pair within: %#v", resp.Keys)
|
return fmt.Errorf("Unable to find key pair within: %#v", resp.KeyPairs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsKeyPairDelete(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsKeyPairDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
_, err := ec2conn.DeleteKeyPair(d.Id())
|
err := ec2conn.DeleteKeyPair(&ec2.DeleteKeyPairRequest{
|
||||||
|
KeyName: aws.String(d.Id()),
|
||||||
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAWSKeyPair_normal(t *testing.T) {
|
func TestAccAWSKeyPair_normal(t *testing.T) {
|
||||||
var conf ec2.KeyPair
|
var conf ec2.KeyPairInfo
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -29,7 +30,7 @@ func TestAccAWSKeyPair_normal(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckAWSKeyPairDestroy(s *terraform.State) error {
|
func testAccCheckAWSKeyPairDestroy(s *terraform.State) error {
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
if rs.Type != "aws_key_pair" {
|
if rs.Type != "aws_key_pair" {
|
||||||
|
@ -37,17 +38,18 @@ func testAccCheckAWSKeyPairDestroy(s *terraform.State) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find key pair
|
// Try to find key pair
|
||||||
resp, err := conn.KeyPairs(
|
resp, err := ec2conn.DescribeKeyPairs(&ec2.DescribeKeyPairsRequest{
|
||||||
[]string{rs.Primary.ID}, nil)
|
KeyNames: []string{rs.Primary.ID},
|
||||||
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if len(resp.Keys) > 0 {
|
if len(resp.KeyPairs) > 0 {
|
||||||
return fmt.Errorf("still exist.")
|
return fmt.Errorf("still exist.")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the error is what we want
|
// Verify the error is what we want
|
||||||
ec2err, ok := err.(*ec2.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if !ok {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -59,16 +61,16 @@ func testAccCheckAWSKeyPairDestroy(s *terraform.State) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckAWSKeyPairFingerprint(expectedFingerprint string, conf *ec2.KeyPair) resource.TestCheckFunc {
|
func testAccCheckAWSKeyPairFingerprint(expectedFingerprint string, conf *ec2.KeyPairInfo) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
if conf.Fingerprint != expectedFingerprint {
|
if *conf.KeyFingerprint != expectedFingerprint {
|
||||||
return fmt.Errorf("incorrect fingerprint. expected %s, got %s", expectedFingerprint, conf.Fingerprint)
|
return fmt.Errorf("incorrect fingerprint. expected %s, got %s", expectedFingerprint, *conf.KeyFingerprint)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckAWSKeyPairExists(n string, res *ec2.KeyPair) resource.TestCheckFunc {
|
func testAccCheckAWSKeyPairExists(n string, res *ec2.KeyPairInfo) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
rs, ok := s.RootModule().Resources[n]
|
rs, ok := s.RootModule().Resources[n]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -79,18 +81,20 @@ func testAccCheckAWSKeyPairExists(n string, res *ec2.KeyPair) resource.TestCheck
|
||||||
return fmt.Errorf("No KeyPair name is set")
|
return fmt.Errorf("No KeyPair name is set")
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
resp, err := conn.KeyPairs(
|
resp, err := ec2conn.DescribeKeyPairs(&ec2.DescribeKeyPairsRequest{
|
||||||
[]string{rs.Primary.ID}, nil)
|
KeyNames: []string{rs.Primary.ID},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(resp.Keys) != 1 ||
|
if len(resp.KeyPairs) != 1 ||
|
||||||
resp.Keys[0].Name != rs.Primary.ID {
|
*resp.KeyPairs[0].KeyName != rs.Primary.ID {
|
||||||
return fmt.Errorf("KeyPair not found")
|
return fmt.Errorf("KeyPair not found")
|
||||||
}
|
}
|
||||||
*res = resp.Keys[0]
|
|
||||||
|
*res = resp.KeyPairs[0]
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceAwsMainRouteTableAssociation() *schema.Resource {
|
func resourceAwsMainRouteTableAssociation() *schema.Resource {
|
||||||
|
@ -39,7 +40,7 @@ func resourceAwsMainRouteTableAssociation() *schema.Resource {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsMainRouteTableAssociationCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsMainRouteTableAssociationCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
vpcId := d.Get("vpc_id").(string)
|
vpcId := d.Get("vpc_id").(string)
|
||||||
routeTableId := d.Get("route_table_id").(string)
|
routeTableId := d.Get("route_table_id").(string)
|
||||||
|
|
||||||
|
@ -50,23 +51,23 @@ func resourceAwsMainRouteTableAssociationCreate(d *schema.ResourceData, meta int
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := ec2conn.ReassociateRouteTable(
|
resp, err := ec2conn.ReplaceRouteTableAssociation(&ec2.ReplaceRouteTableAssociationRequest{
|
||||||
mainAssociation.AssociationId,
|
AssociationID: mainAssociation.RouteTableAssociationID,
|
||||||
routeTableId,
|
RouteTableID: aws.String(routeTableId),
|
||||||
)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Set("original_route_table_id", mainAssociation.RouteTableId)
|
d.Set("original_route_table_id", mainAssociation.RouteTableID)
|
||||||
d.SetId(resp.AssociationId)
|
d.SetId(*resp.NewAssociationID)
|
||||||
log.Printf("[INFO] New main route table association ID: %s", d.Id())
|
log.Printf("[INFO] New main route table association ID: %s", d.Id())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsMainRouteTableAssociationRead(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsMainRouteTableAssociationRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
mainAssociation, err := findMainRouteTableAssociation(
|
mainAssociation, err := findMainRouteTableAssociation(
|
||||||
ec2conn,
|
ec2conn,
|
||||||
|
@ -75,7 +76,7 @@ func resourceAwsMainRouteTableAssociationRead(d *schema.ResourceData, meta inter
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if mainAssociation.AssociationId != d.Id() {
|
if *mainAssociation.RouteTableAssociationID != d.Id() {
|
||||||
// It seems it doesn't exist anymore, so clear the ID
|
// It seems it doesn't exist anymore, so clear the ID
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
}
|
}
|
||||||
|
@ -87,25 +88,28 @@ func resourceAwsMainRouteTableAssociationRead(d *schema.ResourceData, meta inter
|
||||||
// original_route_table_id - this needs to stay recorded as the AWS-created
|
// original_route_table_id - this needs to stay recorded as the AWS-created
|
||||||
// table from VPC creation.
|
// table from VPC creation.
|
||||||
func resourceAwsMainRouteTableAssociationUpdate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsMainRouteTableAssociationUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
vpcId := d.Get("vpc_id").(string)
|
vpcId := d.Get("vpc_id").(string)
|
||||||
routeTableId := d.Get("route_table_id").(string)
|
routeTableId := d.Get("route_table_id").(string)
|
||||||
|
|
||||||
log.Printf("[INFO] Updating main route table association: %s => %s", vpcId, routeTableId)
|
log.Printf("[INFO] Updating main route table association: %s => %s", vpcId, routeTableId)
|
||||||
|
|
||||||
resp, err := ec2conn.ReassociateRouteTable(d.Id(), routeTableId)
|
resp, err := ec2conn.ReplaceRouteTableAssociation(&ec2.ReplaceRouteTableAssociationRequest{
|
||||||
|
AssociationID: aws.String(d.Id()),
|
||||||
|
RouteTableID: aws.String(routeTableId),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.SetId(resp.AssociationId)
|
d.SetId(*resp.NewAssociationID)
|
||||||
log.Printf("[INFO] New main route table association ID: %s", d.Id())
|
log.Printf("[INFO] New main route table association ID: %s", d.Id())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsMainRouteTableAssociationDelete(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsMainRouteTableAssociationDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
vpcId := d.Get("vpc_id").(string)
|
vpcId := d.Get("vpc_id").(string)
|
||||||
originalRouteTableId := d.Get("original_route_table_id").(string)
|
originalRouteTableId := d.Get("original_route_table_id").(string)
|
||||||
|
|
||||||
|
@ -113,12 +117,15 @@ func resourceAwsMainRouteTableAssociationDelete(d *schema.ResourceData, meta int
|
||||||
vpcId,
|
vpcId,
|
||||||
originalRouteTableId)
|
originalRouteTableId)
|
||||||
|
|
||||||
resp, err := ec2conn.ReassociateRouteTable(d.Id(), originalRouteTableId)
|
resp, err := ec2conn.ReplaceRouteTableAssociation(&ec2.ReplaceRouteTableAssociationRequest{
|
||||||
|
AssociationID: aws.String(d.Id()),
|
||||||
|
RouteTableID: aws.String(originalRouteTableId),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] Resulting Association ID: %s", resp.AssociationId)
|
log.Printf("[INFO] Resulting Association ID: %s", *resp.NewAssociationID)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -130,7 +137,7 @@ func findMainRouteTableAssociation(ec2conn *ec2.EC2, vpcId string) (*ec2.RouteTa
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, a := range mainRouteTable.Associations {
|
for _, a := range mainRouteTable.Associations {
|
||||||
if a.Main {
|
if *a.Main {
|
||||||
return &a, nil
|
return &a, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,10 +145,17 @@ func findMainRouteTableAssociation(ec2conn *ec2.EC2, vpcId string) (*ec2.RouteTa
|
||||||
}
|
}
|
||||||
|
|
||||||
func findMainRouteTable(ec2conn *ec2.EC2, vpcId string) (*ec2.RouteTable, error) {
|
func findMainRouteTable(ec2conn *ec2.EC2, vpcId string) (*ec2.RouteTable, error) {
|
||||||
filter := ec2.NewFilter()
|
mainFilter := ec2.Filter{
|
||||||
filter.Add("association.main", "true")
|
aws.String("association.main"),
|
||||||
filter.Add("vpc-id", vpcId)
|
[]string{"true"},
|
||||||
routeResp, err := ec2conn.DescribeRouteTables(nil, filter)
|
}
|
||||||
|
vpcFilter := ec2.Filter{
|
||||||
|
aws.String("vpc-id"),
|
||||||
|
[]string{vpcId},
|
||||||
|
}
|
||||||
|
routeResp, err := ec2conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
|
||||||
|
Filters: []ec2.Filter{mainFilter, vpcFilter},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if len(routeResp.RouteTables) != 1 {
|
} else if len(routeResp.RouteTables) != 1 {
|
||||||
|
|
|
@ -65,15 +65,15 @@ func testAccCheckMainRouteTableAssociation(
|
||||||
return fmt.Errorf("Not found: %s", vpcResource)
|
return fmt.Errorf("Not found: %s", vpcResource)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
mainAssociation, err := findMainRouteTableAssociation(conn, vpc.Primary.ID)
|
mainAssociation, err := findMainRouteTableAssociation(conn, vpc.Primary.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if mainAssociation.AssociationId != rs.Primary.ID {
|
if *mainAssociation.RouteTableAssociationID != rs.Primary.ID {
|
||||||
return fmt.Errorf("Found wrong main association: %s",
|
return fmt.Errorf("Found wrong main association: %s",
|
||||||
mainAssociation.AssociationId)
|
*mainAssociation.RouteTableAssociationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -6,10 +6,11 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/hashcode"
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceAwsNetworkAcl() *schema.Resource {
|
func resourceAwsNetworkAcl() *schema.Resource {
|
||||||
|
@ -108,32 +109,34 @@ func resourceAwsNetworkAcl() *schema.Resource {
|
||||||
|
|
||||||
func resourceAwsNetworkAclCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsNetworkAclCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
// Create the Network Acl
|
// Create the Network Acl
|
||||||
createOpts := &ec2.CreateNetworkAcl{
|
createOpts := &ec2.CreateNetworkACLRequest{
|
||||||
VpcId: d.Get("vpc_id").(string),
|
VPCID: aws.String(d.Get("vpc_id").(string)),
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] Network Acl create config: %#v", createOpts)
|
log.Printf("[DEBUG] Network Acl create config: %#v", createOpts)
|
||||||
resp, err := ec2conn.CreateNetworkAcl(createOpts)
|
resp, err := ec2conn.CreateNetworkACL(createOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error creating network acl: %s", err)
|
return fmt.Errorf("Error creating network acl: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the ID and store it
|
// Get the ID and store it
|
||||||
networkAcl := &resp.NetworkAcl
|
networkAcl := resp.NetworkACL
|
||||||
d.SetId(networkAcl.NetworkAclId)
|
d.SetId(*networkAcl.NetworkACLID)
|
||||||
log.Printf("[INFO] Network Acl ID: %s", networkAcl.NetworkAclId)
|
log.Printf("[INFO] Network Acl ID: %s", *networkAcl.NetworkACLID)
|
||||||
|
|
||||||
// Update rules and subnet association once acl is created
|
// Update rules and subnet association once acl is created
|
||||||
return resourceAwsNetworkAclUpdate(d, meta)
|
return resourceAwsNetworkAclUpdate(d, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
resp, err := ec2conn.NetworkAcls([]string{d.Id()}, ec2.NewFilter())
|
resp, err := ec2conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
|
||||||
|
NetworkACLIDs: []string{d.Id()},
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -142,29 +145,29 @@ func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
networkAcl := &resp.NetworkAcls[0]
|
networkAcl := &resp.NetworkACLs[0]
|
||||||
var ingressEntries []ec2.NetworkAclEntry
|
var ingressEntries []ec2.NetworkACLEntry
|
||||||
var egressEntries []ec2.NetworkAclEntry
|
var egressEntries []ec2.NetworkACLEntry
|
||||||
|
|
||||||
// separate the ingress and egress rules
|
// separate the ingress and egress rules
|
||||||
for _, e := range networkAcl.EntrySet {
|
for _, e := range networkAcl.Entries {
|
||||||
if e.Egress == true {
|
if *e.Egress == true {
|
||||||
egressEntries = append(egressEntries, e)
|
egressEntries = append(egressEntries, e)
|
||||||
} else {
|
} else {
|
||||||
ingressEntries = append(ingressEntries, e)
|
ingressEntries = append(ingressEntries, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Set("vpc_id", networkAcl.VpcId)
|
d.Set("vpc_id", networkAcl.VPCID)
|
||||||
d.Set("ingress", ingressEntries)
|
d.Set("ingress", ingressEntries)
|
||||||
d.Set("egress", egressEntries)
|
d.Set("egress", egressEntries)
|
||||||
d.Set("tags", tagsToMap(networkAcl.Tags))
|
d.Set("tags", tagsToMapSDK(networkAcl.Tags))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
d.Partial(true)
|
d.Partial(true)
|
||||||
|
|
||||||
if d.HasChange("ingress") {
|
if d.HasChange("ingress") {
|
||||||
|
@ -190,13 +193,16 @@ func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to update acl %s with subnet %s: %s", d.Id(), newSubnet, err)
|
return fmt.Errorf("Failed to update acl %s with subnet %s: %s", d.Id(), newSubnet, err)
|
||||||
}
|
}
|
||||||
_, err = ec2conn.ReplaceNetworkAclAssociation(association.NetworkAclAssociationId, d.Id())
|
_, err = ec2conn.ReplaceNetworkACLAssociation(&ec2.ReplaceNetworkACLAssociationRequest{
|
||||||
|
AssociationID: association.NetworkACLAssociationID,
|
||||||
|
NetworkACLID: aws.String(d.Id()),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setTags(ec2conn, d); err != nil {
|
if err := setTagsSDK(ec2conn, d); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
d.SetPartial("tags")
|
d.SetPartial("tags")
|
||||||
|
@ -226,7 +232,11 @@ func updateNetworkAclEntries(d *schema.ResourceData, entryType string, ec2conn *
|
||||||
}
|
}
|
||||||
for _, remove := range toBeDeleted {
|
for _, remove := range toBeDeleted {
|
||||||
// Delete old Acl
|
// Delete old Acl
|
||||||
_, err := ec2conn.DeleteNetworkAclEntry(d.Id(), remove.RuleNumber, remove.Egress)
|
err := ec2conn.DeleteNetworkACLEntry(&ec2.DeleteNetworkACLEntryRequest{
|
||||||
|
NetworkACLID: aws.String(d.Id()),
|
||||||
|
RuleNumber: remove.RuleNumber,
|
||||||
|
Egress: remove.Egress,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error deleting %s entry: %s", entryType, err)
|
return fmt.Errorf("Error deleting %s entry: %s", entryType, err)
|
||||||
}
|
}
|
||||||
|
@ -238,7 +248,15 @@ func updateNetworkAclEntries(d *schema.ResourceData, entryType string, ec2conn *
|
||||||
}
|
}
|
||||||
for _, add := range toBeCreated {
|
for _, add := range toBeCreated {
|
||||||
// Add new Acl entry
|
// Add new Acl entry
|
||||||
_, err := ec2conn.CreateNetworkAclEntry(d.Id(), &add)
|
err := ec2conn.CreateNetworkACLEntry(&ec2.CreateNetworkACLEntryRequest{
|
||||||
|
NetworkACLID: aws.String(d.Id()),
|
||||||
|
CIDRBlock: add.CIDRBlock,
|
||||||
|
Egress: add.Egress,
|
||||||
|
PortRange: add.PortRange,
|
||||||
|
Protocol: add.Protocol,
|
||||||
|
RuleAction: add.RuleAction,
|
||||||
|
RuleNumber: add.RuleNumber,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error creating %s entry: %s", entryType, err)
|
return fmt.Errorf("Error creating %s entry: %s", entryType, err)
|
||||||
}
|
}
|
||||||
|
@ -247,12 +265,15 @@ func updateNetworkAclEntries(d *schema.ResourceData, entryType string, ec2conn *
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
log.Printf("[INFO] Deleting Network Acl: %s", d.Id())
|
log.Printf("[INFO] Deleting Network Acl: %s", d.Id())
|
||||||
return resource.Retry(5*time.Minute, func() error {
|
return resource.Retry(5*time.Minute, func() error {
|
||||||
if _, err := ec2conn.DeleteNetworkAcl(d.Id()); err != nil {
|
err := ec2conn.DeleteNetworkACL(&ec2.DeleteNetworkACLRequest{
|
||||||
ec2err := err.(*ec2.Error)
|
NetworkACLID: aws.String(d.Id()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec2err := err.(aws.APIError)
|
||||||
switch ec2err.Code {
|
switch ec2err.Code {
|
||||||
case "InvalidNetworkAclID.NotFound":
|
case "InvalidNetworkAclID.NotFound":
|
||||||
return nil
|
return nil
|
||||||
|
@ -267,7 +288,10 @@ func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err)
|
return fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err)
|
||||||
}
|
}
|
||||||
_, err = ec2conn.ReplaceNetworkAclAssociation(association.NetworkAclAssociationId, defaultAcl.NetworkAclId)
|
_, err = ec2conn.ReplaceNetworkACLAssociation(&ec2.ReplaceNetworkACLAssociationRequest{
|
||||||
|
AssociationID: association.NetworkACLAssociationID,
|
||||||
|
NetworkACLID: defaultAcl.NetworkACLID,
|
||||||
|
})
|
||||||
return resource.RetryError{Err: err}
|
return resource.RetryError{Err: err}
|
||||||
default:
|
default:
|
||||||
// Any other error, we want to quit the retry loop immediately
|
// Any other error, we want to quit the retry loop immediately
|
||||||
|
@ -296,30 +320,43 @@ func resourceAwsNetworkAclEntryHash(v interface{}) int {
|
||||||
return hashcode.String(buf.String())
|
return hashcode.String(buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultNetworkAcl(vpc_id string, ec2conn *ec2.EC2) (defaultAcl *ec2.NetworkAcl, err error) {
|
func getDefaultNetworkAcl(vpc_id string, ec2conn *ec2.EC2) (defaultAcl *ec2.NetworkACL, err error) {
|
||||||
filter := ec2.NewFilter()
|
resp, err := ec2conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
|
||||||
filter.Add("default", "true")
|
NetworkACLIDs: []string{},
|
||||||
filter.Add("vpc-id", vpc_id)
|
Filters: []ec2.Filter{
|
||||||
|
ec2.Filter{
|
||||||
resp, err := ec2conn.NetworkAcls([]string{}, filter)
|
Name: aws.String("default"),
|
||||||
|
Values: []string{"true"},
|
||||||
|
},
|
||||||
|
ec2.Filter{
|
||||||
|
Name: aws.String("vpc-id"),
|
||||||
|
Values: []string{vpc_id},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &resp.NetworkAcls[0], nil
|
return &resp.NetworkACLs[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findNetworkAclAssociation(subnetId string, ec2conn *ec2.EC2) (networkAclAssociation *ec2.NetworkAclAssociation, err error) {
|
func findNetworkAclAssociation(subnetId string, ec2conn *ec2.EC2) (networkAclAssociation *ec2.NetworkACLAssociation, err error) {
|
||||||
filter := ec2.NewFilter()
|
resp, err := ec2conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
|
||||||
filter.Add("association.subnet-id", subnetId)
|
NetworkACLIDs: []string{},
|
||||||
|
Filters: []ec2.Filter{
|
||||||
resp, err := ec2conn.NetworkAcls([]string{}, filter)
|
ec2.Filter{
|
||||||
|
Name: aws.String("association.subnet-id"),
|
||||||
|
Values: []string{subnetId},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, association := range resp.NetworkAcls[0].AssociationSet {
|
for _, association := range resp.NetworkACLs[0].Associations {
|
||||||
if association.SubnetId == subnetId {
|
if *association.SubnetID == subnetId {
|
||||||
return &association, nil
|
return &association, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,16 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
// "github.com/hashicorp/terraform/helper/hashcode"
|
// "github.com/hashicorp/terraform/helper/hashcode"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
// "github.com/hashicorp/terraform/helper/schema"
|
// "github.com/hashicorp/terraform/helper/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAWSNetworkAclsWithEgressAndIngressRules(t *testing.T) {
|
func TestAccAWSNetworkAclsWithEgressAndIngressRules(t *testing.T) {
|
||||||
var networkAcl ec2.NetworkAcl
|
var networkAcl ec2.NetworkACL
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -24,29 +25,29 @@ func TestAccAWSNetworkAclsWithEgressAndIngressRules(t *testing.T) {
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckAWSNetworkAclExists("aws_network_acl.bar", &networkAcl),
|
testAccCheckAWSNetworkAclExists("aws_network_acl.bar", &networkAcl),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.bar", "ingress.580214135.protocol", "tcp"),
|
"aws_network_acl.bar", "ingress.3409203205.protocol", "tcp"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.bar", "ingress.580214135.rule_no", "1"),
|
"aws_network_acl.bar", "ingress.3409203205.rule_no", "1"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.bar", "ingress.580214135.from_port", "80"),
|
"aws_network_acl.bar", "ingress.3409203205.from_port", "80"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.bar", "ingress.580214135.to_port", "80"),
|
"aws_network_acl.bar", "ingress.3409203205.to_port", "80"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.bar", "ingress.580214135.action", "allow"),
|
"aws_network_acl.bar", "ingress.3409203205.action", "allow"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.bar", "ingress.580214135.cidr_block", "10.3.10.3/18"),
|
"aws_network_acl.bar", "ingress.3409203205.cidr_block", "10.3.10.3/18"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.bar", "egress.1730430240.protocol", "tcp"),
|
"aws_network_acl.bar", "egress.2579689292.protocol", "tcp"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.bar", "egress.1730430240.rule_no", "2"),
|
"aws_network_acl.bar", "egress.2579689292.rule_no", "2"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.bar", "egress.1730430240.from_port", "443"),
|
"aws_network_acl.bar", "egress.2579689292.from_port", "443"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.bar", "egress.1730430240.to_port", "443"),
|
"aws_network_acl.bar", "egress.2579689292.to_port", "443"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.bar", "egress.1730430240.cidr_block", "10.3.2.3/18"),
|
"aws_network_acl.bar", "egress.2579689292.cidr_block", "10.3.2.3/18"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.bar", "egress.1730430240.action", "allow"),
|
"aws_network_acl.bar", "egress.2579689292.action", "allow"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -54,7 +55,7 @@ func TestAccAWSNetworkAclsWithEgressAndIngressRules(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccAWSNetworkAclsOnlyIngressRules(t *testing.T) {
|
func TestAccAWSNetworkAclsOnlyIngressRules(t *testing.T) {
|
||||||
var networkAcl ec2.NetworkAcl
|
var networkAcl ec2.NetworkACL
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -67,17 +68,17 @@ func TestAccAWSNetworkAclsOnlyIngressRules(t *testing.T) {
|
||||||
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
|
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
|
||||||
// testAccCheckSubnetAssociation("aws_network_acl.foos", "aws_subnet.blob"),
|
// testAccCheckSubnetAssociation("aws_network_acl.foos", "aws_subnet.blob"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.protocol", "tcp"),
|
"aws_network_acl.foos", "ingress.2750166237.protocol", "tcp"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.rule_no", "1"),
|
"aws_network_acl.foos", "ingress.2750166237.rule_no", "1"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.from_port", "0"),
|
"aws_network_acl.foos", "ingress.2750166237.from_port", "0"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.to_port", "22"),
|
"aws_network_acl.foos", "ingress.2750166237.to_port", "22"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.action", "deny"),
|
"aws_network_acl.foos", "ingress.2750166237.action", "deny"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.cidr_block", "10.2.2.3/18"),
|
"aws_network_acl.foos", "ingress.2750166237.cidr_block", "10.2.2.3/18"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -85,7 +86,7 @@ func TestAccAWSNetworkAclsOnlyIngressRules(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccAWSNetworkAclsOnlyIngressRulesChange(t *testing.T) {
|
func TestAccAWSNetworkAclsOnlyIngressRulesChange(t *testing.T) {
|
||||||
var networkAcl ec2.NetworkAcl
|
var networkAcl ec2.NetworkACL
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -98,21 +99,21 @@ func TestAccAWSNetworkAclsOnlyIngressRulesChange(t *testing.T) {
|
||||||
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
|
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
|
||||||
testIngressRuleLength(&networkAcl, 2),
|
testIngressRuleLength(&networkAcl, 2),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.protocol", "tcp"),
|
"aws_network_acl.foos", "ingress.37211640.protocol", "tcp"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.rule_no", "1"),
|
"aws_network_acl.foos", "ingress.37211640.rule_no", "1"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.from_port", "0"),
|
"aws_network_acl.foos", "ingress.37211640.from_port", "0"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.to_port", "22"),
|
"aws_network_acl.foos", "ingress.37211640.to_port", "22"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.action", "deny"),
|
"aws_network_acl.foos", "ingress.37211640.action", "deny"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.cidr_block", "10.2.2.3/18"),
|
"aws_network_acl.foos", "ingress.37211640.cidr_block", "10.2.2.3/18"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.2438803013.from_port", "443"),
|
"aws_network_acl.foos", "ingress.2750166237.from_port", "443"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.2438803013.rule_no", "2"),
|
"aws_network_acl.foos", "ingress.2750166237.rule_no", "2"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
resource.TestStep{
|
resource.TestStep{
|
||||||
|
@ -121,17 +122,17 @@ func TestAccAWSNetworkAclsOnlyIngressRulesChange(t *testing.T) {
|
||||||
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
|
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
|
||||||
testIngressRuleLength(&networkAcl, 1),
|
testIngressRuleLength(&networkAcl, 1),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.protocol", "tcp"),
|
"aws_network_acl.foos", "ingress.37211640.protocol", "tcp"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.rule_no", "1"),
|
"aws_network_acl.foos", "ingress.37211640.rule_no", "1"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.from_port", "0"),
|
"aws_network_acl.foos", "ingress.37211640.from_port", "0"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.to_port", "22"),
|
"aws_network_acl.foos", "ingress.37211640.to_port", "22"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.action", "deny"),
|
"aws_network_acl.foos", "ingress.37211640.action", "deny"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_network_acl.foos", "ingress.3697634361.cidr_block", "10.2.2.3/18"),
|
"aws_network_acl.foos", "ingress.37211640.cidr_block", "10.2.2.3/18"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -139,7 +140,7 @@ func TestAccAWSNetworkAclsOnlyIngressRulesChange(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccAWSNetworkAclsOnlyEgressRules(t *testing.T) {
|
func TestAccAWSNetworkAclsOnlyEgressRules(t *testing.T) {
|
||||||
var networkAcl ec2.NetworkAcl
|
var networkAcl ec2.NetworkACL
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -150,14 +151,14 @@ func TestAccAWSNetworkAclsOnlyEgressRules(t *testing.T) {
|
||||||
Config: testAccAWSNetworkAclEgressConfig,
|
Config: testAccAWSNetworkAclEgressConfig,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckAWSNetworkAclExists("aws_network_acl.bond", &networkAcl),
|
testAccCheckAWSNetworkAclExists("aws_network_acl.bond", &networkAcl),
|
||||||
testAccCheckTags(&networkAcl.Tags, "foo", "bar"),
|
testAccCheckTagsSDK(&networkAcl.Tags, "foo", "bar"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccNetworkAcl_SubnetChange(t *testing.T) {
|
func TestAccAWSNetworkAcl_SubnetChange(t *testing.T) {
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -183,7 +184,7 @@ func TestAccNetworkAcl_SubnetChange(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckAWSNetworkAclDestroy(s *terraform.State) error {
|
func testAccCheckAWSNetworkAclDestroy(s *terraform.State) error {
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
if rs.Type != "aws_network" {
|
if rs.Type != "aws_network" {
|
||||||
|
@ -191,16 +192,18 @@ func testAccCheckAWSNetworkAclDestroy(s *terraform.State) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the network acl
|
// Retrieve the network acl
|
||||||
resp, err := conn.NetworkAcls([]string{rs.Primary.ID}, ec2.NewFilter())
|
resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
|
||||||
|
NetworkACLIDs: []string{rs.Primary.ID},
|
||||||
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if len(resp.NetworkAcls) > 0 && resp.NetworkAcls[0].NetworkAclId == rs.Primary.ID {
|
if len(resp.NetworkACLs) > 0 && *resp.NetworkACLs[0].NetworkACLID == rs.Primary.ID {
|
||||||
return fmt.Errorf("Network Acl (%s) still exists.", rs.Primary.ID)
|
return fmt.Errorf("Network Acl (%s) still exists.", rs.Primary.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ec2err, ok := err.(*ec2.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if !ok {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -213,7 +216,7 @@ func testAccCheckAWSNetworkAclDestroy(s *terraform.State) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckAWSNetworkAclExists(n string, networkAcl *ec2.NetworkAcl) resource.TestCheckFunc {
|
func testAccCheckAWSNetworkAclExists(n string, networkAcl *ec2.NetworkACL) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
rs, ok := s.RootModule().Resources[n]
|
rs, ok := s.RootModule().Resources[n]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -223,15 +226,17 @@ func testAccCheckAWSNetworkAclExists(n string, networkAcl *ec2.NetworkAcl) resou
|
||||||
if rs.Primary.ID == "" {
|
if rs.Primary.ID == "" {
|
||||||
return fmt.Errorf("No Security Group is set")
|
return fmt.Errorf("No Security Group is set")
|
||||||
}
|
}
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
resp, err := conn.NetworkAcls([]string{rs.Primary.ID}, nil)
|
resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
|
||||||
|
NetworkACLIDs: []string{rs.Primary.ID},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resp.NetworkAcls) > 0 && resp.NetworkAcls[0].NetworkAclId == rs.Primary.ID {
|
if len(resp.NetworkACLs) > 0 && *resp.NetworkACLs[0].NetworkACLID == rs.Primary.ID {
|
||||||
*networkAcl = resp.NetworkAcls[0]
|
*networkAcl = resp.NetworkACLs[0]
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,11 +244,11 @@ func testAccCheckAWSNetworkAclExists(n string, networkAcl *ec2.NetworkAcl) resou
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testIngressRuleLength(networkAcl *ec2.NetworkAcl, length int) resource.TestCheckFunc {
|
func testIngressRuleLength(networkAcl *ec2.NetworkACL, length int) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
var ingressEntries []ec2.NetworkAclEntry
|
var ingressEntries []ec2.NetworkACLEntry
|
||||||
for _, e := range networkAcl.EntrySet {
|
for _, e := range networkAcl.Entries {
|
||||||
if e.Egress == false {
|
if *e.Egress == false {
|
||||||
ingressEntries = append(ingressEntries, e)
|
ingressEntries = append(ingressEntries, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,21 +266,26 @@ func testAccCheckSubnetIsAssociatedWithAcl(acl string, sub string) resource.Test
|
||||||
networkAcl := s.RootModule().Resources[acl]
|
networkAcl := s.RootModule().Resources[acl]
|
||||||
subnet := s.RootModule().Resources[sub]
|
subnet := s.RootModule().Resources[sub]
|
||||||
|
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
filter := ec2.NewFilter()
|
resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
|
||||||
filter.Add("association.subnet-id", subnet.Primary.ID)
|
NetworkACLIDs: []string{networkAcl.Primary.ID},
|
||||||
resp, err := conn.NetworkAcls([]string{networkAcl.Primary.ID}, filter)
|
Filters: []ec2.Filter{
|
||||||
|
ec2.Filter{
|
||||||
|
Name: aws.String("association.subnet-id"),
|
||||||
|
Values: []string{subnet.Primary.ID},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(resp.NetworkAcls) > 0 {
|
if len(resp.NetworkACLs) > 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
r, _ := conn.NetworkAcls([]string{}, ec2.NewFilter())
|
// r, _ := conn.NetworkACLs([]string{}, ec2.NewFilter())
|
||||||
fmt.Printf("\n\nall acls\n %#v\n\n", r.NetworkAcls)
|
// fmt.Printf("\n\nall acls\n %#v\n\n", r.NetworkAcls)
|
||||||
conn.NetworkAcls([]string{}, filter)
|
// conn.NetworkAcls([]string{}, filter)
|
||||||
|
|
||||||
return fmt.Errorf("Network Acl %s is not associated with subnet %s", acl, sub)
|
return fmt.Errorf("Network Acl %s is not associated with subnet %s", acl, sub)
|
||||||
}
|
}
|
||||||
|
@ -286,15 +296,21 @@ func testAccCheckSubnetIsNotAssociatedWithAcl(acl string, subnet string) resourc
|
||||||
networkAcl := s.RootModule().Resources[acl]
|
networkAcl := s.RootModule().Resources[acl]
|
||||||
subnet := s.RootModule().Resources[subnet]
|
subnet := s.RootModule().Resources[subnet]
|
||||||
|
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
filter := ec2.NewFilter()
|
resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{
|
||||||
filter.Add("association.subnet-id", subnet.Primary.ID)
|
NetworkACLIDs: []string{networkAcl.Primary.ID},
|
||||||
resp, err := conn.NetworkAcls([]string{networkAcl.Primary.ID}, filter)
|
Filters: []ec2.Filter{
|
||||||
|
ec2.Filter{
|
||||||
|
Name: aws.String("association.subnet-id"),
|
||||||
|
Values: []string{subnet.Primary.ID},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(resp.NetworkAcls) > 0 {
|
if len(resp.NetworkACLs) > 0 {
|
||||||
return fmt.Errorf("Network Acl %s is still associated with subnet %s", acl, subnet)
|
return fmt.Errorf("Network Acl %s is still associated with subnet %s", acl, subnet)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -138,7 +138,7 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er
|
||||||
Delay: 30 * time.Second,
|
Delay: 30 * time.Second,
|
||||||
Pending: []string{"PENDING"},
|
Pending: []string{"PENDING"},
|
||||||
Target: "INSYNC",
|
Target: "INSYNC",
|
||||||
Timeout: 10 * time.Minute,
|
Timeout: 30 * time.Minute,
|
||||||
MinTimeout: 5 * time.Second,
|
MinTimeout: 5 * time.Second,
|
||||||
Refresh: func() (result interface{}, state string, err error) {
|
Refresh: func() (result interface{}, state string, err error) {
|
||||||
changeRequest := &route53.GetChangeRequest{
|
changeRequest := &route53.GetChangeRequest{
|
||||||
|
|
|
@ -6,10 +6,11 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/hashcode"
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceAwsRouteTable() *schema.Resource {
|
func resourceAwsRouteTable() *schema.Resource {
|
||||||
|
@ -61,11 +62,11 @@ func resourceAwsRouteTable() *schema.Resource {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsRouteTableCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsRouteTableCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
// Create the routing table
|
// Create the routing table
|
||||||
createOpts := &ec2.CreateRouteTable{
|
createOpts := &ec2.CreateRouteTableRequest{
|
||||||
VpcId: d.Get("vpc_id").(string),
|
VPCID: aws.String(d.Get("vpc_id").(string)),
|
||||||
}
|
}
|
||||||
log.Printf("[DEBUG] RouteTable create config: %#v", createOpts)
|
log.Printf("[DEBUG] RouteTable create config: %#v", createOpts)
|
||||||
|
|
||||||
|
@ -75,8 +76,8 @@ func resourceAwsRouteTableCreate(d *schema.ResourceData, meta interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the ID and store it
|
// Get the ID and store it
|
||||||
rt := &resp.RouteTable
|
rt := resp.RouteTable
|
||||||
d.SetId(rt.RouteTableId)
|
d.SetId(*rt.RouteTableID)
|
||||||
log.Printf("[INFO] Route Table ID: %s", d.Id())
|
log.Printf("[INFO] Route Table ID: %s", d.Id())
|
||||||
|
|
||||||
// Wait for the route table to become available
|
// Wait for the route table to become available
|
||||||
|
@ -99,7 +100,7 @@ func resourceAwsRouteTableCreate(d *schema.ResourceData, meta interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(ec2conn, d.Id())()
|
rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(ec2conn, d.Id())()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -110,40 +111,48 @@ func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
rt := rtRaw.(*ec2.RouteTable)
|
rt := rtRaw.(*ec2.RouteTable)
|
||||||
d.Set("vpc_id", rt.VpcId)
|
d.Set("vpc_id", rt.VPCID)
|
||||||
|
|
||||||
// Create an empty schema.Set to hold all routes
|
// Create an empty schema.Set to hold all routes
|
||||||
route := &schema.Set{F: resourceAwsRouteTableHash}
|
route := &schema.Set{F: resourceAwsRouteTableHash}
|
||||||
|
|
||||||
// Loop through the routes and add them to the set
|
// Loop through the routes and add them to the set
|
||||||
for _, r := range rt.Routes {
|
for _, r := range rt.Routes {
|
||||||
if r.GatewayId == "local" {
|
if r.GatewayID != nil && *r.GatewayID == "local" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Origin == "EnableVgwRoutePropagation" {
|
if r.Origin != nil && *r.Origin == "EnableVgwRoutePropagation" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
m := make(map[string]interface{})
|
m := make(map[string]interface{})
|
||||||
m["cidr_block"] = r.DestinationCidrBlock
|
|
||||||
|
|
||||||
m["gateway_id"] = r.GatewayId
|
if r.DestinationCIDRBlock != nil {
|
||||||
m["instance_id"] = r.InstanceId
|
m["cidr_block"] = *r.DestinationCIDRBlock
|
||||||
m["vpc_peering_connection_id"] = r.VpcPeeringConnectionId
|
}
|
||||||
|
if r.GatewayID != nil {
|
||||||
|
m["gateway_id"] = *r.GatewayID
|
||||||
|
}
|
||||||
|
if r.InstanceID != nil {
|
||||||
|
m["instance_id"] = *r.InstanceID
|
||||||
|
}
|
||||||
|
if r.VPCPeeringConnectionID != nil {
|
||||||
|
m["vpc_peering_connection_id"] = *r.VPCPeeringConnectionID
|
||||||
|
}
|
||||||
|
|
||||||
route.Add(m)
|
route.Add(m)
|
||||||
}
|
}
|
||||||
d.Set("route", route)
|
d.Set("route", route)
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
d.Set("tags", tagsToMap(rt.Tags))
|
d.Set("tags", tagsToMapSDK(rt.Tags))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
// Check if the route set as a whole has changed
|
// Check if the route set as a whole has changed
|
||||||
if d.HasChange("route") {
|
if d.HasChange("route") {
|
||||||
|
@ -159,8 +168,10 @@ func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"[INFO] Deleting route from %s: %s",
|
"[INFO] Deleting route from %s: %s",
|
||||||
d.Id(), m["cidr_block"].(string))
|
d.Id(), m["cidr_block"].(string))
|
||||||
_, err := ec2conn.DeleteRoute(
|
err := ec2conn.DeleteRoute(&ec2.DeleteRouteRequest{
|
||||||
d.Id(), m["cidr_block"].(string))
|
RouteTableID: aws.String(d.Id()),
|
||||||
|
DestinationCIDRBlock: aws.String(m["cidr_block"].(string)),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -174,17 +185,16 @@ func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error
|
||||||
for _, route := range nrs.List() {
|
for _, route := range nrs.List() {
|
||||||
m := route.(map[string]interface{})
|
m := route.(map[string]interface{})
|
||||||
|
|
||||||
opts := ec2.CreateRoute{
|
opts := ec2.CreateRouteRequest{
|
||||||
RouteTableId: d.Id(),
|
RouteTableID: aws.String(d.Id()),
|
||||||
DestinationCidrBlock: m["cidr_block"].(string),
|
DestinationCIDRBlock: aws.String(m["cidr_block"].(string)),
|
||||||
GatewayId: m["gateway_id"].(string),
|
GatewayID: aws.String(m["gateway_id"].(string)),
|
||||||
InstanceId: m["instance_id"].(string),
|
InstanceID: aws.String(m["instance_id"].(string)),
|
||||||
VpcPeeringConnectionId: m["vpc_peering_connection_id"].(string),
|
VPCPeeringConnectionID: aws.String(m["vpc_peering_connection_id"].(string)),
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] Creating route for %s: %#v", d.Id(), opts)
|
log.Printf("[INFO] Creating route for %s: %#v", d.Id(), opts)
|
||||||
_, err := ec2conn.CreateRoute(&opts)
|
if err := ec2conn.CreateRoute(&opts); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +203,7 @@ func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setTags(ec2conn, d); err != nil {
|
if err := setTagsSDK(ec2conn, d); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
d.SetPartial("tags")
|
d.SetPartial("tags")
|
||||||
|
@ -203,7 +213,7 @@ func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsRouteTableDelete(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsRouteTableDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
// First request the routing table since we'll have to disassociate
|
// First request the routing table since we'll have to disassociate
|
||||||
// all the subnets first.
|
// all the subnets first.
|
||||||
|
@ -218,16 +228,22 @@ func resourceAwsRouteTableDelete(d *schema.ResourceData, meta interface{}) error
|
||||||
|
|
||||||
// Do all the disassociations
|
// Do all the disassociations
|
||||||
for _, a := range rt.Associations {
|
for _, a := range rt.Associations {
|
||||||
log.Printf("[INFO] Disassociating association: %s", a.AssociationId)
|
log.Printf("[INFO] Disassociating association: %s", *a.RouteTableAssociationID)
|
||||||
if _, err := ec2conn.DisassociateRouteTable(a.AssociationId); err != nil {
|
err := ec2conn.DisassociateRouteTable(&ec2.DisassociateRouteTableRequest{
|
||||||
|
AssociationID: a.RouteTableAssociationID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the route table
|
// Delete the route table
|
||||||
log.Printf("[INFO] Deleting Route Table: %s", d.Id())
|
log.Printf("[INFO] Deleting Route Table: %s", d.Id())
|
||||||
if _, err := ec2conn.DeleteRouteTable(d.Id()); err != nil {
|
err = ec2conn.DeleteRouteTable(&ec2.DeleteRouteTableRequest{
|
||||||
ec2err, ok := err.(*ec2.Error)
|
RouteTableID: aws.String(d.Id()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec2err, ok := err.(aws.APIError)
|
||||||
if ok && ec2err.Code == "InvalidRouteTableID.NotFound" {
|
if ok && ec2err.Code == "InvalidRouteTableID.NotFound" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -279,9 +295,11 @@ func resourceAwsRouteTableHash(v interface{}) int {
|
||||||
// a RouteTable.
|
// a RouteTable.
|
||||||
func resourceAwsRouteTableStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
|
func resourceAwsRouteTableStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
|
||||||
return func() (interface{}, string, error) {
|
return func() (interface{}, string, error) {
|
||||||
resp, err := conn.DescribeRouteTables([]string{id}, ec2.NewFilter())
|
resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
|
||||||
|
RouteTableIDs: []string{id},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidRouteTableID.NotFound" {
|
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidRouteTableID.NotFound" {
|
||||||
resp = nil
|
resp = nil
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Error on RouteTableStateRefresh: %s", err)
|
log.Printf("Error on RouteTableStateRefresh: %s", err)
|
||||||
|
|
|
@ -4,8 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceAwsRouteTableAssociation() *schema.Resource {
|
func resourceAwsRouteTableAssociation() *schema.Resource {
|
||||||
|
@ -31,30 +32,31 @@ func resourceAwsRouteTableAssociation() *schema.Resource {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsRouteTableAssociationCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsRouteTableAssociationCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"[INFO] Creating route table association: %s => %s",
|
"[INFO] Creating route table association: %s => %s",
|
||||||
d.Get("subnet_id").(string),
|
d.Get("subnet_id").(string),
|
||||||
d.Get("route_table_id").(string))
|
d.Get("route_table_id").(string))
|
||||||
|
|
||||||
resp, err := ec2conn.AssociateRouteTable(
|
resp, err := ec2conn.AssociateRouteTable(&ec2.AssociateRouteTableRequest{
|
||||||
d.Get("route_table_id").(string),
|
RouteTableID: aws.String(d.Get("route_table_id").(string)),
|
||||||
d.Get("subnet_id").(string))
|
SubnetID: aws.String(d.Get("subnet_id").(string)),
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the ID and return
|
// Set the ID and return
|
||||||
d.SetId(resp.AssociationId)
|
d.SetId(*resp.AssociationID)
|
||||||
log.Printf("[INFO] Association ID: %s", d.Id())
|
log.Printf("[INFO] Association ID: %s", d.Id())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsRouteTableAssociationRead(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsRouteTableAssociationRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
// Get the routing table that this association belongs to
|
// Get the routing table that this association belongs to
|
||||||
rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(
|
rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(
|
||||||
|
@ -70,9 +72,9 @@ func resourceAwsRouteTableAssociationRead(d *schema.ResourceData, meta interface
|
||||||
// Inspect that the association exists
|
// Inspect that the association exists
|
||||||
found := false
|
found := false
|
||||||
for _, a := range rt.Associations {
|
for _, a := range rt.Associations {
|
||||||
if a.AssociationId == d.Id() {
|
if *a.RouteTableAssociationID == d.Id() {
|
||||||
found = true
|
found = true
|
||||||
d.Set("subnet_id", a.SubnetId)
|
d.Set("subnet_id", *a.SubnetID)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,19 +88,21 @@ func resourceAwsRouteTableAssociationRead(d *schema.ResourceData, meta interface
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsRouteTableAssociationUpdate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsRouteTableAssociationUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"[INFO] Creating route table association: %s => %s",
|
"[INFO] Creating route table association: %s => %s",
|
||||||
d.Get("subnet_id").(string),
|
d.Get("subnet_id").(string),
|
||||||
d.Get("route_table_id").(string))
|
d.Get("route_table_id").(string))
|
||||||
|
|
||||||
resp, err := ec2conn.ReassociateRouteTable(
|
req := &ec2.ReplaceRouteTableAssociationRequest{
|
||||||
d.Id(),
|
AssociationID: aws.String(d.Id()),
|
||||||
d.Get("route_table_id").(string))
|
RouteTableID: aws.String(d.Get("route_table_id").(string)),
|
||||||
|
}
|
||||||
|
resp, err := ec2conn.ReplaceRouteTableAssociation(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ec2err, ok := err.(*ec2.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if ok && ec2err.Code == "InvalidAssociationID.NotFound" {
|
if ok && ec2err.Code == "InvalidAssociationID.NotFound" {
|
||||||
// Not found, so just create a new one
|
// Not found, so just create a new one
|
||||||
return resourceAwsRouteTableAssociationCreate(d, meta)
|
return resourceAwsRouteTableAssociationCreate(d, meta)
|
||||||
|
@ -108,18 +112,21 @@ func resourceAwsRouteTableAssociationUpdate(d *schema.ResourceData, meta interfa
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the ID
|
// Update the ID
|
||||||
d.SetId(resp.AssociationId)
|
d.SetId(*resp.NewAssociationID)
|
||||||
log.Printf("[INFO] Association ID: %s", d.Id())
|
log.Printf("[INFO] Association ID: %s", d.Id())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsRouteTableAssociationDelete(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsRouteTableAssociationDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
log.Printf("[INFO] Deleting route table association: %s", d.Id())
|
log.Printf("[INFO] Deleting route table association: %s", d.Id())
|
||||||
if _, err := ec2conn.DisassociateRouteTable(d.Id()); err != nil {
|
err := ec2conn.DisassociateRouteTable(&ec2.DisassociateRouteTableRequest{
|
||||||
ec2err, ok := err.(*ec2.Error)
|
AssociationID: aws.String(d.Id()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec2err, ok := err.(aws.APIError)
|
||||||
if ok && ec2err.Code == "InvalidAssociationID.NotFound" {
|
if ok && ec2err.Code == "InvalidAssociationID.NotFound" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAWSRouteTableAssociation(t *testing.T) {
|
func TestAccAWSRouteTableAssociation(t *testing.T) {
|
||||||
|
@ -37,7 +38,7 @@ func TestAccAWSRouteTableAssociation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckRouteTableAssociationDestroy(s *terraform.State) error {
|
func testAccCheckRouteTableAssociationDestroy(s *terraform.State) error {
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
if rs.Type != "aws_route_table_association" {
|
if rs.Type != "aws_route_table_association" {
|
||||||
|
@ -45,11 +46,12 @@ func testAccCheckRouteTableAssociationDestroy(s *terraform.State) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find the resource
|
// Try to find the resource
|
||||||
resp, err := conn.DescribeRouteTables(
|
resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
|
||||||
[]string{rs.Primary.Attributes["route_table_Id"]}, ec2.NewFilter())
|
RouteTableIDs: []string{rs.Primary.Attributes["route_table_id"]},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Verify the error is what we want
|
// Verify the error is what we want
|
||||||
ec2err, ok := err.(*ec2.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if !ok {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -62,7 +64,7 @@ func testAccCheckRouteTableAssociationDestroy(s *terraform.State) error {
|
||||||
rt := resp.RouteTables[0]
|
rt := resp.RouteTables[0]
|
||||||
if len(rt.Associations) > 0 {
|
if len(rt.Associations) > 0 {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"route table %s has associations", rt.RouteTableId)
|
"route table %s has associations", *rt.RouteTableID)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,9 +83,10 @@ func testAccCheckRouteTableAssociationExists(n string, v *ec2.RouteTable) resour
|
||||||
return fmt.Errorf("No ID is set")
|
return fmt.Errorf("No ID is set")
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
resp, err := conn.DescribeRouteTables(
|
resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
|
||||||
[]string{rs.Primary.Attributes["route_table_id"]}, ec2.NewFilter())
|
RouteTableIDs: []string{rs.Primary.Attributes["route_table_id"]},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAWSRouteTable_normal(t *testing.T) {
|
func TestAccAWSRouteTable_normal(t *testing.T) {
|
||||||
|
@ -19,7 +20,7 @@ func TestAccAWSRouteTable_normal(t *testing.T) {
|
||||||
|
|
||||||
routes := make(map[string]ec2.Route)
|
routes := make(map[string]ec2.Route)
|
||||||
for _, r := range v.Routes {
|
for _, r := range v.Routes {
|
||||||
routes[r.DestinationCidrBlock] = r
|
routes[*r.DestinationCIDRBlock] = r
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := routes["10.1.0.0/16"]; !ok {
|
if _, ok := routes["10.1.0.0/16"]; !ok {
|
||||||
|
@ -39,7 +40,7 @@ func TestAccAWSRouteTable_normal(t *testing.T) {
|
||||||
|
|
||||||
routes := make(map[string]ec2.Route)
|
routes := make(map[string]ec2.Route)
|
||||||
for _, r := range v.Routes {
|
for _, r := range v.Routes {
|
||||||
routes[r.DestinationCidrBlock] = r
|
routes[*r.DestinationCIDRBlock] = r
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := routes["10.1.0.0/16"]; !ok {
|
if _, ok := routes["10.1.0.0/16"]; !ok {
|
||||||
|
@ -91,7 +92,7 @@ func TestAccAWSRouteTable_instance(t *testing.T) {
|
||||||
|
|
||||||
routes := make(map[string]ec2.Route)
|
routes := make(map[string]ec2.Route)
|
||||||
for _, r := range v.Routes {
|
for _, r := range v.Routes {
|
||||||
routes[r.DestinationCidrBlock] = r
|
routes[*r.DestinationCIDRBlock] = r
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := routes["10.1.0.0/16"]; !ok {
|
if _, ok := routes["10.1.0.0/16"]; !ok {
|
||||||
|
@ -133,7 +134,7 @@ func TestAccAWSRouteTable_tags(t *testing.T) {
|
||||||
Config: testAccRouteTableConfigTags,
|
Config: testAccRouteTableConfigTags,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckRouteTableExists("aws_route_table.foo", &route_table),
|
testAccCheckRouteTableExists("aws_route_table.foo", &route_table),
|
||||||
testAccCheckTags(&route_table.Tags, "foo", "bar"),
|
testAccCheckTagsSDK(&route_table.Tags, "foo", "bar"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -141,8 +142,8 @@ func TestAccAWSRouteTable_tags(t *testing.T) {
|
||||||
Config: testAccRouteTableConfigTagsUpdate,
|
Config: testAccRouteTableConfigTagsUpdate,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckRouteTableExists("aws_route_table.foo", &route_table),
|
testAccCheckRouteTableExists("aws_route_table.foo", &route_table),
|
||||||
testAccCheckTags(&route_table.Tags, "foo", ""),
|
testAccCheckTagsSDK(&route_table.Tags, "foo", ""),
|
||||||
testAccCheckTags(&route_table.Tags, "bar", "baz"),
|
testAccCheckTagsSDK(&route_table.Tags, "bar", "baz"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -150,7 +151,7 @@ func TestAccAWSRouteTable_tags(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckRouteTableDestroy(s *terraform.State) error {
|
func testAccCheckRouteTableDestroy(s *terraform.State) error {
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
if rs.Type != "aws_route_table" {
|
if rs.Type != "aws_route_table" {
|
||||||
|
@ -158,8 +159,9 @@ func testAccCheckRouteTableDestroy(s *terraform.State) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find the resource
|
// Try to find the resource
|
||||||
resp, err := conn.DescribeRouteTables(
|
resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
|
||||||
[]string{rs.Primary.ID}, ec2.NewFilter())
|
RouteTableIDs: []string{rs.Primary.ID},
|
||||||
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if len(resp.RouteTables) > 0 {
|
if len(resp.RouteTables) > 0 {
|
||||||
return fmt.Errorf("still exist.")
|
return fmt.Errorf("still exist.")
|
||||||
|
@ -169,7 +171,7 @@ func testAccCheckRouteTableDestroy(s *terraform.State) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the error is what we want
|
// Verify the error is what we want
|
||||||
ec2err, ok := err.(*ec2.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if !ok {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -192,9 +194,10 @@ func testAccCheckRouteTableExists(n string, v *ec2.RouteTable) resource.TestChec
|
||||||
return fmt.Errorf("No ID is set")
|
return fmt.Errorf("No ID is set")
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
resp, err := conn.DescribeRouteTables(
|
resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
|
||||||
[]string{rs.Primary.ID}, ec2.NewFilter())
|
RouteTableIDs: []string{rs.Primary.ID},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -208,7 +211,10 @@ func testAccCheckRouteTableExists(n string, v *ec2.RouteTable) resource.TestChec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccAWSRouteTable_vpcPeering(t *testing.T) {
|
// TODO: re-enable this test.
|
||||||
|
// VPC Peering connections are prefixed with pcx
|
||||||
|
// Right now there is no VPC Peering resource
|
||||||
|
func _TestAccAWSRouteTable_vpcPeering(t *testing.T) {
|
||||||
var v ec2.RouteTable
|
var v ec2.RouteTable
|
||||||
|
|
||||||
testCheck := func(*terraform.State) error {
|
testCheck := func(*terraform.State) error {
|
||||||
|
@ -218,7 +224,7 @@ func TestAccAWSRouteTable_vpcPeering(t *testing.T) {
|
||||||
|
|
||||||
routes := make(map[string]ec2.Route)
|
routes := make(map[string]ec2.Route)
|
||||||
for _, r := range v.Routes {
|
for _, r := range v.Routes {
|
||||||
routes[r.DestinationCidrBlock] = r
|
routes[*r.DestinationCIDRBlock] = r
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := routes["10.1.0.0/16"]; !ok {
|
if _, ok := routes["10.1.0.0/16"]; !ok {
|
||||||
|
@ -345,6 +351,9 @@ resource "aws_route_table" "foo" {
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// TODO: re-enable this test.
|
||||||
|
// VPC Peering connections are prefixed with pcx
|
||||||
|
// Right now there is no VPC Peering resource
|
||||||
const testAccRouteTableVpcPeeringConfig = `
|
const testAccRouteTableVpcPeeringConfig = `
|
||||||
resource "aws_vpc" "foo" {
|
resource "aws_vpc" "foo" {
|
||||||
cidr_block = "10.1.0.0/16"
|
cidr_block = "10.1.0.0/16"
|
||||||
|
@ -359,7 +368,7 @@ resource "aws_route_table" "foo" {
|
||||||
|
|
||||||
route {
|
route {
|
||||||
cidr_block = "10.2.0.0/16"
|
cidr_block = "10.2.0.0/16"
|
||||||
vpc_peering_connection_id = "vpc-12345"
|
vpc_peering_connection_id = "pcx-12345"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -7,10 +7,11 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/hashcode"
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceAwsSecurityGroup() *schema.Resource {
|
func resourceAwsSecurityGroup() *schema.Resource {
|
||||||
|
@ -141,18 +142,18 @@ func resourceAwsSecurityGroup() *schema.Resource {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
securityGroupOpts := ec2.SecurityGroup{
|
securityGroupOpts := &ec2.CreateSecurityGroupRequest{
|
||||||
Name: d.Get("name").(string),
|
GroupName: aws.String(d.Get("name").(string)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := d.Get("vpc_id"); v != nil {
|
if v := d.Get("vpc_id"); v != nil {
|
||||||
securityGroupOpts.VpcId = v.(string)
|
securityGroupOpts.VPCID = aws.String(v.(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := d.Get("description"); v != nil {
|
if v := d.Get("description"); v != nil {
|
||||||
securityGroupOpts.Description = v.(string)
|
securityGroupOpts.Description = aws.String(v.(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf(
|
log.Printf(
|
||||||
|
@ -162,7 +163,7 @@ func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) er
|
||||||
return fmt.Errorf("Error creating Security Group: %s", err)
|
return fmt.Errorf("Error creating Security Group: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.SetId(createResp.Id)
|
d.SetId(*createResp.GroupID)
|
||||||
|
|
||||||
log.Printf("[INFO] Security Group ID: %s", d.Id())
|
log.Printf("[INFO] Security Group ID: %s", d.Id())
|
||||||
|
|
||||||
|
@ -186,7 +187,7 @@ func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) er
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
|
sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -197,24 +198,23 @@ func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) erro
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sg := sgRaw.(*ec2.SecurityGroupInfo)
|
sg := sgRaw.(ec2.SecurityGroup)
|
||||||
|
|
||||||
ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPerms)
|
ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissions)
|
||||||
egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermsEgress)
|
egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissionsEgress)
|
||||||
|
|
||||||
d.Set("description", sg.Description)
|
d.Set("description", sg.Description)
|
||||||
d.Set("name", sg.Name)
|
d.Set("name", sg.GroupName)
|
||||||
d.Set("vpc_id", sg.VpcId)
|
d.Set("vpc_id", sg.VPCID)
|
||||||
d.Set("owner_id", sg.OwnerId)
|
d.Set("owner_id", sg.OwnerID)
|
||||||
d.Set("ingress", ingressRules)
|
d.Set("ingress", ingressRules)
|
||||||
d.Set("egress", egressRules)
|
d.Set("egress", egressRules)
|
||||||
d.Set("tags", tagsToMap(sg.Tags))
|
d.Set("tags", tagsToMapSDK(sg.Tags))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
|
sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -224,7 +224,8 @@ func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) er
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
group := sgRaw.(*ec2.SecurityGroupInfo).SecurityGroup
|
|
||||||
|
group := sgRaw.(ec2.SecurityGroup)
|
||||||
|
|
||||||
err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group)
|
err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -238,7 +239,7 @@ func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) er
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setTags(ec2conn, d); err != nil {
|
if err := setTagsSDK(ec2conn, d); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,14 +249,16 @@ func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) er
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
log.Printf("[DEBUG] Security Group destroy: %v", d.Id())
|
log.Printf("[DEBUG] Security Group destroy: %v", d.Id())
|
||||||
|
|
||||||
return resource.Retry(5*time.Minute, func() error {
|
return resource.Retry(5*time.Minute, func() error {
|
||||||
_, err := ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: d.Id()})
|
err := ec2conn.DeleteSecurityGroup(&ec2.DeleteSecurityGroupRequest{
|
||||||
|
GroupID: aws.String(d.Id()),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ec2err, ok := err.(*ec2.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if !ok {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -313,34 +316,45 @@ func resourceAwsSecurityGroupRuleHash(v interface{}) int {
|
||||||
return hashcode.String(buf.String())
|
return hashcode.String(buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []ec2.IPPerm) []map[string]interface{} {
|
func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []ec2.IPPermission) []map[string]interface{} {
|
||||||
ruleMap := make(map[string]map[string]interface{})
|
ruleMap := make(map[string]map[string]interface{})
|
||||||
for _, perm := range permissions {
|
for _, perm := range permissions {
|
||||||
k := fmt.Sprintf("%s-%d-%d", perm.Protocol, perm.FromPort, perm.ToPort)
|
var fromPort, toPort int
|
||||||
|
if v := perm.FromPort; v != nil {
|
||||||
|
fromPort = *v
|
||||||
|
}
|
||||||
|
if v := perm.ToPort; v != nil {
|
||||||
|
toPort = *v
|
||||||
|
}
|
||||||
|
|
||||||
|
k := fmt.Sprintf("%s-%d-%d", *perm.IPProtocol, fromPort, toPort)
|
||||||
m, ok := ruleMap[k]
|
m, ok := ruleMap[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
m = make(map[string]interface{})
|
m = make(map[string]interface{})
|
||||||
ruleMap[k] = m
|
ruleMap[k] = m
|
||||||
}
|
}
|
||||||
|
|
||||||
m["from_port"] = perm.FromPort
|
m["from_port"] = fromPort
|
||||||
m["to_port"] = perm.ToPort
|
m["to_port"] = toPort
|
||||||
m["protocol"] = perm.Protocol
|
m["protocol"] = *perm.IPProtocol
|
||||||
|
|
||||||
if len(perm.SourceIPs) > 0 {
|
if len(perm.IPRanges) > 0 {
|
||||||
raw, ok := m["cidr_blocks"]
|
raw, ok := m["cidr_blocks"]
|
||||||
if !ok {
|
if !ok {
|
||||||
raw = make([]string, 0, len(perm.SourceIPs))
|
raw = make([]string, 0, len(perm.IPRanges))
|
||||||
}
|
}
|
||||||
list := raw.([]string)
|
list := raw.([]string)
|
||||||
|
|
||||||
list = append(list, perm.SourceIPs...)
|
for _, ip := range perm.IPRanges {
|
||||||
|
list = append(list, *ip.CIDRIP)
|
||||||
|
}
|
||||||
|
|
||||||
m["cidr_blocks"] = list
|
m["cidr_blocks"] = list
|
||||||
}
|
}
|
||||||
|
|
||||||
var groups []string
|
var groups []string
|
||||||
if len(perm.SourceGroups) > 0 {
|
if len(perm.UserIDGroupPairs) > 0 {
|
||||||
groups = flattenSecurityGroups(perm.SourceGroups)
|
groups = flattenSecurityGroupsSDK(perm.UserIDGroupPairs)
|
||||||
}
|
}
|
||||||
for i, id := range groups {
|
for i, id := range groups {
|
||||||
if id == d.Id() {
|
if id == d.Id() {
|
||||||
|
@ -364,7 +378,6 @@ func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []
|
||||||
for _, m := range ruleMap {
|
for _, m := range ruleMap {
|
||||||
rules = append(rules, m)
|
rules = append(rules, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
return rules
|
return rules
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,6 +396,7 @@ func resourceAwsSecurityGroupUpdateRules(
|
||||||
os := o.(*schema.Set)
|
os := o.(*schema.Set)
|
||||||
ns := n.(*schema.Set)
|
ns := n.(*schema.Set)
|
||||||
|
|
||||||
|
// TODO: re-munge this when test is updated
|
||||||
remove := expandIPPerms(d.Id(), os.Difference(ns).List())
|
remove := expandIPPerms(d.Id(), os.Difference(ns).List())
|
||||||
add := expandIPPerms(d.Id(), ns.Difference(os).List())
|
add := expandIPPerms(d.Id(), ns.Difference(os).List())
|
||||||
|
|
||||||
|
@ -396,34 +410,53 @@ func resourceAwsSecurityGroupUpdateRules(
|
||||||
// not have service issues.
|
// not have service issues.
|
||||||
|
|
||||||
if len(remove) > 0 || len(add) > 0 {
|
if len(remove) > 0 || len(add) > 0 {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
|
var err error
|
||||||
if len(remove) > 0 {
|
if len(remove) > 0 {
|
||||||
// Revoke the old rules
|
log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v",
|
||||||
revoke := ec2conn.RevokeSecurityGroup
|
group, ruleset, remove)
|
||||||
|
|
||||||
if ruleset == "egress" {
|
if ruleset == "egress" {
|
||||||
revoke = ec2conn.RevokeSecurityGroupEgress
|
req := &ec2.RevokeSecurityGroupEgressRequest{
|
||||||
|
GroupID: group.GroupID,
|
||||||
|
IPPermissions: remove,
|
||||||
|
}
|
||||||
|
err = ec2conn.RevokeSecurityGroupEgress(req)
|
||||||
|
} else {
|
||||||
|
req := &ec2.RevokeSecurityGroupIngressRequest{
|
||||||
|
GroupID: group.GroupID,
|
||||||
|
IPPermissions: remove,
|
||||||
|
}
|
||||||
|
err = ec2conn.RevokeSecurityGroupIngress(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] Revoking security group %s %s rule: %#v",
|
if err != nil {
|
||||||
group, ruleset, remove)
|
|
||||||
if _, err := revoke(group, remove); err != nil {
|
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Error revoking security group %s rules: %s",
|
"Error authorizing security group %s rules: %s",
|
||||||
ruleset, err)
|
ruleset, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(add) > 0 {
|
if len(add) > 0 {
|
||||||
|
log.Printf("[DEBUG] Authorizing security group %#v %s rule: %#v",
|
||||||
|
group, ruleset, add)
|
||||||
// Authorize the new rules
|
// Authorize the new rules
|
||||||
authorize := ec2conn.AuthorizeSecurityGroup
|
|
||||||
if ruleset == "egress" {
|
if ruleset == "egress" {
|
||||||
authorize = ec2conn.AuthorizeSecurityGroupEgress
|
req := &ec2.AuthorizeSecurityGroupEgressRequest{
|
||||||
|
GroupID: group.GroupID,
|
||||||
|
IPPermissions: add,
|
||||||
|
}
|
||||||
|
err = ec2conn.AuthorizeSecurityGroupEgress(req)
|
||||||
|
} else {
|
||||||
|
req := &ec2.AuthorizeSecurityGroupIngressRequest{
|
||||||
|
GroupID: group.GroupID,
|
||||||
|
IPPermissions: add,
|
||||||
|
}
|
||||||
|
err = ec2conn.AuthorizeSecurityGroupIngress(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v",
|
if err != nil {
|
||||||
group, ruleset, add)
|
|
||||||
if _, err := authorize(group, add); err != nil {
|
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Error authorizing security group %s rules: %s",
|
"Error authorizing security group %s rules: %s",
|
||||||
ruleset, err)
|
ruleset, err)
|
||||||
|
@ -431,7 +464,6 @@ func resourceAwsSecurityGroupUpdateRules(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,10 +471,12 @@ func resourceAwsSecurityGroupUpdateRules(
|
||||||
// a security group.
|
// a security group.
|
||||||
func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
|
func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
|
||||||
return func() (interface{}, string, error) {
|
return func() (interface{}, string, error) {
|
||||||
sgs := []ec2.SecurityGroup{ec2.SecurityGroup{Id: id}}
|
req := &ec2.DescribeSecurityGroupsRequest{
|
||||||
resp, err := conn.SecurityGroups(sgs, nil)
|
GroupIDs: []string{id},
|
||||||
|
}
|
||||||
|
resp, err := conn.DescribeSecurityGroups(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ec2err, ok := err.(*ec2.Error); ok {
|
if ec2err, ok := err.(aws.APIError); ok {
|
||||||
if ec2err.Code == "InvalidSecurityGroupID.NotFound" ||
|
if ec2err.Code == "InvalidSecurityGroupID.NotFound" ||
|
||||||
ec2err.Code == "InvalidGroup.NotFound" {
|
ec2err.Code == "InvalidGroup.NotFound" {
|
||||||
resp = nil
|
resp = nil
|
||||||
|
@ -460,7 +494,7 @@ func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
|
||||||
return nil, "", nil
|
return nil, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
group := &resp.Groups[0]
|
group := resp.SecurityGroups[0]
|
||||||
return group, "exists", nil
|
return group, "exists", nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,18 @@ package aws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAWSSecurityGroup_normal(t *testing.T) {
|
func TestAccAWSSecurityGroup_normal(t *testing.T) {
|
||||||
var group ec2.SecurityGroupInfo
|
var group ec2.SecurityGroup
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -44,7 +46,7 @@ func TestAccAWSSecurityGroup_normal(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccAWSSecurityGroup_self(t *testing.T) {
|
func TestAccAWSSecurityGroup_self(t *testing.T) {
|
||||||
var group ec2.SecurityGroupInfo
|
var group ec2.SecurityGroup
|
||||||
|
|
||||||
checkSelf := func(s *terraform.State) (err error) {
|
checkSelf := func(s *terraform.State) (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -53,7 +55,7 @@ func TestAccAWSSecurityGroup_self(t *testing.T) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if group.IPPerms[0].SourceGroups[0].Id != group.Id {
|
if *group.IPPermissions[0].UserIDGroupPairs[0].GroupID != *group.GroupID {
|
||||||
return fmt.Errorf("bad: %#v", group)
|
return fmt.Errorf("bad: %#v", group)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,10 +91,10 @@ func TestAccAWSSecurityGroup_self(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccAWSSecurityGroup_vpc(t *testing.T) {
|
func TestAccAWSSecurityGroup_vpc(t *testing.T) {
|
||||||
var group ec2.SecurityGroupInfo
|
var group ec2.SecurityGroup
|
||||||
|
|
||||||
testCheck := func(*terraform.State) error {
|
testCheck := func(*terraform.State) error {
|
||||||
if group.VpcId == "" {
|
if *group.VPCID == "" {
|
||||||
return fmt.Errorf("should have vpc ID")
|
return fmt.Errorf("should have vpc ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +143,7 @@ func TestAccAWSSecurityGroup_vpc(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccAWSSecurityGroup_MultiIngress(t *testing.T) {
|
func TestAccAWSSecurityGroup_MultiIngress(t *testing.T) {
|
||||||
var group ec2.SecurityGroupInfo
|
var group ec2.SecurityGroup
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -159,7 +161,7 @@ func TestAccAWSSecurityGroup_MultiIngress(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccAWSSecurityGroup_Change(t *testing.T) {
|
func TestAccAWSSecurityGroup_Change(t *testing.T) {
|
||||||
var group ec2.SecurityGroupInfo
|
var group ec2.SecurityGroup
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -184,30 +186,27 @@ func TestAccAWSSecurityGroup_Change(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckAWSSecurityGroupDestroy(s *terraform.State) error {
|
func testAccCheckAWSSecurityGroupDestroy(s *terraform.State) error {
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
if rs.Type != "aws_security_group" {
|
if rs.Type != "aws_security_group" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sgs := []ec2.SecurityGroup{
|
|
||||||
ec2.SecurityGroup{
|
|
||||||
Id: rs.Primary.ID,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve our group
|
// Retrieve our group
|
||||||
resp, err := conn.SecurityGroups(sgs, nil)
|
req := &ec2.DescribeSecurityGroupsRequest{
|
||||||
|
GroupIDs: []string{rs.Primary.ID},
|
||||||
|
}
|
||||||
|
resp, err := conn.DescribeSecurityGroups(req)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if len(resp.Groups) > 0 && resp.Groups[0].Id == rs.Primary.ID {
|
if len(resp.SecurityGroups) > 0 && *resp.SecurityGroups[0].GroupID == rs.Primary.ID {
|
||||||
return fmt.Errorf("Security Group (%s) still exists.", rs.Primary.ID)
|
return fmt.Errorf("Security Group (%s) still exists.", rs.Primary.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ec2err, ok := err.(*ec2.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if !ok {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -220,7 +219,7 @@ func testAccCheckAWSSecurityGroupDestroy(s *terraform.State) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckAWSSecurityGroupExists(n string, group *ec2.SecurityGroupInfo) resource.TestCheckFunc {
|
func testAccCheckAWSSecurityGroupExists(n string, group *ec2.SecurityGroup) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
rs, ok := s.RootModule().Resources[n]
|
rs, ok := s.RootModule().Resources[n]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -231,20 +230,19 @@ func testAccCheckAWSSecurityGroupExists(n string, group *ec2.SecurityGroupInfo)
|
||||||
return fmt.Errorf("No Security Group is set")
|
return fmt.Errorf("No Security Group is set")
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
sgs := []ec2.SecurityGroup{
|
req := &ec2.DescribeSecurityGroupsRequest{
|
||||||
ec2.SecurityGroup{
|
GroupIDs: []string{rs.Primary.ID},
|
||||||
Id: rs.Primary.ID,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
resp, err := conn.SecurityGroups(sgs, nil)
|
resp, err := conn.DescribeSecurityGroups(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resp.Groups) > 0 && resp.Groups[0].Id == rs.Primary.ID {
|
if len(resp.SecurityGroups) > 0 && *resp.SecurityGroups[0].GroupID == rs.Primary.ID {
|
||||||
|
|
||||||
*group = resp.Groups[0]
|
log.Printf("\n==\n===\nfound group\n===\n==\n")
|
||||||
|
*group = resp.SecurityGroups[0]
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -253,32 +251,32 @@ func testAccCheckAWSSecurityGroupExists(n string, group *ec2.SecurityGroupInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckAWSSecurityGroupAttributes(group *ec2.SecurityGroupInfo) resource.TestCheckFunc {
|
func testAccCheckAWSSecurityGroupAttributes(group *ec2.SecurityGroup) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
p := ec2.IPPerm{
|
p := ec2.IPPermission{
|
||||||
FromPort: 80,
|
FromPort: aws.Integer(80),
|
||||||
ToPort: 8000,
|
ToPort: aws.Integer(8000),
|
||||||
Protocol: "tcp",
|
IPProtocol: aws.String("tcp"),
|
||||||
SourceIPs: []string{"10.0.0.0/8"},
|
IPRanges: []ec2.IPRange{ec2.IPRange{aws.String("10.0.0.0/8")}},
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.Name != "terraform_acceptance_test_example" {
|
if *group.GroupName != "terraform_acceptance_test_example" {
|
||||||
return fmt.Errorf("Bad name: %s", group.Name)
|
return fmt.Errorf("Bad name: %s", *group.GroupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.Description != "Used in the terraform acceptance tests" {
|
if *group.Description != "Used in the terraform acceptance tests" {
|
||||||
return fmt.Errorf("Bad description: %s", group.Description)
|
return fmt.Errorf("Bad description: %s", *group.Description)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(group.IPPerms) == 0 {
|
if len(group.IPPermissions) == 0 {
|
||||||
return fmt.Errorf("No IPPerms")
|
return fmt.Errorf("No IPPerms")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare our ingress
|
// Compare our ingress
|
||||||
if !reflect.DeepEqual(group.IPPerms[0], p) {
|
if !reflect.DeepEqual(group.IPPermissions[0], p) {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
|
"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
|
||||||
group.IPPerms[0],
|
group.IPPermissions[0],
|
||||||
p)
|
p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,7 +285,7 @@ func testAccCheckAWSSecurityGroupAttributes(group *ec2.SecurityGroupInfo) resour
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccAWSSecurityGroup_tags(t *testing.T) {
|
func TestAccAWSSecurityGroup_tags(t *testing.T) {
|
||||||
var group ec2.SecurityGroupInfo
|
var group ec2.SecurityGroup
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -298,7 +296,7 @@ func TestAccAWSSecurityGroup_tags(t *testing.T) {
|
||||||
Config: testAccAWSSecurityGroupConfigTags,
|
Config: testAccAWSSecurityGroupConfigTags,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckAWSSecurityGroupExists("aws_security_group.foo", &group),
|
testAccCheckAWSSecurityGroupExists("aws_security_group.foo", &group),
|
||||||
testAccCheckTags(&group.Tags, "foo", "bar"),
|
testAccCheckTagsSDK(&group.Tags, "foo", "bar"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -306,56 +304,56 @@ func TestAccAWSSecurityGroup_tags(t *testing.T) {
|
||||||
Config: testAccAWSSecurityGroupConfigTagsUpdate,
|
Config: testAccAWSSecurityGroupConfigTagsUpdate,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckAWSSecurityGroupExists("aws_security_group.foo", &group),
|
testAccCheckAWSSecurityGroupExists("aws_security_group.foo", &group),
|
||||||
testAccCheckTags(&group.Tags, "foo", ""),
|
testAccCheckTagsSDK(&group.Tags, "foo", ""),
|
||||||
testAccCheckTags(&group.Tags, "bar", "baz"),
|
testAccCheckTagsSDK(&group.Tags, "bar", "baz"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckAWSSecurityGroupAttributesChanged(group *ec2.SecurityGroupInfo) resource.TestCheckFunc {
|
func testAccCheckAWSSecurityGroupAttributesChanged(group *ec2.SecurityGroup) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
p := []ec2.IPPerm{
|
p := []ec2.IPPermission{
|
||||||
ec2.IPPerm{
|
ec2.IPPermission{
|
||||||
FromPort: 80,
|
FromPort: aws.Integer(80),
|
||||||
ToPort: 9000,
|
ToPort: aws.Integer(9000),
|
||||||
Protocol: "tcp",
|
IPProtocol: aws.String("tcp"),
|
||||||
SourceIPs: []string{"10.0.0.0/8"},
|
IPRanges: []ec2.IPRange{ec2.IPRange{aws.String("10.0.0.0/8")}},
|
||||||
},
|
},
|
||||||
ec2.IPPerm{
|
ec2.IPPermission{
|
||||||
FromPort: 80,
|
FromPort: aws.Integer(80),
|
||||||
ToPort: 8000,
|
ToPort: aws.Integer(8000),
|
||||||
Protocol: "tcp",
|
IPProtocol: aws.String("tcp"),
|
||||||
SourceIPs: []string{"0.0.0.0/0", "10.0.0.0/8"},
|
IPRanges: []ec2.IPRange{ec2.IPRange{aws.String("0.0.0.0/0")}, ec2.IPRange{aws.String("10.0.0.0/8")}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.Name != "terraform_acceptance_test_example" {
|
if *group.GroupName != "terraform_acceptance_test_example" {
|
||||||
return fmt.Errorf("Bad name: %s", group.Name)
|
return fmt.Errorf("Bad name: %s", *group.GroupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.Description != "Used in the terraform acceptance tests" {
|
if *group.Description != "Used in the terraform acceptance tests" {
|
||||||
return fmt.Errorf("Bad description: %s", group.Description)
|
return fmt.Errorf("Bad description: %s", *group.Description)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare our ingress
|
// Compare our ingress
|
||||||
if len(group.IPPerms) != 2 {
|
if len(group.IPPermissions) != 2 {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
|
"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
|
||||||
group.IPPerms,
|
group.IPPermissions,
|
||||||
p)
|
p)
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.IPPerms[0].ToPort == 8000 {
|
if *group.IPPermissions[0].ToPort == 8000 {
|
||||||
group.IPPerms[1], group.IPPerms[0] =
|
group.IPPermissions[1], group.IPPermissions[0] =
|
||||||
group.IPPerms[0], group.IPPerms[1]
|
group.IPPermissions[0], group.IPPermissions[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(group.IPPerms, p) {
|
if !reflect.DeepEqual(group.IPPermissions, p) {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
|
"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
|
||||||
group.IPPerms,
|
group.IPPermissions,
|
||||||
p)
|
p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,10 +68,10 @@ func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
// Get the ID and store it
|
// Get the ID and store it
|
||||||
subnet := resp.Subnet
|
subnet := resp.Subnet
|
||||||
d.SetId(*subnet.SubnetID)
|
d.SetId(*subnet.SubnetID)
|
||||||
log.Printf("[INFO] Subnet ID: %s", subnet.SubnetID)
|
log.Printf("[INFO] Subnet ID: %s", *subnet.SubnetID)
|
||||||
|
|
||||||
// Wait for the Subnet to become available
|
// Wait for the Subnet to become available
|
||||||
log.Printf("[DEBUG] Waiting for subnet (%s) to become available", subnet.SubnetID)
|
log.Printf("[DEBUG] Waiting for subnet (%s) to become available", *subnet.SubnetID)
|
||||||
stateConf := &resource.StateChangeConf{
|
stateConf := &resource.StateChangeConf{
|
||||||
Pending: []string{"pending"},
|
Pending: []string{"pending"},
|
||||||
Target: "available",
|
Target: "available",
|
||||||
|
|
|
@ -15,11 +15,11 @@ func TestAccAWSSubnet(t *testing.T) {
|
||||||
|
|
||||||
testCheck := func(*terraform.State) error {
|
testCheck := func(*terraform.State) error {
|
||||||
if *v.CIDRBlock != "10.1.1.0/24" {
|
if *v.CIDRBlock != "10.1.1.0/24" {
|
||||||
return fmt.Errorf("bad cidr: %s", v.CIDRBlock)
|
return fmt.Errorf("bad cidr: %s", *v.CIDRBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *v.MapPublicIPOnLaunch != true {
|
if *v.MapPublicIPOnLaunch != true {
|
||||||
return fmt.Errorf("bad MapPublicIpOnLaunch: %t", v.MapPublicIPOnLaunch)
|
return fmt.Errorf("bad MapPublicIpOnLaunch: %t", *v.MapPublicIPOnLaunch)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -5,9 +5,10 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceAwsVpcPeeringConnection() *schema.Resource {
|
func resourceAwsVpcPeeringConnection() *schema.Resource {
|
||||||
|
@ -22,6 +23,7 @@ func resourceAwsVpcPeeringConnection() *schema.Resource {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("AWS_ACCOUNT_ID", nil),
|
||||||
},
|
},
|
||||||
"peer_vpc_id": &schema.Schema{
|
"peer_vpc_id": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
|
@ -39,23 +41,23 @@ func resourceAwsVpcPeeringConnection() *schema.Resource {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsVpcPeeringCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsVpcPeeringCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
// Create the vpc peering connection
|
// Create the vpc peering connection
|
||||||
createOpts := &ec2.CreateVpcPeeringConnection{
|
createOpts := &ec2.CreateVPCPeeringConnectionRequest{
|
||||||
PeerOwnerId: d.Get("peer_owner_id").(string),
|
PeerOwnerID: aws.String(d.Get("peer_owner_id").(string)),
|
||||||
PeerVpcId: d.Get("peer_vpc_id").(string),
|
PeerVPCID: aws.String(d.Get("peer_vpc_id").(string)),
|
||||||
VpcId: d.Get("vpc_id").(string),
|
VPCID: aws.String(d.Get("vpc_id").(string)),
|
||||||
}
|
}
|
||||||
log.Printf("[DEBUG] VpcPeeringCreate create config: %#v", createOpts)
|
log.Printf("[DEBUG] VpcPeeringCreate create config: %#v", createOpts)
|
||||||
resp, err := ec2conn.CreateVpcPeeringConnection(createOpts)
|
resp, err := ec2conn.CreateVPCPeeringConnection(createOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error creating vpc peering connection: %s", err)
|
return fmt.Errorf("Error creating vpc peering connection: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the ID and store it
|
// Get the ID and store it
|
||||||
rt := &resp.VpcPeeringConnection
|
rt := resp.VPCPeeringConnection
|
||||||
d.SetId(rt.VpcPeeringConnectionId)
|
d.SetId(*rt.VPCPeeringConnectionID)
|
||||||
log.Printf("[INFO] Vpc Peering Connection ID: %s", d.Id())
|
log.Printf("[INFO] Vpc Peering Connection ID: %s", d.Id())
|
||||||
|
|
||||||
// Wait for the vpc peering connection to become available
|
// Wait for the vpc peering connection to become available
|
||||||
|
@ -78,7 +80,7 @@ func resourceAwsVpcPeeringCreate(d *schema.ResourceData, meta interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsVpcPeeringRead(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsVpcPeeringRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
pcRaw, _, err := resourceAwsVpcPeeringConnectionStateRefreshFunc(ec2conn, d.Id())()
|
pcRaw, _, err := resourceAwsVpcPeeringConnectionStateRefreshFunc(ec2conn, d.Id())()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -88,20 +90,20 @@ func resourceAwsVpcPeeringRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pc := pcRaw.(*ec2.VpcPeeringConnection)
|
pc := pcRaw.(*ec2.VPCPeeringConnection)
|
||||||
|
|
||||||
d.Set("peer_owner_id", pc.AccepterVpcInfo.OwnerId)
|
d.Set("peer_owner_id", pc.AccepterVPCInfo.OwnerID)
|
||||||
d.Set("peer_vpc_id", pc.AccepterVpcInfo.VpcId)
|
d.Set("peer_vpc_id", pc.AccepterVPCInfo.VPCID)
|
||||||
d.Set("vpc_id", pc.RequesterVpcInfo.VpcId)
|
d.Set("vpc_id", pc.RequesterVPCInfo.VPCID)
|
||||||
d.Set("tags", tagsToMap(pc.Tags))
|
d.Set("tags", tagsToMapSDK(pc.Tags))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsVpcPeeringUpdate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsVpcPeeringUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
if err := setTags(ec2conn, d); err != nil {
|
if err := setTagsSDK(ec2conn, d); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
d.SetPartial("tags")
|
d.SetPartial("tags")
|
||||||
|
@ -111,9 +113,12 @@ func resourceAwsVpcPeeringUpdate(d *schema.ResourceData, meta interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsVpcPeeringDelete(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsVpcPeeringDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
ec2conn := meta.(*AWSClient).ec2conn
|
ec2conn := meta.(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
_, err := ec2conn.DeleteVpcPeeringConnection(d.Id())
|
_, err := ec2conn.DeleteVPCPeeringConnection(
|
||||||
|
&ec2.DeleteVPCPeeringConnectionRequest{
|
||||||
|
VPCPeeringConnectionID: aws.String(d.Id()),
|
||||||
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,9 +127,11 @@ func resourceAwsVpcPeeringDelete(d *schema.ResourceData, meta interface{}) error
|
||||||
func resourceAwsVpcPeeringConnectionStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
|
func resourceAwsVpcPeeringConnectionStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
|
||||||
return func() (interface{}, string, error) {
|
return func() (interface{}, string, error) {
|
||||||
|
|
||||||
resp, err := conn.DescribeVpcPeeringConnection([]string{id}, ec2.NewFilter())
|
resp, err := conn.DescribeVPCPeeringConnections(&ec2.DescribeVPCPeeringConnectionsRequest{
|
||||||
|
VPCPeeringConnectionIDs: []string{id},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidVpcPeeringConnectionID.NotFound" {
|
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpcPeeringConnectionID.NotFound" {
|
||||||
resp = nil
|
resp = nil
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Error on VpcPeeringConnectionStateRefresh: %s", err)
|
log.Printf("Error on VpcPeeringConnectionStateRefresh: %s", err)
|
||||||
|
@ -138,7 +145,7 @@ func resourceAwsVpcPeeringConnectionStateRefreshFunc(conn *ec2.EC2, id string) r
|
||||||
return nil, "", nil
|
return nil, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pc := &resp.VpcPeeringConnections[0]
|
pc := &resp.VPCPeeringConnections[0]
|
||||||
|
|
||||||
return pc, "ready", nil
|
return pc, "ready", nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAWSVPCPeeringConnection_normal(t *testing.T) {
|
func TestAccAWSVPCPeeringConnection_normal(t *testing.T) {
|
||||||
|
@ -28,17 +28,20 @@ func TestAccAWSVPCPeeringConnection_normal(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckAWSVpcPeeringConnectionDestroy(s *terraform.State) error {
|
func testAccCheckAWSVpcPeeringConnectionDestroy(s *terraform.State) error {
|
||||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
conn := testAccProvider.Meta().(*AWSClient).awsEC2conn
|
||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
if rs.Type != "aws_vpc_peering_connection" {
|
if rs.Type != "aws_vpc_peering_connection" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
describe, err := conn.DescribeVpcPeeringConnection([]string{rs.Primary.ID}, ec2.NewFilter())
|
describe, err := conn.DescribeVPCPeeringConnections(
|
||||||
|
&ec2.DescribeVPCPeeringConnectionsRequest{
|
||||||
|
VPCPeeringConnectionIDs: []string{rs.Primary.ID},
|
||||||
|
})
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if len(describe.VpcPeeringConnections) != 0 {
|
if len(describe.VPCPeeringConnections) != 0 {
|
||||||
return fmt.Errorf("vpc peering connection still exists")
|
return fmt.Errorf("vpc peering connection still exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,11 +71,10 @@ resource "aws_vpc" "foo" {
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_vpc" "bar" {
|
resource "aws_vpc" "bar" {
|
||||||
cidr_block = "10.0.1.0/16"
|
cidr_block = "10.1.0.0/16"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_vpc_peering_connection" "foo" {
|
resource "aws_vpc_peering_connection" "foo" {
|
||||||
peer_owner_id = "12345"
|
|
||||||
vpc_id = "${aws_vpc.foo.id}"
|
vpc_id = "${aws_vpc.foo.id}"
|
||||||
peer_vpc_id = "${aws_vpc.bar.id}"
|
peer_vpc_id = "${aws_vpc.bar.id}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/aws-sdk-go/aws"
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
awsEC2 "github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/aws-sdk-go/gen/elb"
|
"github.com/hashicorp/aws-sdk-go/gen/elb"
|
||||||
"github.com/hashicorp/aws-sdk-go/gen/rds"
|
"github.com/hashicorp/aws-sdk-go/gen/rds"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
@ -39,15 +40,15 @@ func expandListeners(configured []interface{}) ([]elb.Listener, error) {
|
||||||
|
|
||||||
// Takes the result of flatmap.Expand for an array of ingress/egress
|
// Takes the result of flatmap.Expand for an array of ingress/egress
|
||||||
// security group rules and returns EC2 API compatible objects
|
// security group rules and returns EC2 API compatible objects
|
||||||
func expandIPPerms(id string, configured []interface{}) []ec2.IPPerm {
|
func expandIPPerms(id string, configured []interface{}) []awsEC2.IPPermission {
|
||||||
perms := make([]ec2.IPPerm, len(configured))
|
perms := make([]awsEC2.IPPermission, len(configured))
|
||||||
for i, mRaw := range configured {
|
for i, mRaw := range configured {
|
||||||
var perm ec2.IPPerm
|
var perm awsEC2.IPPermission
|
||||||
m := mRaw.(map[string]interface{})
|
m := mRaw.(map[string]interface{})
|
||||||
|
|
||||||
perm.FromPort = m["from_port"].(int)
|
perm.FromPort = aws.Integer(m["from_port"].(int))
|
||||||
perm.ToPort = m["to_port"].(int)
|
perm.ToPort = aws.Integer(m["to_port"].(int))
|
||||||
perm.Protocol = m["protocol"].(string)
|
perm.IPProtocol = aws.String(m["protocol"].(string))
|
||||||
|
|
||||||
var groups []string
|
var groups []string
|
||||||
if raw, ok := m["security_groups"]; ok {
|
if raw, ok := m["security_groups"]; ok {
|
||||||
|
@ -61,25 +62,25 @@ func expandIPPerms(id string, configured []interface{}) []ec2.IPPerm {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(groups) > 0 {
|
if len(groups) > 0 {
|
||||||
perm.SourceGroups = make([]ec2.UserSecurityGroup, len(groups))
|
perm.UserIDGroupPairs = make([]awsEC2.UserIDGroupPair, len(groups))
|
||||||
for i, name := range groups {
|
for i, name := range groups {
|
||||||
ownerId, id := "", name
|
ownerId, id := "", name
|
||||||
if items := strings.Split(id, "/"); len(items) > 1 {
|
if items := strings.Split(id, "/"); len(items) > 1 {
|
||||||
ownerId, id = items[0], items[1]
|
ownerId, id = items[0], items[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
perm.SourceGroups[i] = ec2.UserSecurityGroup{
|
perm.UserIDGroupPairs[i] = awsEC2.UserIDGroupPair{
|
||||||
Id: id,
|
GroupID: aws.String(id),
|
||||||
OwnerId: ownerId,
|
UserID: aws.String(ownerId),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if raw, ok := m["cidr_blocks"]; ok {
|
if raw, ok := m["cidr_blocks"]; ok {
|
||||||
list := raw.([]interface{})
|
list := raw.([]interface{})
|
||||||
perm.SourceIPs = make([]string, len(list))
|
perm.IPRanges = make([]awsEC2.IPRange, len(list))
|
||||||
for i, v := range list {
|
for i, v := range list {
|
||||||
perm.SourceIPs[i] = v.(string)
|
perm.IPRanges[i] = awsEC2.IPRange{aws.String(v.(string))}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,31 +112,6 @@ func expandParameters(configured []interface{}) ([]rds.Parameter, error) {
|
||||||
return parameters, nil
|
return parameters, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flattens an array of ipPerms into a list of primitives that
|
|
||||||
// flatmap.Flatten() can handle
|
|
||||||
func flattenIPPerms(list []ec2.IPPerm) []map[string]interface{} {
|
|
||||||
result := make([]map[string]interface{}, 0, len(list))
|
|
||||||
|
|
||||||
for _, perm := range list {
|
|
||||||
n := make(map[string]interface{})
|
|
||||||
n["from_port"] = perm.FromPort
|
|
||||||
n["protocol"] = perm.Protocol
|
|
||||||
n["to_port"] = perm.ToPort
|
|
||||||
|
|
||||||
if len(perm.SourceIPs) > 0 {
|
|
||||||
n["cidr_blocks"] = perm.SourceIPs
|
|
||||||
}
|
|
||||||
|
|
||||||
if v := flattenSecurityGroups(perm.SourceGroups); len(v) > 0 {
|
|
||||||
n["security_groups"] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flattens a health check into something that flatmap.Flatten()
|
// Flattens a health check into something that flatmap.Flatten()
|
||||||
// can handle
|
// can handle
|
||||||
func flattenHealthCheck(check *elb.HealthCheck) []map[string]interface{} {
|
func flattenHealthCheck(check *elb.HealthCheck) []map[string]interface{} {
|
||||||
|
@ -162,6 +138,15 @@ func flattenSecurityGroups(list []ec2.UserSecurityGroup) []string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flattens an array of UserSecurityGroups into a []string
|
||||||
|
func flattenSecurityGroupsSDK(list []awsEC2.UserIDGroupPair) []string {
|
||||||
|
result := make([]string, 0, len(list))
|
||||||
|
for _, g := range list {
|
||||||
|
result = append(result, *g.GroupID)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// Flattens an array of Instances into a []string
|
// Flattens an array of Instances into a []string
|
||||||
func flattenInstances(list []elb.Instance) []string {
|
func flattenInstances(list []elb.Instance) []string {
|
||||||
result := make([]string, 0, len(list))
|
result := make([]string, 0, len(list))
|
||||||
|
|
|
@ -5,12 +5,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/aws-sdk-go/aws"
|
"github.com/hashicorp/aws-sdk-go/aws"
|
||||||
|
awsEC2 "github.com/hashicorp/aws-sdk-go/gen/ec2"
|
||||||
"github.com/hashicorp/aws-sdk-go/gen/elb"
|
"github.com/hashicorp/aws-sdk-go/gen/elb"
|
||||||
"github.com/hashicorp/aws-sdk-go/gen/rds"
|
"github.com/hashicorp/aws-sdk-go/gen/rds"
|
||||||
"github.com/hashicorp/terraform/flatmap"
|
"github.com/hashicorp/terraform/flatmap"
|
||||||
"github.com/hashicorp/terraform/helper/hashcode"
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns test configuration
|
// Returns test configuration
|
||||||
|
@ -61,120 +61,58 @@ func TestExpandIPPerms(t *testing.T) {
|
||||||
}
|
}
|
||||||
perms := expandIPPerms("foo", expanded)
|
perms := expandIPPerms("foo", expanded)
|
||||||
|
|
||||||
expected := []ec2.IPPerm{
|
expected := []awsEC2.IPPermission{
|
||||||
ec2.IPPerm{
|
awsEC2.IPPermission{
|
||||||
Protocol: "icmp",
|
IPProtocol: aws.String("icmp"),
|
||||||
FromPort: 1,
|
FromPort: aws.Integer(1),
|
||||||
ToPort: -1,
|
ToPort: aws.Integer(-1),
|
||||||
SourceIPs: []string{"0.0.0.0/0"},
|
IPRanges: []awsEC2.IPRange{awsEC2.IPRange{aws.String("0.0.0.0/0")}},
|
||||||
SourceGroups: []ec2.UserSecurityGroup{
|
UserIDGroupPairs: []awsEC2.UserIDGroupPair{
|
||||||
ec2.UserSecurityGroup{
|
awsEC2.UserIDGroupPair{
|
||||||
OwnerId: "foo",
|
UserID: aws.String("foo"),
|
||||||
Id: "sg-22222",
|
GroupID: aws.String("sg-22222"),
|
||||||
},
|
},
|
||||||
ec2.UserSecurityGroup{
|
awsEC2.UserIDGroupPair{
|
||||||
Id: "sg-11111",
|
GroupID: aws.String("sg-22222"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ec2.IPPerm{
|
awsEC2.IPPermission{
|
||||||
Protocol: "icmp",
|
IPProtocol: aws.String("icmp"),
|
||||||
FromPort: 1,
|
FromPort: aws.Integer(1),
|
||||||
ToPort: -1,
|
ToPort: aws.Integer(-1),
|
||||||
SourceGroups: []ec2.UserSecurityGroup{
|
UserIDGroupPairs: []awsEC2.UserIDGroupPair{
|
||||||
ec2.UserSecurityGroup{
|
awsEC2.UserIDGroupPair{
|
||||||
Id: "foo",
|
UserID: aws.String("foo"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(perms, expected) {
|
exp := expected[0]
|
||||||
|
perm := perms[0]
|
||||||
|
|
||||||
|
if *exp.FromPort != *perm.FromPort {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
|
"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
|
||||||
perms[0],
|
*perm.FromPort,
|
||||||
expected)
|
*exp.FromPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
if *exp.IPRanges[0].CIDRIP != *perm.IPRanges[0].CIDRIP {
|
||||||
|
t.Fatalf(
|
||||||
func TestFlattenIPPerms(t *testing.T) {
|
"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
|
||||||
cases := []struct {
|
*perm.IPRanges[0].CIDRIP,
|
||||||
Input []ec2.IPPerm
|
*exp.IPRanges[0].CIDRIP)
|
||||||
Output []map[string]interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Input: []ec2.IPPerm{
|
|
||||||
ec2.IPPerm{
|
|
||||||
Protocol: "icmp",
|
|
||||||
FromPort: 1,
|
|
||||||
ToPort: -1,
|
|
||||||
SourceIPs: []string{"0.0.0.0/0"},
|
|
||||||
SourceGroups: []ec2.UserSecurityGroup{
|
|
||||||
ec2.UserSecurityGroup{
|
|
||||||
Id: "sg-11111",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Output: []map[string]interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"protocol": "icmp",
|
|
||||||
"from_port": 1,
|
|
||||||
"to_port": -1,
|
|
||||||
"cidr_blocks": []string{"0.0.0.0/0"},
|
|
||||||
"security_groups": []string{"sg-11111"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
Input: []ec2.IPPerm{
|
|
||||||
ec2.IPPerm{
|
|
||||||
Protocol: "icmp",
|
|
||||||
FromPort: 1,
|
|
||||||
ToPort: -1,
|
|
||||||
SourceIPs: []string{"0.0.0.0/0"},
|
|
||||||
SourceGroups: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Output: []map[string]interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"protocol": "icmp",
|
|
||||||
"from_port": 1,
|
|
||||||
"to_port": -1,
|
|
||||||
"cidr_blocks": []string{"0.0.0.0/0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Input: []ec2.IPPerm{
|
|
||||||
ec2.IPPerm{
|
|
||||||
Protocol: "icmp",
|
|
||||||
FromPort: 1,
|
|
||||||
ToPort: -1,
|
|
||||||
SourceIPs: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Output: []map[string]interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"protocol": "icmp",
|
|
||||||
"from_port": 1,
|
|
||||||
"to_port": -1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
if *exp.UserIDGroupPairs[0].UserID != *perm.UserIDGroupPairs[0].UserID {
|
||||||
output := flattenIPPerms(tc.Input)
|
t.Fatalf(
|
||||||
if !reflect.DeepEqual(output, tc.Output) {
|
"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
|
||||||
t.Fatalf("Input:\n\n%#v\n\nOutput:\n\n%#v", tc.Input, output)
|
*perm.UserIDGroupPairs[0].UserID,
|
||||||
}
|
*exp.UserIDGroupPairs[0].UserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExpandListeners(t *testing.T) {
|
func TestExpandListeners(t *testing.T) {
|
||||||
|
|
|
@ -30,7 +30,7 @@ func Provider() terraform.ResourceProvider {
|
||||||
"timeout": &schema.Schema{
|
"timeout": &schema.Schema{
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Required: true,
|
Required: true,
|
||||||
DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_TIMEOUT", 180),
|
DefaultFunc: schema.EnvDefaultFunc("CLOUDSTACK_TIMEOUT", 300),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -46,6 +46,9 @@ func Provider() terraform.ResourceProvider {
|
||||||
"cloudstack_nic": resourceCloudStackNIC(),
|
"cloudstack_nic": resourceCloudStackNIC(),
|
||||||
"cloudstack_port_forward": resourceCloudStackPortForward(),
|
"cloudstack_port_forward": resourceCloudStackPortForward(),
|
||||||
"cloudstack_vpc": resourceCloudStackVPC(),
|
"cloudstack_vpc": resourceCloudStackVPC(),
|
||||||
|
"cloudstack_vpn_connection": resourceCloudStackVPNConnection(),
|
||||||
|
"cloudstack_vpn_customer_gateway": resourceCloudStackVPNCustomerGateway(),
|
||||||
|
"cloudstack_vpn_gateway": resourceCloudStackVPNGateway(),
|
||||||
},
|
},
|
||||||
|
|
||||||
ConfigureFunc: providerConfigure,
|
ConfigureFunc: providerConfigure,
|
||||||
|
|
|
@ -51,7 +51,8 @@ var CLOUDSTACK_NETWORK_1_OFFERING = ""
|
||||||
var CLOUDSTACK_NETWORK_1_IPADDRESS = ""
|
var CLOUDSTACK_NETWORK_1_IPADDRESS = ""
|
||||||
var CLOUDSTACK_NETWORK_2 = ""
|
var CLOUDSTACK_NETWORK_2 = ""
|
||||||
var CLOUDSTACK_NETWORK_2_IPADDRESS = ""
|
var CLOUDSTACK_NETWORK_2_IPADDRESS = ""
|
||||||
var CLOUDSTACK_VPC_CIDR = ""
|
var CLOUDSTACK_VPC_CIDR_1 = ""
|
||||||
|
var CLOUDSTACK_VPC_CIDR_2 = ""
|
||||||
var CLOUDSTACK_VPC_OFFERING = ""
|
var CLOUDSTACK_VPC_OFFERING = ""
|
||||||
var CLOUDSTACK_VPC_NETWORK_CIDR = ""
|
var CLOUDSTACK_VPC_NETWORK_CIDR = ""
|
||||||
var CLOUDSTACK_VPC_NETWORK_OFFERING = ""
|
var CLOUDSTACK_VPC_NETWORK_OFFERING = ""
|
||||||
|
|
|
@ -95,18 +95,18 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{})
|
||||||
return e.Error()
|
return e.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the template UUID
|
|
||||||
templateid, e := retrieveUUID(cs, "template", d.Get("template").(string))
|
|
||||||
if e != nil {
|
|
||||||
return e.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the zone object
|
// Retrieve the zone object
|
||||||
zone, _, err := cs.Zone.GetZoneByName(d.Get("zone").(string))
|
zone, _, err := cs.Zone.GetZoneByName(d.Get("zone").(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve the template UUID
|
||||||
|
templateid, e := retrieveTemplateUUID(cs, zone.Id, d.Get("template").(string))
|
||||||
|
if e != nil {
|
||||||
|
return e.Error()
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new parameter struct
|
// Create a new parameter struct
|
||||||
p := cs.VirtualMachine.NewDeployVirtualMachineParams(serviceofferingid, templateid, zone.Id)
|
p := cs.VirtualMachine.NewDeployVirtualMachineParams(serviceofferingid, templateid, zone.Id)
|
||||||
|
|
||||||
|
|
|
@ -132,6 +132,6 @@ resource "cloudstack_vpc" "foobar" {
|
||||||
resource "cloudstack_ipaddress" "foo" {
|
resource "cloudstack_ipaddress" "foo" {
|
||||||
vpc = "${cloudstack_vpc.foobar.name}"
|
vpc = "${cloudstack_vpc.foobar.name}"
|
||||||
}`,
|
}`,
|
||||||
CLOUDSTACK_VPC_CIDR,
|
CLOUDSTACK_VPC_CIDR_1,
|
||||||
CLOUDSTACK_VPC_OFFERING,
|
CLOUDSTACK_VPC_OFFERING,
|
||||||
CLOUDSTACK_ZONE)
|
CLOUDSTACK_ZONE)
|
||||||
|
|
|
@ -196,7 +196,7 @@ resource "cloudstack_network_acl_rule" "foo" {
|
||||||
traffic_type = "ingress"
|
traffic_type = "ingress"
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
CLOUDSTACK_VPC_CIDR,
|
CLOUDSTACK_VPC_CIDR_1,
|
||||||
CLOUDSTACK_VPC_OFFERING,
|
CLOUDSTACK_VPC_OFFERING,
|
||||||
CLOUDSTACK_ZONE)
|
CLOUDSTACK_ZONE)
|
||||||
|
|
||||||
|
@ -233,6 +233,6 @@ resource "cloudstack_network_acl_rule" "foo" {
|
||||||
traffic_type = "egress"
|
traffic_type = "egress"
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
CLOUDSTACK_VPC_CIDR,
|
CLOUDSTACK_VPC_CIDR_1,
|
||||||
CLOUDSTACK_VPC_OFFERING,
|
CLOUDSTACK_VPC_OFFERING,
|
||||||
CLOUDSTACK_ZONE)
|
CLOUDSTACK_ZONE)
|
||||||
|
|
|
@ -112,6 +112,6 @@ resource "cloudstack_network_acl" "foo" {
|
||||||
description = "terraform-acl-text"
|
description = "terraform-acl-text"
|
||||||
vpc = "${cloudstack_vpc.foobar.name}"
|
vpc = "${cloudstack_vpc.foobar.name}"
|
||||||
}`,
|
}`,
|
||||||
CLOUDSTACK_VPC_CIDR,
|
CLOUDSTACK_VPC_CIDR_1,
|
||||||
CLOUDSTACK_VPC_OFFERING,
|
CLOUDSTACK_VPC_OFFERING,
|
||||||
CLOUDSTACK_ZONE)
|
CLOUDSTACK_ZONE)
|
||||||
|
|
|
@ -186,7 +186,7 @@ resource "cloudstack_network" "foo" {
|
||||||
aclid = "${cloudstack_network_acl.foo.id}"
|
aclid = "${cloudstack_network_acl.foo.id}"
|
||||||
zone = "${cloudstack_vpc.foobar.zone}"
|
zone = "${cloudstack_vpc.foobar.zone}"
|
||||||
}`,
|
}`,
|
||||||
CLOUDSTACK_VPC_CIDR,
|
CLOUDSTACK_VPC_CIDR_1,
|
||||||
CLOUDSTACK_VPC_OFFERING,
|
CLOUDSTACK_VPC_OFFERING,
|
||||||
CLOUDSTACK_ZONE,
|
CLOUDSTACK_ZONE,
|
||||||
CLOUDSTACK_VPC_NETWORK_CIDR,
|
CLOUDSTACK_VPC_NETWORK_CIDR,
|
||||||
|
|
|
@ -72,8 +72,8 @@ func testAccCheckCloudStackVPCAttributes(
|
||||||
return fmt.Errorf("Bad display text: %s", vpc.Displaytext)
|
return fmt.Errorf("Bad display text: %s", vpc.Displaytext)
|
||||||
}
|
}
|
||||||
|
|
||||||
if vpc.Cidr != CLOUDSTACK_VPC_CIDR {
|
if vpc.Cidr != CLOUDSTACK_VPC_CIDR_1 {
|
||||||
return fmt.Errorf("Bad VPC offering: %s", vpc.Cidr)
|
return fmt.Errorf("Bad VPC CIDR: %s", vpc.Cidr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -113,6 +113,6 @@ resource "cloudstack_vpc" "foo" {
|
||||||
vpc_offering = "%s"
|
vpc_offering = "%s"
|
||||||
zone = "%s"
|
zone = "%s"
|
||||||
}`,
|
}`,
|
||||||
CLOUDSTACK_VPC_CIDR,
|
CLOUDSTACK_VPC_CIDR_1,
|
||||||
CLOUDSTACK_VPC_OFFERING,
|
CLOUDSTACK_VPC_OFFERING,
|
||||||
CLOUDSTACK_ZONE)
|
CLOUDSTACK_ZONE)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
|
@ -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
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}`)
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
|
@ -40,8 +40,6 @@ func retrieveUUID(cs *cloudstack.CloudStackClient, name, value string) (uuid str
|
||||||
uuid, err = cs.VPC.GetVPCOfferingID(value)
|
uuid, err = cs.VPC.GetVPCOfferingID(value)
|
||||||
case "vpc":
|
case "vpc":
|
||||||
uuid, err = cs.VPC.GetVPCID(value)
|
uuid, err = cs.VPC.GetVPCID(value)
|
||||||
case "template":
|
|
||||||
uuid, err = cs.Template.GetTemplateID(value, "executable")
|
|
||||||
case "network":
|
case "network":
|
||||||
uuid, err = cs.Network.GetNetworkID(value)
|
uuid, err = cs.Network.GetNetworkID(value)
|
||||||
case "zone":
|
case "zone":
|
||||||
|
@ -71,6 +69,22 @@ func retrieveUUID(cs *cloudstack.CloudStackClient, name, value string) (uuid str
|
||||||
return uuid, nil
|
return uuid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func retrieveTemplateUUID(cs *cloudstack.CloudStackClient, zoneid, value string) (uuid string, e *retrieveError) {
|
||||||
|
// If the supplied value isn't a UUID, try to retrieve the UUID ourselves
|
||||||
|
if isUUID(value) {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Retrieving UUID of template: %s", value)
|
||||||
|
|
||||||
|
uuid, err := cs.Template.GetTemplateID(value, "executable", zoneid)
|
||||||
|
if err != nil {
|
||||||
|
return uuid, &retrieveError{name: "template", value: value, err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
|
|
||||||
func isUUID(s string) bool {
|
func isUUID(s string) bool {
|
||||||
re := regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
|
re := regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
|
||||||
return re.MatchString(s)
|
return re.MatchString(s)
|
||||||
|
|
|
@ -21,6 +21,7 @@ func Provider() terraform.ResourceProvider {
|
||||||
"digitalocean_domain": resourceDigitalOceanDomain(),
|
"digitalocean_domain": resourceDigitalOceanDomain(),
|
||||||
"digitalocean_droplet": resourceDigitalOceanDroplet(),
|
"digitalocean_droplet": resourceDigitalOceanDroplet(),
|
||||||
"digitalocean_record": resourceDigitalOceanRecord(),
|
"digitalocean_record": resourceDigitalOceanRecord(),
|
||||||
|
"digitalocean_ssh_key": resourceDigitalOceanSSHKey(),
|
||||||
},
|
},
|
||||||
|
|
||||||
ConfigureFunc: providerConfigure,
|
ConfigureFunc: providerConfigure,
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}`
|
|
@ -483,14 +483,19 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
|
||||||
d.Set("can_ip_forward", instance.CanIpForward)
|
d.Set("can_ip_forward", instance.CanIpForward)
|
||||||
|
|
||||||
// Set the service accounts
|
// Set the service accounts
|
||||||
for i, serviceAccount := range instance.ServiceAccounts {
|
serviceAccounts := make([]map[string]interface{}, 0, 1)
|
||||||
prefix := fmt.Sprintf("service_account.%d", i)
|
for _, serviceAccount := range instance.ServiceAccounts {
|
||||||
d.Set(prefix+".email", serviceAccount.Email)
|
scopes := make([]string, len(serviceAccount.Scopes))
|
||||||
d.Set(prefix+".scopes.#", len(serviceAccount.Scopes))
|
for i, scope := range serviceAccount.Scopes {
|
||||||
for j, scope := range serviceAccount.Scopes {
|
scopes[i] = scope
|
||||||
d.Set(fmt.Sprintf("%s.scopes.%d", prefix, j), scope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serviceAccounts = append(serviceAccounts, map[string]interface{}{
|
||||||
|
"email": serviceAccount.Email,
|
||||||
|
"scopes": scopes,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
d.Set("service_account", serviceAccounts)
|
||||||
|
|
||||||
networksCount := d.Get("network.#").(int)
|
networksCount := d.Get("network.#").(int)
|
||||||
networkInterfacesCount := d.Get("network_interface.#").(int)
|
networkInterfacesCount := d.Get("network_interface.#").(int)
|
||||||
|
@ -506,13 +511,10 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
|
||||||
// Use the first external IP found for the default connection info.
|
// Use the first external IP found for the default connection info.
|
||||||
externalIP := ""
|
externalIP := ""
|
||||||
internalIP := ""
|
internalIP := ""
|
||||||
|
networks := make([]map[string]interface{}, 0, 1)
|
||||||
if networksCount > 0 {
|
if networksCount > 0 {
|
||||||
// TODO: Remove this when realizing deprecation of .network
|
// TODO: Remove this when realizing deprecation of .network
|
||||||
for i, iface := range instance.NetworkInterfaces {
|
for _, iface := range instance.NetworkInterfaces {
|
||||||
prefix := fmt.Sprintf("network.%d", i)
|
|
||||||
d.Set(prefix+".name", iface.Name)
|
|
||||||
log.Printf(prefix+".name = %s", iface.Name)
|
|
||||||
|
|
||||||
var natIP string
|
var natIP string
|
||||||
for _, config := range iface.AccessConfigs {
|
for _, config := range iface.AccessConfigs {
|
||||||
if config.Type == "ONE_TO_ONE_NAT" {
|
if config.Type == "ONE_TO_ONE_NAT" {
|
||||||
|
@ -524,23 +526,28 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
|
||||||
if externalIP == "" && natIP != "" {
|
if externalIP == "" && natIP != "" {
|
||||||
externalIP = natIP
|
externalIP = natIP
|
||||||
}
|
}
|
||||||
d.Set(prefix+".external_address", natIP)
|
|
||||||
|
|
||||||
d.Set(prefix+".internal_address", iface.NetworkIP)
|
network := make(map[string]interface{})
|
||||||
|
network["name"] = iface.Name
|
||||||
|
network["external_address"] = natIP
|
||||||
|
network["internal_address"] = iface.NetworkIP
|
||||||
|
networks = append(networks, network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
d.Set("network", networks)
|
||||||
|
|
||||||
|
networkInterfaces := make([]map[string]interface{}, 0, 1)
|
||||||
if networkInterfacesCount > 0 {
|
if networkInterfacesCount > 0 {
|
||||||
for i, iface := range instance.NetworkInterfaces {
|
for _, iface := range instance.NetworkInterfaces {
|
||||||
|
|
||||||
prefix := fmt.Sprintf("network_interface.%d", i)
|
|
||||||
d.Set(prefix+".name", iface.Name)
|
|
||||||
|
|
||||||
// The first non-empty ip is left in natIP
|
// The first non-empty ip is left in natIP
|
||||||
var natIP string
|
var natIP string
|
||||||
for j, config := range iface.AccessConfigs {
|
accessConfigs := make(
|
||||||
acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j)
|
[]map[string]interface{}, 0, len(iface.AccessConfigs))
|
||||||
d.Set(acPrefix+".nat_ip", config.NatIP)
|
for _, config := range iface.AccessConfigs {
|
||||||
|
accessConfigs = append(accessConfigs, map[string]interface{}{
|
||||||
|
"nat_ip": config.NatIP,
|
||||||
|
})
|
||||||
|
|
||||||
if natIP == "" {
|
if natIP == "" {
|
||||||
natIP = config.NatIP
|
natIP = config.NatIP
|
||||||
}
|
}
|
||||||
|
@ -550,13 +557,18 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
|
||||||
externalIP = natIP
|
externalIP = natIP
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Set(prefix+".address", iface.NetworkIP)
|
|
||||||
if internalIP == "" {
|
if internalIP == "" {
|
||||||
internalIP = iface.NetworkIP
|
internalIP = iface.NetworkIP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
networkInterfaces = append(networkInterfaces, map[string]interface{}{
|
||||||
|
"name": iface.Name,
|
||||||
|
"address": iface.NetworkIP,
|
||||||
|
"access_config": accessConfigs,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
d.Set("network_interface", networkInterfaces)
|
||||||
|
|
||||||
// Fall back on internal ip if there is no external ip. This makes sense in the situation where
|
// Fall back on internal ip if there is no external ip. This makes sense in the situation where
|
||||||
// terraform is being used on a cloud instance and can therefore access the instances it creates
|
// terraform is being used on a cloud instance and can therefore access the instances it creates
|
||||||
|
|
|
@ -14,6 +14,7 @@ type ColorizeUi struct {
|
||||||
OutputColor string
|
OutputColor string
|
||||||
InfoColor string
|
InfoColor string
|
||||||
ErrorColor string
|
ErrorColor string
|
||||||
|
WarnColor string
|
||||||
Ui cli.Ui
|
Ui cli.Ui
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +34,10 @@ func (u *ColorizeUi) Error(message string) {
|
||||||
u.Ui.Error(u.colorize(message, u.ErrorColor))
|
u.Ui.Error(u.colorize(message, u.ErrorColor))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ColorizeUi) Warn(message string) {
|
||||||
|
u.Ui.Warn(u.colorize(message, u.WarnColor))
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ColorizeUi) colorize(message string, color string) string {
|
func (u *ColorizeUi) colorize(message string, color string) string {
|
||||||
if color == "" {
|
if color == "" {
|
||||||
return message
|
return message
|
||||||
|
|
|
@ -33,9 +33,9 @@ func validateContext(ctx *terraform.Context, ui cli.Ui) bool {
|
||||||
"fix these before continuing.\n")
|
"fix these before continuing.\n")
|
||||||
|
|
||||||
if len(ws) > 0 {
|
if len(ws) > 0 {
|
||||||
ui.Output("Warnings:\n")
|
ui.Warn("Warnings:\n")
|
||||||
for _, w := range ws {
|
for _, w := range ws {
|
||||||
ui.Output(fmt.Sprintf(" * %s", w))
|
ui.Warn(fmt.Sprintf(" * %s", w))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(es) > 0 {
|
if len(es) > 0 {
|
||||||
|
@ -44,13 +44,16 @@ func validateContext(ctx *terraform.Context, ui cli.Ui) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(es) > 0 {
|
if len(es) > 0 {
|
||||||
ui.Output("Errors:\n")
|
ui.Error("Errors:\n")
|
||||||
for _, e := range es {
|
for _, e := range es {
|
||||||
ui.Output(fmt.Sprintf(" * %s", e))
|
ui.Error(fmt.Sprintf(" * %s", e))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
} else {
|
||||||
|
ui.Warn(fmt.Sprintf("\n"+
|
||||||
|
"No errors found. Continuing with %d warning(s).\n", len(ws)))
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -120,7 +120,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize a blank state file with remote enabled
|
// Initialize a blank state file with remote enabled
|
||||||
remoteCmd := &RemoteCommand{
|
remoteCmd := &RemoteConfigCommand{
|
||||||
Meta: c.Meta,
|
Meta: c.Meta,
|
||||||
remoteConf: remoteConf,
|
remoteConf: remoteConf,
|
||||||
}
|
}
|
||||||
|
|
|
@ -331,6 +331,7 @@ func (m *Meta) process(args []string, vars bool) []string {
|
||||||
Ui: &ColorizeUi{
|
Ui: &ColorizeUi{
|
||||||
Colorize: m.Colorize(),
|
Colorize: m.Colorize(),
|
||||||
ErrorColor: "[red]",
|
ErrorColor: "[red]",
|
||||||
|
WarnColor: "[yellow]",
|
||||||
Ui: m.oldUi,
|
Ui: m.oldUi,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,339 +1,57 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/state"
|
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// remoteCommandConfig is used to encapsulate our configuration
|
|
||||||
type remoteCommandConfig struct {
|
|
||||||
disableRemote bool
|
|
||||||
pullOnDisable bool
|
|
||||||
|
|
||||||
statePath string
|
|
||||||
backupPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoteCommand is a Command implementation that is used to
|
|
||||||
// enable and disable remote state management
|
|
||||||
type RemoteCommand struct {
|
type RemoteCommand struct {
|
||||||
Meta
|
Meta
|
||||||
conf remoteCommandConfig
|
|
||||||
remoteConf terraform.RemoteState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RemoteCommand) Run(args []string) int {
|
func (c *RemoteCommand) Run(argsRaw []string) int {
|
||||||
|
// Duplicate the args so we can munge them without affecting
|
||||||
|
// future subcommand invocations which will do the same.
|
||||||
|
args := make([]string, len(argsRaw))
|
||||||
|
copy(args, argsRaw)
|
||||||
args = c.Meta.process(args, false)
|
args = c.Meta.process(args, false)
|
||||||
config := make(map[string]string)
|
|
||||||
cmdFlags := flag.NewFlagSet("remote", flag.ContinueOnError)
|
if len(args) == 0 {
|
||||||
cmdFlags.BoolVar(&c.conf.disableRemote, "disable", false, "")
|
c.Ui.Error(c.Help())
|
||||||
cmdFlags.BoolVar(&c.conf.pullOnDisable, "pull", true, "")
|
|
||||||
cmdFlags.StringVar(&c.conf.statePath, "state", DefaultStateFilename, "path")
|
|
||||||
cmdFlags.StringVar(&c.conf.backupPath, "backup", "", "path")
|
|
||||||
cmdFlags.StringVar(&c.remoteConf.Type, "backend", "atlas", "")
|
|
||||||
cmdFlags.Var((*FlagKV)(&config), "backend-config", "config")
|
|
||||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show help if given no inputs
|
switch args[0] {
|
||||||
if !c.conf.disableRemote && c.remoteConf.Type == "atlas" && len(config) == 0 {
|
case "config":
|
||||||
cmdFlags.Usage()
|
cmd := &RemoteConfigCommand{Meta: c.Meta}
|
||||||
|
return cmd.Run(args[1:])
|
||||||
|
case "pull":
|
||||||
|
cmd := &RemotePullCommand{Meta: c.Meta}
|
||||||
|
return cmd.Run(args[1:])
|
||||||
|
case "push":
|
||||||
|
cmd := &RemotePushCommand{Meta: c.Meta}
|
||||||
|
return cmd.Run(args[1:])
|
||||||
|
default:
|
||||||
|
c.Ui.Error(c.Help())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the local state path
|
|
||||||
c.statePath = c.conf.statePath
|
|
||||||
|
|
||||||
// Populate the various configurations
|
|
||||||
c.remoteConf.Config = config
|
|
||||||
|
|
||||||
// Get the state information. We specifically request the cache only
|
|
||||||
// for the remote state here because it is possible the remote state
|
|
||||||
// is invalid and we don't want to error.
|
|
||||||
stateOpts := c.StateOpts()
|
|
||||||
stateOpts.RemoteCacheOnly = true
|
|
||||||
if _, err := c.StateRaw(stateOpts); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Error loading local state: %s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the local and remote [cached] state
|
|
||||||
localState := c.stateResult.Local.State()
|
|
||||||
var remoteState *terraform.State
|
|
||||||
if remote := c.stateResult.Remote; remote != nil {
|
|
||||||
remoteState = remote.State()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if remote state is being disabled
|
|
||||||
if c.conf.disableRemote {
|
|
||||||
if !remoteState.IsRemote() {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Remote state management not enabled! Aborting."))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if !localState.Empty() {
|
|
||||||
c.Ui.Error(fmt.Sprintf("State file already exists at '%s'. Aborting.",
|
|
||||||
c.conf.statePath))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.disableRemoteState()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure there is no conflict
|
|
||||||
haveCache := !remoteState.Empty()
|
|
||||||
haveLocal := !localState.Empty()
|
|
||||||
switch {
|
|
||||||
case haveCache && haveLocal:
|
|
||||||
c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!",
|
|
||||||
c.conf.statePath))
|
|
||||||
return 1
|
|
||||||
|
|
||||||
case !haveCache && !haveLocal:
|
|
||||||
// If we don't have either state file, initialize a blank state file
|
|
||||||
return c.initBlankState()
|
|
||||||
|
|
||||||
case haveCache && !haveLocal:
|
|
||||||
// Update the remote state target potentially
|
|
||||||
return c.updateRemoteConfig()
|
|
||||||
|
|
||||||
case !haveCache && haveLocal:
|
|
||||||
// Enable remote state management
|
|
||||||
return c.enableRemoteState()
|
|
||||||
}
|
|
||||||
|
|
||||||
panic("unhandled case")
|
|
||||||
}
|
|
||||||
|
|
||||||
// disableRemoteState is used to disable remote state management,
|
|
||||||
// and move the state file into place.
|
|
||||||
func (c *RemoteCommand) disableRemoteState() int {
|
|
||||||
if c.stateResult == nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf(
|
|
||||||
"Internal error. State() must be called internally before remote\n" +
|
|
||||||
"state can be disabled. Please report this as a bug."))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if !c.stateResult.State.State().IsRemote() {
|
|
||||||
c.Ui.Error(fmt.Sprintf(
|
|
||||||
"Remote state is not enabled. Can't disable remote state."))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
local := c.stateResult.Local
|
|
||||||
remote := c.stateResult.Remote
|
|
||||||
|
|
||||||
// Ensure we have the latest state before disabling
|
|
||||||
if c.conf.pullOnDisable {
|
|
||||||
log.Printf("[INFO] Refreshing local state from remote server")
|
|
||||||
if err := remote.RefreshState(); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf(
|
|
||||||
"Failed to refresh from remote state: %s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit if we were unable to update
|
|
||||||
if change := remote.RefreshResult(); !change.SuccessfulPull() {
|
|
||||||
c.Ui.Error(fmt.Sprintf("%s", change))
|
|
||||||
return 1
|
|
||||||
} else {
|
|
||||||
log.Printf("[INFO] %s", change)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the remote management, and copy into place
|
|
||||||
newState := remote.State()
|
|
||||||
newState.Remote = nil
|
|
||||||
if err := local.WriteState(newState); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
|
|
||||||
c.conf.statePath, err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if err := local.PersistState(); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
|
|
||||||
c.conf.statePath, err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the old state file
|
|
||||||
if err := os.Remove(c.stateResult.RemotePath); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to remove the local state file: %v", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateRemoteConfig is used to verify that the remote configuration
|
|
||||||
// we have is valid
|
|
||||||
func (c *RemoteCommand) validateRemoteConfig() error {
|
|
||||||
conf := c.remoteConf
|
|
||||||
_, err := remote.NewClient(conf.Type, conf.Config)
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("%s", err))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// initBlank state is used to initialize a blank state that is
|
|
||||||
// remote enabled
|
|
||||||
func (c *RemoteCommand) initBlankState() int {
|
|
||||||
// Validate the remote configuration
|
|
||||||
if err := c.validateRemoteConfig(); err != nil {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a blank state, attach the remote configuration
|
|
||||||
blank := terraform.NewState()
|
|
||||||
blank.Remote = &c.remoteConf
|
|
||||||
|
|
||||||
// Persist the state
|
|
||||||
remote := &state.LocalState{Path: c.stateResult.RemotePath}
|
|
||||||
if err := remote.WriteState(blank); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if err := remote.PersistState(); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success!
|
|
||||||
c.Ui.Output("Initialized blank state with remote state enabled!")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateRemoteConfig is used to update the configuration of the
|
|
||||||
// remote state store
|
|
||||||
func (c *RemoteCommand) updateRemoteConfig() int {
|
|
||||||
// Validate the remote configuration
|
|
||||||
if err := c.validateRemoteConfig(); err != nil {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read in the local state, which is just the cache of the remote state
|
|
||||||
remote := c.stateResult.Remote.Cache
|
|
||||||
|
|
||||||
// Update the configuration
|
|
||||||
state := remote.State()
|
|
||||||
state.Remote = &c.remoteConf
|
|
||||||
if err := remote.WriteState(state); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("%s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if err := remote.PersistState(); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("%s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success!
|
|
||||||
c.Ui.Output("Remote configuration updated")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// enableRemoteState is used to enable remote state management
|
|
||||||
// and to move a state file into place
|
|
||||||
func (c *RemoteCommand) enableRemoteState() int {
|
|
||||||
// Validate the remote configuration
|
|
||||||
if err := c.validateRemoteConfig(); err != nil {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the local state
|
|
||||||
local := c.stateResult.Local
|
|
||||||
if err := local.RefreshState(); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to read local state: %s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backup the state file before we modify it
|
|
||||||
backupPath := c.conf.backupPath
|
|
||||||
if backupPath != "-" {
|
|
||||||
// Provide default backup path if none provided
|
|
||||||
if backupPath == "" {
|
|
||||||
backupPath = c.conf.statePath + DefaultBackupExtention
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[INFO] Writing backup state to: %s", backupPath)
|
|
||||||
backup := &state.LocalState{Path: backupPath}
|
|
||||||
if err := backup.WriteState(local.State()); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if err := backup.PersistState(); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the local configuration, move into place
|
|
||||||
state := local.State()
|
|
||||||
state.Remote = &c.remoteConf
|
|
||||||
remote := c.stateResult.Remote
|
|
||||||
if err := remote.WriteState(state); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("%s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if err := remote.PersistState(); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("%s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the original, local state file
|
|
||||||
log.Printf("[INFO] Removing state file: %s", c.conf.statePath)
|
|
||||||
if err := os.Remove(c.conf.statePath); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to remove state file '%s': %v",
|
|
||||||
c.conf.statePath, err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success!
|
|
||||||
c.Ui.Output("Remote state management enabled")
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RemoteCommand) Help() string {
|
func (c *RemoteCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: terraform remote [options]
|
Usage: terraform remote <subcommand> [options]
|
||||||
|
|
||||||
Configures Terraform to use a remote state server. This allows state
|
Configure remote state storage with Terraform.
|
||||||
to be pulled down when necessary and then pushed to the server when
|
|
||||||
updated. In this mode, the state file does not need to be stored durably
|
|
||||||
since the remote server provides the durability.
|
|
||||||
|
|
||||||
Options:
|
Available subcommands:
|
||||||
|
|
||||||
-backend=Atlas Specifies the type of remote backend. Must be one
|
config Configure the remote storage settings.
|
||||||
of Atlas, Consul, or HTTP. Defaults to Atlas.
|
pull Sync the remote storage by downloading to local storage.
|
||||||
|
push Sync the remote storage by uploading the local storage.
|
||||||
-backend-config="k=v" Specifies configuration for the remote storage
|
|
||||||
backend. This can be specified multiple times.
|
|
||||||
|
|
||||||
-backup=path Path to backup the existing state file before
|
|
||||||
modifying. Defaults to the "-state" path with
|
|
||||||
".backup" extension. Set to "-" to disable backup.
|
|
||||||
|
|
||||||
-disable Disables remote state management and migrates the state
|
|
||||||
to the -state path.
|
|
||||||
|
|
||||||
-pull=true Controls if the remote state is pulled before disabling.
|
|
||||||
This defaults to true to ensure the latest state is cached
|
|
||||||
before disabling.
|
|
||||||
|
|
||||||
-state=path Path to read state. Defaults to "terraform.tfstate"
|
|
||||||
unless remote state is enabled.
|
|
||||||
|
|
||||||
`
|
`
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RemoteCommand) Synopsis() string {
|
func (c *RemoteCommand) Synopsis() string {
|
||||||
return "Configures remote state management"
|
return "Configure remote state storage"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
|
@ -8,11 +8,11 @@ import (
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PullCommand struct {
|
type RemotePullCommand struct {
|
||||||
Meta
|
Meta
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PullCommand) Run(args []string) int {
|
func (c *RemotePullCommand) Run(args []string) int {
|
||||||
args = c.Meta.process(args, false)
|
args = c.Meta.process(args, false)
|
||||||
cmdFlags := flag.NewFlagSet("pull", flag.ContinueOnError)
|
cmdFlags := flag.NewFlagSet("pull", flag.ContinueOnError)
|
||||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||||
|
@ -67,7 +67,7 @@ func (c *PullCommand) Run(args []string) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PullCommand) Help() string {
|
func (c *RemotePullCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: terraform pull [options]
|
Usage: terraform pull [options]
|
||||||
|
|
||||||
|
@ -77,6 +77,6 @@ Usage: terraform pull [options]
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PullCommand) Synopsis() string {
|
func (c *RemotePullCommand) Synopsis() string {
|
||||||
return "Refreshes the local state copy from the remote server"
|
return "Refreshes the local state copy from the remote server"
|
||||||
}
|
}
|
|
@ -15,12 +15,12 @@ import (
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPull_noRemote(t *testing.T) {
|
func TestRemotePull_noRemote(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &PullCommand{
|
c := &RemotePullCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
ContextOpts: testCtxConfig(testProvider()),
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
|
@ -33,7 +33,7 @@ func TestPull_noRemote(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPull_local(t *testing.T) {
|
func TestRemotePull_local(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ func TestPull_local(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &PullCommand{
|
c := &RemotePullCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
ContextOpts: testCtxConfig(testProvider()),
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
Ui: ui,
|
Ui: ui,
|
|
@ -8,11 +8,11 @@ import (
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PushCommand struct {
|
type RemotePushCommand struct {
|
||||||
Meta
|
Meta
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PushCommand) Run(args []string) int {
|
func (c *RemotePushCommand) Run(args []string) int {
|
||||||
var force bool
|
var force bool
|
||||||
args = c.Meta.process(args, false)
|
args = c.Meta.process(args, false)
|
||||||
cmdFlags := flag.NewFlagSet("push", flag.ContinueOnError)
|
cmdFlags := flag.NewFlagSet("push", flag.ContinueOnError)
|
||||||
|
@ -71,7 +71,7 @@ func (c *PushCommand) Run(args []string) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PushCommand) Help() string {
|
func (c *RemotePushCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: terraform push [options]
|
Usage: terraform push [options]
|
||||||
|
|
||||||
|
@ -87,6 +87,6 @@ Options:
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PushCommand) Synopsis() string {
|
func (c *RemotePushCommand) Synopsis() string {
|
||||||
return "Uploads the the local state to the remote server"
|
return "Uploads the the local state to the remote server"
|
||||||
}
|
}
|
|
@ -9,12 +9,12 @@ import (
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPush_noRemote(t *testing.T) {
|
func TestRemotePush_noRemote(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &PushCommand{
|
c := &RemotePushCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
ContextOpts: testCtxConfig(testProvider()),
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
|
@ -27,7 +27,7 @@ func TestPush_noRemote(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPush_local(t *testing.T) {
|
func TestRemotePush_local(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ func TestPush_local(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &PushCommand{
|
c := &RemotePushCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
ContextOpts: testCtxConfig(testProvider()),
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
Ui: ui,
|
Ui: ui,
|
|
@ -13,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test disabling remote management
|
// Test disabling remote management
|
||||||
func TestRemote_disable(t *testing.T) {
|
func TestRemoteConfig_disable(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ func TestRemote_disable(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &RemoteCommand{
|
c := &RemoteConfigCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
ContextOpts: testCtxConfig(testProvider()),
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
|
@ -68,7 +68,7 @@ func TestRemote_disable(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test disabling remote management without pulling
|
// Test disabling remote management without pulling
|
||||||
func TestRemote_disable_noPull(t *testing.T) {
|
func TestRemoteConfig_disable_noPull(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ func TestRemote_disable_noPull(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &RemoteCommand{
|
c := &RemoteConfigCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
ContextOpts: testCtxConfig(testProvider()),
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
|
@ -122,12 +122,12 @@ func TestRemote_disable_noPull(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test disabling remote management when not enabled
|
// Test disabling remote management when not enabled
|
||||||
func TestRemote_disable_notEnabled(t *testing.T) {
|
func TestRemoteConfig_disable_notEnabled(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &RemoteCommand{
|
c := &RemoteConfigCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
ContextOpts: testCtxConfig(testProvider()),
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
|
@ -141,7 +141,7 @@ func TestRemote_disable_notEnabled(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test disabling remote management with a state file in the way
|
// Test disabling remote management with a state file in the way
|
||||||
func TestRemote_disable_otherState(t *testing.T) {
|
func TestRemoteConfig_disable_otherState(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ func TestRemote_disable_otherState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &RemoteCommand{
|
c := &RemoteConfigCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
ContextOpts: testCtxConfig(testProvider()),
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
|
@ -185,7 +185,7 @@ func TestRemote_disable_otherState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the case where both managed and non managed state present
|
// Test the case where both managed and non managed state present
|
||||||
func TestRemote_managedAndNonManaged(t *testing.T) {
|
func TestRemoteConfig_managedAndNonManaged(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ func TestRemote_managedAndNonManaged(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &RemoteCommand{
|
c := &RemoteConfigCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
ContextOpts: testCtxConfig(testProvider()),
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
|
@ -229,12 +229,12 @@ func TestRemote_managedAndNonManaged(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test initializing blank state
|
// Test initializing blank state
|
||||||
func TestRemote_initBlank(t *testing.T) {
|
func TestRemoteConfig_initBlank(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &RemoteCommand{
|
c := &RemoteConfigCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
ContextOpts: testCtxConfig(testProvider()),
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
|
@ -269,12 +269,12 @@ func TestRemote_initBlank(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test initializing without remote settings
|
// Test initializing without remote settings
|
||||||
func TestRemote_initBlank_missingRemote(t *testing.T) {
|
func TestRemoteConfig_initBlank_missingRemote(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &RemoteCommand{
|
c := &RemoteConfigCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
ContextOpts: testCtxConfig(testProvider()),
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
|
@ -288,7 +288,7 @@ func TestRemote_initBlank_missingRemote(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test updating remote config
|
// Test updating remote config
|
||||||
func TestRemote_updateRemote(t *testing.T) {
|
func TestRemoteConfig_updateRemote(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
|
@ -310,7 +310,7 @@ func TestRemote_updateRemote(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &RemoteCommand{
|
c := &RemoteConfigCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
ContextOpts: testCtxConfig(testProvider()),
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
|
@ -345,7 +345,7 @@ func TestRemote_updateRemote(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test enabling remote state
|
// Test enabling remote state
|
||||||
func TestRemote_enableRemote(t *testing.T) {
|
func TestRemoteConfig_enableRemote(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
|
@ -365,7 +365,7 @@ func TestRemote_enableRemote(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &RemoteCommand{
|
c := &RemoteConfigCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
ContextOpts: testCtxConfig(testProvider()),
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
|
|
12
commands.go
12
commands.go
|
@ -80,18 +80,6 @@ func init() {
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
||||||
"pull": func() (cli.Command, error) {
|
|
||||||
return &command.PullCommand{
|
|
||||||
Meta: meta,
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
|
|
||||||
"push": func() (cli.Command, error) {
|
|
||||||
return &command.PushCommand{
|
|
||||||
Meta: meta,
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
|
|
||||||
"refresh": func() (cli.Command, error) {
|
"refresh": func() (cli.Command, error) {
|
||||||
return &command.RefreshCommand{
|
return &command.RefreshCommand{
|
||||||
Meta: meta,
|
Meta: meta,
|
||||||
|
|
|
@ -17,6 +17,7 @@ var Funcs map[string]ast.Function
|
||||||
func init() {
|
func init() {
|
||||||
Funcs = map[string]ast.Function{
|
Funcs = map[string]ast.Function{
|
||||||
"file": interpolationFuncFile(),
|
"file": interpolationFuncFile(),
|
||||||
|
"format": interpolationFuncFormat(),
|
||||||
"join": interpolationFuncJoin(),
|
"join": interpolationFuncJoin(),
|
||||||
"element": interpolationFuncElement(),
|
"element": interpolationFuncElement(),
|
||||||
"replace": interpolationFuncReplace(),
|
"replace": interpolationFuncReplace(),
|
||||||
|
@ -66,6 +67,21 @@ func interpolationFuncFile() ast.Function {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// interpolationFuncFormat implements the "replace" function that does
|
||||||
|
// string replacement.
|
||||||
|
func interpolationFuncFormat() ast.Function {
|
||||||
|
return ast.Function{
|
||||||
|
ArgTypes: []ast.Type{ast.TypeString},
|
||||||
|
Variadic: true,
|
||||||
|
VariadicType: ast.TypeAny,
|
||||||
|
ReturnType: ast.TypeString,
|
||||||
|
Callback: func(args []interface{}) (interface{}, error) {
|
||||||
|
format := args[0].(string)
|
||||||
|
return fmt.Sprintf(format, args[1:]...), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// interpolationFuncJoin implements the "join" function that allows
|
// interpolationFuncJoin implements the "join" function that allows
|
||||||
// multi-variable values to be joined by some character.
|
// multi-variable values to be joined by some character.
|
||||||
func interpolationFuncJoin() ast.Function {
|
func interpolationFuncJoin() ast.Function {
|
||||||
|
|
|
@ -70,6 +70,42 @@ func TestInterpolateFuncFile(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInterpolateFuncFormat(t *testing.T) {
|
||||||
|
testFunction(t, testFunctionConfig{
|
||||||
|
Cases: []testFunctionCase{
|
||||||
|
{
|
||||||
|
`${format("hello")}`,
|
||||||
|
"hello",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
`${format("hello %s", "world")}`,
|
||||||
|
"hello world",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
`${format("hello %d", 42)}`,
|
||||||
|
"hello 42",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
`${format("hello %05d", 42)}`,
|
||||||
|
"hello 00042",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
`${format("hello %05d", 12345)}`,
|
||||||
|
"hello 12345",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestInterpolateFuncJoin(t *testing.T) {
|
func TestInterpolateFuncJoin(t *testing.T) {
|
||||||
testFunction(t, testFunctionConfig{
|
testFunction(t, testFunctionConfig{
|
||||||
Cases: []testFunctionCase{
|
Cases: []testFunctionCase{
|
||||||
|
|
|
@ -48,7 +48,8 @@ type Type uint32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeInvalid Type = 0
|
TypeInvalid Type = 0
|
||||||
TypeString Type = 1 << iota
|
TypeAny Type = 1 << iota
|
||||||
|
TypeString
|
||||||
TypeInt
|
TypeInt
|
||||||
TypeFloat
|
TypeFloat
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,16 +6,18 @@ import "fmt"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_Type_name_0 = "TypeInvalid"
|
_Type_name_0 = "TypeInvalid"
|
||||||
_Type_name_1 = "TypeString"
|
_Type_name_1 = "TypeAny"
|
||||||
_Type_name_2 = "TypeInt"
|
_Type_name_2 = "TypeString"
|
||||||
_Type_name_3 = "TypeFloat"
|
_Type_name_3 = "TypeInt"
|
||||||
|
_Type_name_4 = "TypeFloat"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_Type_index_0 = [...]uint8{0, 11}
|
_Type_index_0 = [...]uint8{0, 11}
|
||||||
_Type_index_1 = [...]uint8{0, 10}
|
_Type_index_1 = [...]uint8{0, 7}
|
||||||
_Type_index_2 = [...]uint8{0, 7}
|
_Type_index_2 = [...]uint8{0, 10}
|
||||||
_Type_index_3 = [...]uint8{0, 9}
|
_Type_index_3 = [...]uint8{0, 7}
|
||||||
|
_Type_index_4 = [...]uint8{0, 9}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (i Type) String() string {
|
func (i Type) String() string {
|
||||||
|
@ -28,6 +30,8 @@ func (i Type) String() string {
|
||||||
return _Type_name_2
|
return _Type_name_2
|
||||||
case i == 8:
|
case i == 8:
|
||||||
return _Type_name_3
|
return _Type_name_3
|
||||||
|
case i == 16:
|
||||||
|
return _Type_name_4
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("Type(%d)", i)
|
return fmt.Sprintf("Type(%d)", i)
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,6 +174,10 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
||||||
|
|
||||||
// Verify the args
|
// Verify the args
|
||||||
for i, expected := range function.ArgTypes {
|
for i, expected := range function.ArgTypes {
|
||||||
|
if expected == ast.TypeAny {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if args[i] != expected {
|
if args[i] != expected {
|
||||||
cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i])
|
cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i])
|
||||||
if cn != nil {
|
if cn != nil {
|
||||||
|
@ -188,7 +192,7 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're variadic, then verify the types there
|
// If we're variadic, then verify the types there
|
||||||
if function.Variadic {
|
if function.Variadic && function.VariadicType != ast.TypeAny {
|
||||||
args = args[len(function.ArgTypes):]
|
args = args[len(function.ArgTypes):]
|
||||||
for i, t := range args {
|
for i, t := range args {
|
||||||
if t != function.VariadicType {
|
if t != function.VariadicType {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package schema
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
@ -24,6 +25,31 @@ type Resource struct {
|
||||||
// resource.
|
// resource.
|
||||||
Schema map[string]*Schema
|
Schema map[string]*Schema
|
||||||
|
|
||||||
|
// SchemaVersion is the version number for this resource's Schema
|
||||||
|
// definition. The current SchemaVersion stored in the state for each
|
||||||
|
// resource. Provider authors can increment this version number
|
||||||
|
// when Schema semantics change. If the State's SchemaVersion is less than
|
||||||
|
// the current SchemaVersion, the InstanceState is yielded to the
|
||||||
|
// MigrateState callback, where the provider can make whatever changes it
|
||||||
|
// needs to update the state to be compatible to the latest version of the
|
||||||
|
// Schema.
|
||||||
|
//
|
||||||
|
// When unset, SchemaVersion defaults to 0, so provider authors can start
|
||||||
|
// their Versioning at any integer >= 1
|
||||||
|
SchemaVersion int
|
||||||
|
|
||||||
|
// MigrateState is responsible for updating an InstanceState with an old
|
||||||
|
// version to the format expected by the current version of the Schema.
|
||||||
|
//
|
||||||
|
// It is called during Refresh if the State's stored SchemaVersion is less
|
||||||
|
// than the current SchemaVersion of the Resource.
|
||||||
|
//
|
||||||
|
// The function is yielded the state's stored SchemaVersion and a pointer to
|
||||||
|
// the InstanceState that needs updating, as well as the configured
|
||||||
|
// provider's configured meta interface{}, in case the migration process
|
||||||
|
// needs to make any remote API calls.
|
||||||
|
MigrateState StateMigrateFunc
|
||||||
|
|
||||||
// The functions below are the CRUD operations for this resource.
|
// The functions below are the CRUD operations for this resource.
|
||||||
//
|
//
|
||||||
// The only optional operation is Update. If Update is not implemented,
|
// The only optional operation is Update. If Update is not implemented,
|
||||||
|
@ -69,6 +95,10 @@ type DeleteFunc func(*ResourceData, interface{}) error
|
||||||
// See Resource documentation.
|
// See Resource documentation.
|
||||||
type ExistsFunc func(*ResourceData, interface{}) (bool, error)
|
type ExistsFunc func(*ResourceData, interface{}) (bool, error)
|
||||||
|
|
||||||
|
// See Resource documentation.
|
||||||
|
type StateMigrateFunc func(
|
||||||
|
int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error)
|
||||||
|
|
||||||
// Apply creates, updates, and/or deletes a resource.
|
// Apply creates, updates, and/or deletes a resource.
|
||||||
func (r *Resource) Apply(
|
func (r *Resource) Apply(
|
||||||
s *terraform.InstanceState,
|
s *terraform.InstanceState,
|
||||||
|
@ -158,6 +188,14 @@ func (r *Resource) Refresh(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needsMigration, stateSchemaVersion := r.checkSchemaVersion(s)
|
||||||
|
if needsMigration && r.MigrateState != nil {
|
||||||
|
s, err := r.MigrateState(stateSchemaVersion, s, meta)
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data, err := schemaMap(r.Schema).Data(s, nil)
|
data, err := schemaMap(r.Schema).Data(s, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return s, err
|
||||||
|
@ -169,6 +207,13 @@ func (r *Resource) Refresh(
|
||||||
state = nil
|
state = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if state != nil && r.SchemaVersion > 0 {
|
||||||
|
if state.Meta == nil {
|
||||||
|
state.Meta = make(map[string]string)
|
||||||
|
}
|
||||||
|
state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion)
|
||||||
|
}
|
||||||
|
|
||||||
return state, err
|
return state, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,3 +234,10 @@ func (r *Resource) InternalValidate() error {
|
||||||
|
|
||||||
return schemaMap(r.Schema).InternalValidate()
|
return schemaMap(r.Schema).InternalValidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determines if a given InstanceState needs to be migrated by checking the
|
||||||
|
// stored version number with the current SchemaVersion
|
||||||
|
func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) {
|
||||||
|
stateSchemaVersion, _ := strconv.Atoi(is.Meta["schema_version"])
|
||||||
|
return stateSchemaVersion < r.SchemaVersion, stateSchemaVersion
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package schema
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
@ -478,3 +479,218 @@ func TestResourceRefresh_noExists(t *testing.T) {
|
||||||
t.Fatalf("should have no state")
|
t.Fatalf("should have no state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResourceRefresh_needsMigration(t *testing.T) {
|
||||||
|
// Schema v2 it deals only in newfoo, which tracks foo as an int
|
||||||
|
r := &Resource{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"newfoo": &Schema{
|
||||||
|
Type: TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Read = func(d *ResourceData, m interface{}) error {
|
||||||
|
return d.Set("newfoo", d.Get("newfoo").(int)+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.MigrateState = func(
|
||||||
|
v int,
|
||||||
|
s *terraform.InstanceState,
|
||||||
|
meta interface{}) (*terraform.InstanceState, error) {
|
||||||
|
// Real state migration functions will probably switch on this value,
|
||||||
|
// but we'll just assert on it for now.
|
||||||
|
if v != 1 {
|
||||||
|
t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta != 42 {
|
||||||
|
t.Fatal("Expected meta to be passed through to the migration function")
|
||||||
|
}
|
||||||
|
|
||||||
|
oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %#v", err)
|
||||||
|
}
|
||||||
|
s.Attributes["newfoo"] = strconv.Itoa((int(oldfoo * 10)))
|
||||||
|
delete(s.Attributes, "oldfoo")
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th
|
||||||
|
// the scale of newfoo
|
||||||
|
s := &terraform.InstanceState{
|
||||||
|
ID: "bar",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"oldfoo": "1.2",
|
||||||
|
},
|
||||||
|
Meta: map[string]string{
|
||||||
|
"schema_version": "1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := r.Refresh(s, 42)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &terraform.InstanceState{
|
||||||
|
ID: "bar",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"id": "bar",
|
||||||
|
"newfoo": "13",
|
||||||
|
},
|
||||||
|
Meta: map[string]string{
|
||||||
|
"schema_version": "2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceRefresh_noMigrationNeeded(t *testing.T) {
|
||||||
|
r := &Resource{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"newfoo": &Schema{
|
||||||
|
Type: TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Read = func(d *ResourceData, m interface{}) error {
|
||||||
|
return d.Set("newfoo", d.Get("newfoo").(int)+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.MigrateState = func(
|
||||||
|
v int,
|
||||||
|
s *terraform.InstanceState,
|
||||||
|
meta interface{}) (*terraform.InstanceState, error) {
|
||||||
|
t.Fatal("Migrate function shouldn't be called!")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &terraform.InstanceState{
|
||||||
|
ID: "bar",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"newfoo": "12",
|
||||||
|
},
|
||||||
|
Meta: map[string]string{
|
||||||
|
"schema_version": "2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := r.Refresh(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &terraform.InstanceState{
|
||||||
|
ID: "bar",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"id": "bar",
|
||||||
|
"newfoo": "13",
|
||||||
|
},
|
||||||
|
Meta: map[string]string{
|
||||||
|
"schema_version": "2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) {
|
||||||
|
r := &Resource{
|
||||||
|
// Version 1 > Version 0
|
||||||
|
SchemaVersion: 1,
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"newfoo": &Schema{
|
||||||
|
Type: TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Read = func(d *ResourceData, m interface{}) error {
|
||||||
|
return d.Set("newfoo", d.Get("newfoo").(int)+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.MigrateState = func(
|
||||||
|
v int,
|
||||||
|
s *terraform.InstanceState,
|
||||||
|
meta interface{}) (*terraform.InstanceState, error) {
|
||||||
|
s.Attributes["newfoo"] = s.Attributes["oldfoo"]
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &terraform.InstanceState{
|
||||||
|
ID: "bar",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"oldfoo": "12",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := r.Refresh(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &terraform.InstanceState{
|
||||||
|
ID: "bar",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"id": "bar",
|
||||||
|
"newfoo": "13",
|
||||||
|
},
|
||||||
|
Meta: map[string]string{
|
||||||
|
"schema_version": "1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceRefresh_migrateStateErr(t *testing.T) {
|
||||||
|
r := &Resource{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"newfoo": &Schema{
|
||||||
|
Type: TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Read = func(d *ResourceData, m interface{}) error {
|
||||||
|
t.Fatal("Read should never be called!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r.MigrateState = func(
|
||||||
|
v int,
|
||||||
|
s *terraform.InstanceState,
|
||||||
|
meta interface{}) (*terraform.InstanceState, error) {
|
||||||
|
return s, fmt.Errorf("triggering an error")
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &terraform.InstanceState{
|
||||||
|
ID: "bar",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"oldfoo": "12",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := r.Refresh(s, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error, but got none!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -113,6 +113,21 @@ type Schema struct {
|
||||||
//
|
//
|
||||||
// NOTE: This currently does not work.
|
// NOTE: This currently does not work.
|
||||||
ComputedWhen []string
|
ComputedWhen []string
|
||||||
|
|
||||||
|
// When Deprecated is set, this attribute is deprecated.
|
||||||
|
//
|
||||||
|
// A deprecated field still works, but will probably stop working in near
|
||||||
|
// future. This string is the message shown to the user with instructions on
|
||||||
|
// how to address the deprecation.
|
||||||
|
Deprecated string
|
||||||
|
|
||||||
|
// When Removed is set, this attribute has been removed from the schema
|
||||||
|
//
|
||||||
|
// Removed attributes can be left in the Schema to generate informative error
|
||||||
|
// messages for the user when they show up in resource configurations.
|
||||||
|
// This string is the message shown to the user with instructions on
|
||||||
|
// what do to about the removed attribute.
|
||||||
|
Removed string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SchemaDefaultFunc is a function called to return a default value for
|
// SchemaDefaultFunc is a function called to return a default value for
|
||||||
|
@ -877,7 +892,7 @@ func (m schemaMap) validate(
|
||||||
raw, err = schema.DefaultFunc()
|
raw, err = schema.DefaultFunc()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, []error{fmt.Errorf(
|
return nil, []error{fmt.Errorf(
|
||||||
"%s, error loading default: %s", k, err)}
|
"%q, error loading default: %s", k, err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're okay as long as we had a value set
|
// We're okay as long as we had a value set
|
||||||
|
@ -886,7 +901,7 @@ func (m schemaMap) validate(
|
||||||
if !ok {
|
if !ok {
|
||||||
if schema.Required {
|
if schema.Required {
|
||||||
return nil, []error{fmt.Errorf(
|
return nil, []error{fmt.Errorf(
|
||||||
"%s: required field is not set", k)}
|
"%q: required field is not set", k)}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -895,7 +910,7 @@ func (m schemaMap) validate(
|
||||||
if !schema.Required && !schema.Optional {
|
if !schema.Required && !schema.Optional {
|
||||||
// This is a computed-only field
|
// This is a computed-only field
|
||||||
return nil, []error{fmt.Errorf(
|
return nil, []error{fmt.Errorf(
|
||||||
"%s: this field cannot be set", k)}
|
"%q: this field cannot be set", k)}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.validateType(k, raw, schema, c)
|
return m.validateType(k, raw, schema, c)
|
||||||
|
@ -1066,16 +1081,30 @@ func (m schemaMap) validateType(
|
||||||
raw interface{},
|
raw interface{},
|
||||||
schema *Schema,
|
schema *Schema,
|
||||||
c *terraform.ResourceConfig) ([]string, []error) {
|
c *terraform.ResourceConfig) ([]string, []error) {
|
||||||
|
var ws []string
|
||||||
|
var es []error
|
||||||
switch schema.Type {
|
switch schema.Type {
|
||||||
case TypeSet:
|
case TypeSet:
|
||||||
fallthrough
|
fallthrough
|
||||||
case TypeList:
|
case TypeList:
|
||||||
return m.validateList(k, raw, schema, c)
|
ws, es = m.validateList(k, raw, schema, c)
|
||||||
case TypeMap:
|
case TypeMap:
|
||||||
return m.validateMap(k, raw, schema, c)
|
ws, es = m.validateMap(k, raw, schema, c)
|
||||||
default:
|
default:
|
||||||
return m.validatePrimitive(k, raw, schema, c)
|
ws, es = m.validatePrimitive(k, raw, schema, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if schema.Deprecated != "" {
|
||||||
|
ws = append(ws, fmt.Sprintf(
|
||||||
|
"%q: [DEPRECATED] %s", k, schema.Deprecated))
|
||||||
|
}
|
||||||
|
|
||||||
|
if schema.Removed != "" {
|
||||||
|
es = append(es, fmt.Errorf(
|
||||||
|
"%q: [REMOVED] %s", k, schema.Removed))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ws, es
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zero returns the zero value for a type.
|
// Zero returns the zero value for a type.
|
||||||
|
|
|
@ -2583,15 +2583,15 @@ func TestSchemaMap_InternalValidate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSchemaMap_Validate(t *testing.T) {
|
func TestSchemaMap_Validate(t *testing.T) {
|
||||||
cases := []struct {
|
cases := map[string]struct {
|
||||||
Schema map[string]*Schema
|
Schema map[string]*Schema
|
||||||
Config map[string]interface{}
|
Config map[string]interface{}
|
||||||
Vars map[string]string
|
Vars map[string]string
|
||||||
Warn bool
|
|
||||||
Err bool
|
Err bool
|
||||||
|
Errors []error
|
||||||
|
Warnings []string
|
||||||
}{
|
}{
|
||||||
// #0 Good
|
"Good": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
|
@ -2606,8 +2606,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// #1 Good, because the var is not set and that error will come elsewhere
|
"Good, because the var is not set and that error will come elsewhere": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"size": &Schema{
|
"size": &Schema{
|
||||||
Type: TypeInt,
|
Type: TypeInt,
|
||||||
|
@ -2624,8 +2623,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// #2 Required field not set
|
"Required field not set": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
|
@ -2638,8 +2636,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #3 Invalid type
|
"Invalid basic type": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"port": &Schema{
|
"port": &Schema{
|
||||||
Type: TypeInt,
|
Type: TypeInt,
|
||||||
|
@ -2654,8 +2651,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #4
|
"Invalid complex type": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"user_data": &Schema{
|
"user_data": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
|
@ -2674,8 +2670,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #5 Bad type, interpolated
|
"Bad type, interpolated": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"size": &Schema{
|
"size": &Schema{
|
||||||
Type: TypeInt,
|
Type: TypeInt,
|
||||||
|
@ -2694,8 +2689,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #6 Required but has DefaultFunc
|
"Required but has DefaultFunc": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
|
@ -2709,8 +2703,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Config: nil,
|
Config: nil,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #7 Required but has DefaultFunc return nil
|
"Required but has DefaultFunc return nil": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
|
@ -2726,8 +2719,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #8 Optional sub-resource
|
"Optional sub-resource": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ingress": &Schema{
|
"ingress": &Schema{
|
||||||
Type: TypeList,
|
Type: TypeList,
|
||||||
|
@ -2747,8 +2739,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: false,
|
Err: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #9 Not a list
|
"Not a list": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ingress": &Schema{
|
"ingress": &Schema{
|
||||||
Type: TypeList,
|
Type: TypeList,
|
||||||
|
@ -2770,8 +2761,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #10 Required sub-resource field
|
"Required sub-resource field": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ingress": &Schema{
|
"ingress": &Schema{
|
||||||
Type: TypeList,
|
Type: TypeList,
|
||||||
|
@ -2795,8 +2785,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #11 Good sub-resource
|
"Good sub-resource": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ingress": &Schema{
|
"ingress": &Schema{
|
||||||
Type: TypeList,
|
Type: TypeList,
|
||||||
|
@ -2823,8 +2812,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: false,
|
Err: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #12 Invalid/unknown field
|
"Invalid/unknown field": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
|
@ -2841,8 +2829,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #13 Computed field set
|
"Computed field set": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
|
@ -2857,8 +2844,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #14 Not a set
|
"Not a set": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
Type: TypeSet,
|
Type: TypeSet,
|
||||||
|
@ -2877,8 +2863,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #15 Maps
|
"Maps": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"user_data": &Schema{
|
"user_data": &Schema{
|
||||||
Type: TypeMap,
|
Type: TypeMap,
|
||||||
|
@ -2893,8 +2878,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #16
|
"Good map: data surrounded by extra slice": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"user_data": &Schema{
|
"user_data": &Schema{
|
||||||
Type: TypeMap,
|
Type: TypeMap,
|
||||||
|
@ -2911,8 +2895,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// #17
|
"Good map": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"user_data": &Schema{
|
"user_data": &Schema{
|
||||||
Type: TypeMap,
|
Type: TypeMap,
|
||||||
|
@ -2927,8 +2910,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// #18
|
"Bad map: just a slice": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"user_data": &Schema{
|
"user_data": &Schema{
|
||||||
Type: TypeMap,
|
Type: TypeMap,
|
||||||
|
@ -2945,8 +2927,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #19
|
"Good set: config has slice with single interpolated value": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"security_groups": &Schema{
|
"security_groups": &Schema{
|
||||||
Type: TypeSet,
|
Type: TypeSet,
|
||||||
|
@ -2967,8 +2948,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: false,
|
Err: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #20
|
"Bad set: config has single interpolated value": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"security_groups": &Schema{
|
"security_groups": &Schema{
|
||||||
Type: TypeSet,
|
Type: TypeSet,
|
||||||
|
@ -2986,8 +2966,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #21 Bad, subresource should not allow unknown elements
|
"Bad, subresource should not allow unknown elements": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ingress": &Schema{
|
"ingress": &Schema{
|
||||||
Type: TypeList,
|
Type: TypeList,
|
||||||
|
@ -3015,8 +2994,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// #22 Bad, subresource should not allow invalid types
|
"Bad, subresource should not allow invalid types": {
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ingress": &Schema{
|
"ingress": &Schema{
|
||||||
Type: TypeList,
|
Type: TypeList,
|
||||||
|
@ -3042,9 +3020,74 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
|
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"Deprecated attribute usage generates warning, but not error": {
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"old_news": &Schema{
|
||||||
|
Type: TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Deprecated: "please use 'new_news' instead",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"old_news": "extra extra!",
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: false,
|
||||||
|
|
||||||
|
Warnings: []string{
|
||||||
|
"\"old_news\": [DEPRECATED] please use 'new_news' instead",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"Deprecated generates no warnings if attr not used": {
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"old_news": &Schema{
|
||||||
|
Type: TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Deprecated: "please use 'new_news' instead",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: false,
|
||||||
|
|
||||||
|
Warnings: nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
"Removed attribute usage generates error": {
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"long_gone": &Schema{
|
||||||
|
Type: TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Removed: "no longer supported by Cloud API",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"long_gone": "still here!",
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: true,
|
||||||
|
Errors: []error{
|
||||||
|
fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"Removed generates no errors if attr not used": {
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"long_gone": &Schema{
|
||||||
|
Type: TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Removed: "no longer supported by Cloud API",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for tn, tc := range cases {
|
||||||
c, err := config.NewRawConfig(tc.Config)
|
c, err := config.NewRawConfig(tc.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -3063,18 +3106,24 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
|
ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
|
||||||
if (len(es) > 0) != tc.Err {
|
if (len(es) > 0) != tc.Err {
|
||||||
if len(es) == 0 {
|
if len(es) == 0 {
|
||||||
t.Errorf("%d: no errors", i)
|
t.Errorf("%q: no errors", tn)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, e := range es {
|
for _, e := range es {
|
||||||
t.Errorf("%d: err: %s", i, e)
|
t.Errorf("%q: err: %s", tn, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len(ws) > 0) != tc.Warn {
|
if !reflect.DeepEqual(ws, tc.Warnings) {
|
||||||
t.Fatalf("%d: ws: %#v", i, ws)
|
t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.Errors != nil {
|
||||||
|
if !reflect.DeepEqual(es, tc.Errors) {
|
||||||
|
t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.google.com/p/go.crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RemoteCmd represents a remote command being prepared or run.
|
// RemoteCmd represents a remote command being prepared or run.
|
||||||
|
|
|
@ -4,7 +4,7 @@ package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"code.google.com/p/go.crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.google.com/p/go.crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.google.com/p/go.crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.google.com/p/go.crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
|
@ -19,9 +19,11 @@ GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
||||||
XC_ARCH=${XC_ARCH:-"386 amd64 arm"}
|
XC_ARCH=${XC_ARCH:-"386 amd64 arm"}
|
||||||
XC_OS=${XC_OS:-linux darwin windows freebsd openbsd}
|
XC_OS=${XC_OS:-linux darwin windows freebsd openbsd}
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies unless running in quick mode
|
||||||
echo "==> Getting dependencies..."
|
if [ "${TF_QUICKDEV}x" == "x" ]; then
|
||||||
go get ./...
|
echo "==> Getting dependencies..."
|
||||||
|
go get ./...
|
||||||
|
fi
|
||||||
|
|
||||||
# Delete the old dir
|
# Delete the old dir
|
||||||
echo "==> Removing old directory..."
|
echo "==> Removing old directory..."
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -39,4 +39,7 @@ var BuiltinClients = map[string]Factory{
|
||||||
"atlas": atlasFactory,
|
"atlas": atlasFactory,
|
||||||
"consul": consulFactory,
|
"consul": consulFactory,
|
||||||
"http": httpFactory,
|
"http": httpFactory,
|
||||||
|
|
||||||
|
// This is used for development purposes only.
|
||||||
|
"_local": fileFactory,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3845,6 +3845,136 @@ func TestContext2Apply_errorDestroy_createBeforeDestroy(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext2Apply_multiDepose_createBeforeDestroy(t *testing.T) {
|
||||||
|
m := testModule(t, "apply-multi-depose-create-before-destroy")
|
||||||
|
p := testProvider("aws")
|
||||||
|
p.DiffFn = testDiffFn
|
||||||
|
ps := map[string]ResourceProviderFactory{"aws": testProviderFuncFixed(p)}
|
||||||
|
state := &State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.web": &ResourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Primary: &InstanceState{ID: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
Providers: ps,
|
||||||
|
State: state,
|
||||||
|
})
|
||||||
|
createdInstanceId := "bar"
|
||||||
|
// Create works
|
||||||
|
createFunc := func(is *InstanceState) (*InstanceState, error) {
|
||||||
|
return &InstanceState{ID: createdInstanceId}, nil
|
||||||
|
}
|
||||||
|
// Destroy starts broken
|
||||||
|
destroyFunc := func(is *InstanceState) (*InstanceState, error) {
|
||||||
|
return is, fmt.Errorf("destroy failed")
|
||||||
|
}
|
||||||
|
p.ApplyFn = func(info *InstanceInfo, is *InstanceState, id *InstanceDiff) (*InstanceState, error) {
|
||||||
|
if id.Destroy {
|
||||||
|
return destroyFunc(is)
|
||||||
|
} else {
|
||||||
|
return createFunc(is)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := ctx.Plan(nil); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy is broken, so even though CBD successfully replaces the instance,
|
||||||
|
// we'll have to save the Deposed instance to destroy later
|
||||||
|
state, err := ctx.Apply()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStateString(t, state, `
|
||||||
|
aws_instance.web: (1 deposed)
|
||||||
|
ID = bar
|
||||||
|
Deposed ID 1 = foo
|
||||||
|
`)
|
||||||
|
|
||||||
|
createdInstanceId = "baz"
|
||||||
|
ctx = testContext2(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
Providers: ps,
|
||||||
|
State: state,
|
||||||
|
})
|
||||||
|
|
||||||
|
if _, err := ctx.Plan(nil); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're replacing the primary instance once again. Destroy is _still_
|
||||||
|
// broken, so the Deposed list gets longer
|
||||||
|
state, err = ctx.Apply()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStateString(t, state, `
|
||||||
|
aws_instance.web: (2 deposed)
|
||||||
|
ID = baz
|
||||||
|
Deposed ID 1 = foo
|
||||||
|
Deposed ID 2 = bar
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Destroy partially fixed!
|
||||||
|
destroyFunc = func(is *InstanceState) (*InstanceState, error) {
|
||||||
|
if is.ID == "foo" || is.ID == "baz" {
|
||||||
|
return nil, nil
|
||||||
|
} else {
|
||||||
|
return is, fmt.Errorf("destroy partially failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createdInstanceId = "qux"
|
||||||
|
if _, err := ctx.Plan(nil); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
state, err = ctx.Apply()
|
||||||
|
// Expect error because 1/2 of Deposed destroys failed
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// foo and baz are now gone, bar sticks around
|
||||||
|
checkStateString(t, state, `
|
||||||
|
aws_instance.web: (1 deposed)
|
||||||
|
ID = qux
|
||||||
|
Deposed ID 1 = bar
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Destroy working fully!
|
||||||
|
destroyFunc = func(is *InstanceState) (*InstanceState, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
createdInstanceId = "quux"
|
||||||
|
if _, err := ctx.Plan(nil); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
state, err = ctx.Apply()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("should not have error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// And finally the state is clean
|
||||||
|
checkStateString(t, state, `
|
||||||
|
aws_instance.web:
|
||||||
|
ID = quux
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
func TestContext2Apply_provisionerResourceRef(t *testing.T) {
|
func TestContext2Apply_provisionerResourceRef(t *testing.T) {
|
||||||
m := testModule(t, "apply-provisioner-resource-ref")
|
m := testModule(t, "apply-provisioner-resource-ref")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
@ -5343,6 +5473,15 @@ func testProvisioner() *MockResourceProvisioner {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkStateString(t *testing.T, state *State, expected string) {
|
||||||
|
actual := strings.TrimSpace(state.String())
|
||||||
|
expected = strings.TrimSpace(expected)
|
||||||
|
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("state does not match! actual:\n%s\n\nexpected:\n%s", actual, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const testContextGraph = `
|
const testContextGraph = `
|
||||||
root: root
|
root: root
|
||||||
aws_instance.bar
|
aws_instance.bar
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -3,7 +3,8 @@ package terraform
|
||||||
// EvalIf is an EvalNode that is a conditional.
|
// EvalIf is an EvalNode that is a conditional.
|
||||||
type EvalIf struct {
|
type EvalIf struct {
|
||||||
If func(EvalContext) (bool, error)
|
If func(EvalContext) (bool, error)
|
||||||
Node EvalNode
|
Then EvalNode
|
||||||
|
Else EvalNode
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
// TODO: test
|
||||||
|
@ -14,7 +15,11 @@ func (n *EvalIf) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if yes {
|
if yes {
|
||||||
return EvalRaw(n.Node, ctx)
|
return EvalRaw(n.Then, ctx)
|
||||||
|
} else {
|
||||||
|
if n.Else != nil {
|
||||||
|
return EvalRaw(n.Else, ctx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -5,16 +5,77 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// EvalReadState is an EvalNode implementation that reads the
|
// EvalReadState is an EvalNode implementation that reads the
|
||||||
// InstanceState for a specific resource out of the state.
|
// primary InstanceState for a specific resource out of the state.
|
||||||
type EvalReadState struct {
|
type EvalReadState struct {
|
||||||
Name string
|
Name string
|
||||||
Tainted bool
|
|
||||||
TaintedIndex int
|
|
||||||
Output **InstanceState
|
Output **InstanceState
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
|
||||||
func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) {
|
func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
|
return readInstanceFromState(ctx, n.Name, n.Output, func(rs *ResourceState) (*InstanceState, error) {
|
||||||
|
return rs.Primary, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// EvalReadStateTainted is an EvalNode implementation that reads a
|
||||||
|
// tainted InstanceState for a specific resource out of the state
|
||||||
|
type EvalReadStateTainted struct {
|
||||||
|
Name string
|
||||||
|
Output **InstanceState
|
||||||
|
// Index indicates which instance in the Tainted list to target, or -1 for
|
||||||
|
// the last item.
|
||||||
|
Index int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *EvalReadStateTainted) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
|
return readInstanceFromState(ctx, n.Name, n.Output, func(rs *ResourceState) (*InstanceState, error) {
|
||||||
|
// Get the index. If it is negative, then we get the last one
|
||||||
|
idx := n.Index
|
||||||
|
if idx < 0 {
|
||||||
|
idx = len(rs.Tainted) - 1
|
||||||
|
}
|
||||||
|
if idx >= 0 && idx < len(rs.Tainted) {
|
||||||
|
return rs.Tainted[idx], nil
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("bad tainted index: %d, for resource: %#v", idx, rs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// EvalReadStateDeposed is an EvalNode implementation that reads the
|
||||||
|
// deposed InstanceState for a specific resource out of the state
|
||||||
|
type EvalReadStateDeposed struct {
|
||||||
|
Name string
|
||||||
|
Output **InstanceState
|
||||||
|
// Index indicates which instance in the Deposed list to target, or -1 for
|
||||||
|
// the last item.
|
||||||
|
Index int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *EvalReadStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
|
return readInstanceFromState(ctx, n.Name, n.Output, func(rs *ResourceState) (*InstanceState, error) {
|
||||||
|
// Get the index. If it is negative, then we get the last one
|
||||||
|
idx := n.Index
|
||||||
|
if idx < 0 {
|
||||||
|
idx = len(rs.Deposed) - 1
|
||||||
|
}
|
||||||
|
if idx >= 0 && idx < len(rs.Deposed) {
|
||||||
|
return rs.Deposed[idx], nil
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("bad deposed index: %d, for resource: %#v", idx, rs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does the bulk of the work for the various flavors of ReadState eval nodes.
|
||||||
|
// Each node just provides a reader function to get from the ResourceState to the
|
||||||
|
// InstanceState, and this takes care of all the plumbing.
|
||||||
|
func readInstanceFromState(
|
||||||
|
ctx EvalContext,
|
||||||
|
resourceName string,
|
||||||
|
output **InstanceState,
|
||||||
|
readerFn func(*ResourceState) (*InstanceState, error),
|
||||||
|
) (*InstanceState, error) {
|
||||||
state, lock := ctx.State()
|
state, lock := ctx.State()
|
||||||
|
|
||||||
// Get a read lock so we can access this instance
|
// Get a read lock so we can access this instance
|
||||||
|
@ -28,33 +89,23 @@ func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for the resource state. If we don't have one, then it is okay.
|
// Look for the resource state. If we don't have one, then it is okay.
|
||||||
rs := mod.Resources[n.Name]
|
rs := mod.Resources[resourceName]
|
||||||
if rs == nil {
|
if rs == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var result *InstanceState
|
// Use the delegate function to get the instance state from the resource state
|
||||||
if !n.Tainted {
|
is, err := readerFn(rs)
|
||||||
// Return the primary
|
if err != nil {
|
||||||
result = rs.Primary
|
return nil, err
|
||||||
} else {
|
|
||||||
// Get the index. If it is negative, then we get the last one
|
|
||||||
idx := n.TaintedIndex
|
|
||||||
if idx < 0 {
|
|
||||||
idx = len(rs.Tainted) - 1
|
|
||||||
}
|
|
||||||
if idx >= 0 && idx < len(rs.Tainted) {
|
|
||||||
// Return the proper tainted resource
|
|
||||||
result = rs.Tainted[idx]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the result to the output pointer
|
// Write the result to the output pointer
|
||||||
if n.Output != nil {
|
if output != nil {
|
||||||
*n.Output = result
|
*output = is
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return is, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EvalRequireState is an EvalNode implementation that early exits
|
// EvalRequireState is an EvalNode implementation that early exits
|
||||||
|
@ -98,20 +149,85 @@ func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EvalWriteState is an EvalNode implementation that reads the
|
// EvalWriteState is an EvalNode implementation that writes the
|
||||||
// InstanceState for a specific resource out of the state.
|
// primary InstanceState for a specific resource into the state.
|
||||||
type EvalWriteState struct {
|
type EvalWriteState struct {
|
||||||
Name string
|
Name string
|
||||||
ResourceType string
|
ResourceType string
|
||||||
Dependencies []string
|
Dependencies []string
|
||||||
State **InstanceState
|
State **InstanceState
|
||||||
Tainted *bool
|
|
||||||
TaintedIndex int
|
|
||||||
TaintedClearPrimary bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
|
||||||
func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
|
func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
|
return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Dependencies,
|
||||||
|
func(rs *ResourceState) error {
|
||||||
|
rs.Primary = *n.State
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EvalWriteStateTainted is an EvalNode implementation that writes
|
||||||
|
// an InstanceState out to the Tainted list of a resource in the state.
|
||||||
|
type EvalWriteStateTainted struct {
|
||||||
|
Name string
|
||||||
|
ResourceType string
|
||||||
|
Dependencies []string
|
||||||
|
State **InstanceState
|
||||||
|
// Index indicates which instance in the Tainted list to target, or -1 to append.
|
||||||
|
Index int
|
||||||
|
}
|
||||||
|
|
||||||
|
// EvalWriteStateTainted is an EvalNode implementation that writes the
|
||||||
|
// one of the tainted InstanceStates for a specific resource out of the state.
|
||||||
|
func (n *EvalWriteStateTainted) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
|
return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Dependencies,
|
||||||
|
func(rs *ResourceState) error {
|
||||||
|
if n.Index == -1 {
|
||||||
|
rs.Tainted = append(rs.Tainted, *n.State)
|
||||||
|
} else {
|
||||||
|
rs.Tainted[n.Index] = *n.State
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EvalWriteStateDeposed is an EvalNode implementation that writes
|
||||||
|
// an InstanceState out to the Deposed list of a resource in the state.
|
||||||
|
type EvalWriteStateDeposed struct {
|
||||||
|
Name string
|
||||||
|
ResourceType string
|
||||||
|
Dependencies []string
|
||||||
|
State **InstanceState
|
||||||
|
// Index indicates which instance in the Deposed list to target, or -1 to append.
|
||||||
|
Index int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *EvalWriteStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
|
return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Dependencies,
|
||||||
|
func(rs *ResourceState) error {
|
||||||
|
if n.Index == -1 {
|
||||||
|
rs.Deposed = append(rs.Deposed, *n.State)
|
||||||
|
} else {
|
||||||
|
rs.Deposed[n.Index] = *n.State
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pulls together the common tasks of the EvalWriteState nodes. All the args
|
||||||
|
// are passed directly down from the EvalNode along with a `writer` function
|
||||||
|
// which is yielded the *ResourceState and is responsible for writing an
|
||||||
|
// InstanceState to the proper field in the ResourceState.
|
||||||
|
func writeInstanceToState(
|
||||||
|
ctx EvalContext,
|
||||||
|
resourceName string,
|
||||||
|
resourceType string,
|
||||||
|
dependencies []string,
|
||||||
|
writerFn func(*ResourceState) error,
|
||||||
|
) (*InstanceState, error) {
|
||||||
state, lock := ctx.State()
|
state, lock := ctx.State()
|
||||||
if state == nil {
|
if state == nil {
|
||||||
return nil, fmt.Errorf("cannot write state to nil state")
|
return nil, fmt.Errorf("cannot write state to nil state")
|
||||||
|
@ -128,35 +244,55 @@ func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for the resource state.
|
// Look for the resource state.
|
||||||
rs := mod.Resources[n.Name]
|
rs := mod.Resources[resourceName]
|
||||||
if rs == nil {
|
if rs == nil {
|
||||||
rs = &ResourceState{}
|
rs = &ResourceState{}
|
||||||
rs.init()
|
rs.init()
|
||||||
mod.Resources[n.Name] = rs
|
mod.Resources[resourceName] = rs
|
||||||
}
|
}
|
||||||
rs.Type = n.ResourceType
|
rs.Type = resourceType
|
||||||
rs.Dependencies = n.Dependencies
|
rs.Dependencies = dependencies
|
||||||
|
|
||||||
if n.Tainted != nil && *n.Tainted {
|
if err := writerFn(rs); err != nil {
|
||||||
if n.TaintedIndex != -1 {
|
return nil, err
|
||||||
rs.Tainted[n.TaintedIndex] = *n.State
|
|
||||||
} else {
|
|
||||||
rs.Tainted = append(rs.Tainted, *n.State)
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.TaintedClearPrimary {
|
|
||||||
rs.Primary = nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Set the primary state
|
|
||||||
rs.Primary = *n.State
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EvalClearPrimaryState is an EvalNode implementation that clears the primary
|
||||||
|
// instance from a resource state.
|
||||||
|
type EvalClearPrimaryState struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *EvalClearPrimaryState) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
|
state, lock := ctx.State()
|
||||||
|
|
||||||
|
// Get a read lock so we can access this instance
|
||||||
|
lock.RLock()
|
||||||
|
defer lock.RUnlock()
|
||||||
|
|
||||||
|
// Look for the module state. If we don't have one, then it doesn't matter.
|
||||||
|
mod := state.ModuleByPath(ctx.Path())
|
||||||
|
if mod == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for the resource state. If we don't have one, then it is okay.
|
||||||
|
rs := mod.Resources[n.Name]
|
||||||
|
if rs == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear primary from the resource state
|
||||||
|
rs.Primary = nil
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// EvalDeposeState is an EvalNode implementation that takes the primary
|
// EvalDeposeState is an EvalNode implementation that takes the primary
|
||||||
// out of a state and makes it tainted. This is done at the beggining of
|
// out of a state and makes it Deposed. This is done at the beginning of
|
||||||
// create-before-destroy calls so that the create can create while preserving
|
// create-before-destroy calls so that the create can create while preserving
|
||||||
// the old state of the to-be-destroyed resource.
|
// the old state of the to-be-destroyed resource.
|
||||||
type EvalDeposeState struct {
|
type EvalDeposeState struct {
|
||||||
|
@ -188,8 +324,8 @@ func (n *EvalDeposeState) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Depose to the tainted
|
// Depose
|
||||||
rs.Tainted = append(rs.Tainted, rs.Primary)
|
rs.Deposed = append(rs.Deposed, rs.Primary)
|
||||||
rs.Primary = nil
|
rs.Primary = nil
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -221,15 +357,15 @@ func (n *EvalUndeposeState) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we don't have any tainted, then we don't have anything to do
|
// If we don't have any desposed resource, then we don't have anything to do
|
||||||
if len(rs.Tainted) == 0 {
|
if len(rs.Deposed) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Undepose to the tainted
|
// Undepose
|
||||||
idx := len(rs.Tainted) - 1
|
idx := len(rs.Deposed) - 1
|
||||||
rs.Primary = rs.Tainted[idx]
|
rs.Primary = rs.Deposed[idx]
|
||||||
rs.Tainted[idx] = nil
|
rs.Deposed[idx] = nil
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,3 +66,163 @@ func TestEvalUpdateStateHook(t *testing.T) {
|
||||||
t.Fatalf("bad: %#v", mockHook.PostStateUpdateState)
|
t.Fatalf("bad: %#v", mockHook.PostStateUpdateState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEvalReadState(t *testing.T) {
|
||||||
|
var output *InstanceState
|
||||||
|
cases := map[string]struct {
|
||||||
|
Resources map[string]*ResourceState
|
||||||
|
Node EvalNode
|
||||||
|
ExpectedInstanceId string
|
||||||
|
}{
|
||||||
|
"ReadState gets primary instance state": {
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.bar": &ResourceState{
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "i-abc123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Node: &EvalReadState{
|
||||||
|
Name: "aws_instance.bar",
|
||||||
|
Output: &output,
|
||||||
|
},
|
||||||
|
ExpectedInstanceId: "i-abc123",
|
||||||
|
},
|
||||||
|
"ReadStateTainted gets tainted instance": {
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.bar": &ResourceState{
|
||||||
|
Tainted: []*InstanceState{
|
||||||
|
&InstanceState{ID: "i-abc123"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Node: &EvalReadStateTainted{
|
||||||
|
Name: "aws_instance.bar",
|
||||||
|
Output: &output,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
ExpectedInstanceId: "i-abc123",
|
||||||
|
},
|
||||||
|
"ReadStateDeposed gets deposed instance": {
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.bar": &ResourceState{
|
||||||
|
Deposed: []*InstanceState{
|
||||||
|
&InstanceState{ID: "i-abc123"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Node: &EvalReadStateDeposed{
|
||||||
|
Name: "aws_instance.bar",
|
||||||
|
Output: &output,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
ExpectedInstanceId: "i-abc123",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, c := range cases {
|
||||||
|
ctx := new(MockEvalContext)
|
||||||
|
ctx.StateState = &State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: c.Resources,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctx.StateLock = new(sync.RWMutex)
|
||||||
|
ctx.PathPath = rootModulePath
|
||||||
|
|
||||||
|
result, err := c.Node.Eval(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%s] Got err: %#v", k, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := c.ExpectedInstanceId
|
||||||
|
if !(result != nil && result.(*InstanceState).ID == expected) {
|
||||||
|
t.Fatalf("[%s] Expected return with ID %#v, got: %#v", k, expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(output != nil && output.ID == expected) {
|
||||||
|
t.Fatalf("[%s] Expected output with ID %#v, got: %#v", k, expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
output = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEvalWriteState(t *testing.T) {
|
||||||
|
state := &State{}
|
||||||
|
ctx := new(MockEvalContext)
|
||||||
|
ctx.StateState = state
|
||||||
|
ctx.StateLock = new(sync.RWMutex)
|
||||||
|
ctx.PathPath = rootModulePath
|
||||||
|
|
||||||
|
is := &InstanceState{ID: "i-abc123"}
|
||||||
|
node := &EvalWriteState{
|
||||||
|
Name: "restype.resname",
|
||||||
|
ResourceType: "restype",
|
||||||
|
State: &is,
|
||||||
|
}
|
||||||
|
_, err := node.Eval(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Got err: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStateString(t, state, `
|
||||||
|
restype.resname:
|
||||||
|
ID = i-abc123
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEvalWriteStateTainted(t *testing.T) {
|
||||||
|
state := &State{}
|
||||||
|
ctx := new(MockEvalContext)
|
||||||
|
ctx.StateState = state
|
||||||
|
ctx.StateLock = new(sync.RWMutex)
|
||||||
|
ctx.PathPath = rootModulePath
|
||||||
|
|
||||||
|
is := &InstanceState{ID: "i-abc123"}
|
||||||
|
node := &EvalWriteStateTainted{
|
||||||
|
Name: "restype.resname",
|
||||||
|
ResourceType: "restype",
|
||||||
|
State: &is,
|
||||||
|
Index: -1,
|
||||||
|
}
|
||||||
|
_, err := node.Eval(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Got err: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStateString(t, state, `
|
||||||
|
restype.resname: (1 tainted)
|
||||||
|
ID = <not created>
|
||||||
|
Tainted ID 1 = i-abc123
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEvalWriteStateDeposed(t *testing.T) {
|
||||||
|
state := &State{}
|
||||||
|
ctx := new(MockEvalContext)
|
||||||
|
ctx.StateState = state
|
||||||
|
ctx.StateLock = new(sync.RWMutex)
|
||||||
|
ctx.PathPath = rootModulePath
|
||||||
|
|
||||||
|
is := &InstanceState{ID: "i-abc123"}
|
||||||
|
node := &EvalWriteStateDeposed{
|
||||||
|
Name: "restype.resname",
|
||||||
|
ResourceType: "restype",
|
||||||
|
State: &is,
|
||||||
|
Index: -1,
|
||||||
|
}
|
||||||
|
_, err := node.Eval(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Got err: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStateString(t, state, `
|
||||||
|
restype.resname: (1 deposed)
|
||||||
|
ID = <not created>
|
||||||
|
Deposed ID 1 = i-abc123
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
|
@ -293,24 +293,16 @@ func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error)
|
||||||
View: n.Resource.Id(),
|
View: n.Resource.Id(),
|
||||||
})
|
})
|
||||||
|
|
||||||
if n.Resource.Lifecycle.CreateBeforeDestroy {
|
steps = append(steps, &DeposedTransformer{
|
||||||
// If we're only destroying tainted resources, then we only
|
|
||||||
// want to find tainted resources and destroy them here.
|
|
||||||
steps = append(steps, &TaintedTransformer{
|
|
||||||
State: state,
|
State: state,
|
||||||
View: n.Resource.Id(),
|
View: n.Resource.Id(),
|
||||||
Deposed: n.Resource.Lifecycle.CreateBeforeDestroy,
|
|
||||||
DeposedInclude: true,
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
case DestroyTainted:
|
case DestroyTainted:
|
||||||
// If we're only destroying tainted resources, then we only
|
// If we're only destroying tainted resources, then we only
|
||||||
// want to find tainted resources and destroy them here.
|
// want to find tainted resources and destroy them here.
|
||||||
steps = append(steps, &TaintedTransformer{
|
steps = append(steps, &TaintedTransformer{
|
||||||
State: state,
|
State: state,
|
||||||
View: n.Resource.Id(),
|
View: n.Resource.Id(),
|
||||||
Deposed: n.Resource.Lifecycle.CreateBeforeDestroy,
|
|
||||||
DeposedInclude: false,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -498,7 +498,7 @@ func (m *ModuleState) prune() {
|
||||||
for k, v := range m.Resources {
|
for k, v := range m.Resources {
|
||||||
v.prune()
|
v.prune()
|
||||||
|
|
||||||
if (v.Primary == nil || v.Primary.ID == "") && len(v.Tainted) == 0 {
|
if (v.Primary == nil || v.Primary.ID == "") && len(v.Tainted) == 0 && len(v.Deposed) == 0 {
|
||||||
delete(m.Resources, k)
|
delete(m.Resources, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -548,7 +548,12 @@ func (m *ModuleState) String() string {
|
||||||
taintStr = fmt.Sprintf(" (%d tainted)", len(rs.Tainted))
|
taintStr = fmt.Sprintf(" (%d tainted)", len(rs.Tainted))
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.WriteString(fmt.Sprintf("%s:%s\n", k, taintStr))
|
deposedStr := ""
|
||||||
|
if len(rs.Deposed) > 0 {
|
||||||
|
deposedStr = fmt.Sprintf(" (%d deposed)", len(rs.Deposed))
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString(fmt.Sprintf("%s:%s%s\n", k, taintStr, deposedStr))
|
||||||
buf.WriteString(fmt.Sprintf(" ID = %s\n", id))
|
buf.WriteString(fmt.Sprintf(" ID = %s\n", id))
|
||||||
|
|
||||||
var attributes map[string]string
|
var attributes map[string]string
|
||||||
|
@ -574,6 +579,10 @@ func (m *ModuleState) String() string {
|
||||||
buf.WriteString(fmt.Sprintf(" Tainted ID %d = %s\n", idx+1, t.ID))
|
buf.WriteString(fmt.Sprintf(" Tainted ID %d = %s\n", idx+1, t.ID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for idx, t := range rs.Deposed {
|
||||||
|
buf.WriteString(fmt.Sprintf(" Deposed ID %d = %s\n", idx+1, t.ID))
|
||||||
|
}
|
||||||
|
|
||||||
if len(rs.Dependencies) > 0 {
|
if len(rs.Dependencies) > 0 {
|
||||||
buf.WriteString(fmt.Sprintf("\n Dependencies:\n"))
|
buf.WriteString(fmt.Sprintf("\n Dependencies:\n"))
|
||||||
for _, dep := range rs.Dependencies {
|
for _, dep := range rs.Dependencies {
|
||||||
|
@ -644,6 +653,16 @@ type ResourceState struct {
|
||||||
// However, in pathological cases, it is possible for the number
|
// However, in pathological cases, it is possible for the number
|
||||||
// of instances to accumulate.
|
// of instances to accumulate.
|
||||||
Tainted []*InstanceState `json:"tainted,omitempty"`
|
Tainted []*InstanceState `json:"tainted,omitempty"`
|
||||||
|
|
||||||
|
// Deposed is used in the mechanics of CreateBeforeDestroy: the existing
|
||||||
|
// Primary is Deposed to get it out of the way for the replacement Primary to
|
||||||
|
// be created by Apply. If the replacement Primary creates successfully, the
|
||||||
|
// Deposed instance is cleaned up. If there were problems creating the
|
||||||
|
// replacement, the instance remains in the Deposed list so it can be
|
||||||
|
// destroyed in a future run. Functionally, Deposed instances are very
|
||||||
|
// similar to Tainted instances in that Terraform is only tracking them in
|
||||||
|
// order to remember to destroy them.
|
||||||
|
Deposed []*InstanceState `json:"deposed,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equal tests whether two ResourceStates are equal.
|
// Equal tests whether two ResourceStates are equal.
|
||||||
|
@ -744,6 +763,12 @@ func (r *ResourceState) deepcopy() *ResourceState {
|
||||||
n.Tainted = append(n.Tainted, inst.deepcopy())
|
n.Tainted = append(n.Tainted, inst.deepcopy())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if r.Deposed != nil {
|
||||||
|
n.Deposed = make([]*InstanceState, 0, len(r.Deposed))
|
||||||
|
for _, inst := range r.Deposed {
|
||||||
|
n.Deposed = append(n.Deposed, inst.deepcopy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
@ -762,6 +787,19 @@ func (r *ResourceState) prune() {
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Tainted = r.Tainted[:n]
|
r.Tainted = r.Tainted[:n]
|
||||||
|
|
||||||
|
n = len(r.Deposed)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
inst := r.Deposed[i]
|
||||||
|
if inst == nil || inst.ID == "" {
|
||||||
|
copy(r.Deposed[i:], r.Deposed[i+1:])
|
||||||
|
r.Deposed[n-1] = nil
|
||||||
|
n--
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Deposed = r.Deposed[:n]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ResourceState) sort() {
|
func (r *ResourceState) sort() {
|
||||||
|
@ -794,6 +832,11 @@ type InstanceState struct {
|
||||||
// that is necessary for the Terraform run to complete, but is not
|
// that is necessary for the Terraform run to complete, but is not
|
||||||
// persisted to a state file.
|
// persisted to a state file.
|
||||||
Ephemeral EphemeralState `json:"-"`
|
Ephemeral EphemeralState `json:"-"`
|
||||||
|
|
||||||
|
// Meta is a simple K/V map that is persisted to the State but otherwise
|
||||||
|
// ignored by Terraform core. It's meant to be used for accounting by
|
||||||
|
// external client code.
|
||||||
|
Meta map[string]string `json:"meta,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *InstanceState) init() {
|
func (i *InstanceState) init() {
|
||||||
|
|
|
@ -443,9 +443,9 @@ aws_instance.bar:
|
||||||
`
|
`
|
||||||
|
|
||||||
const testTerraformApplyErrorDestroyCreateBeforeDestroyStr = `
|
const testTerraformApplyErrorDestroyCreateBeforeDestroyStr = `
|
||||||
aws_instance.bar: (1 tainted)
|
aws_instance.bar: (1 deposed)
|
||||||
ID = foo
|
ID = foo
|
||||||
Tainted ID 1 = bar
|
Deposed ID 1 = bar
|
||||||
`
|
`
|
||||||
|
|
||||||
const testTerraformApplyErrorPartialStr = `
|
const testTerraformApplyErrorPartialStr = `
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -285,7 +285,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
||||||
diffApply.Destroy = false
|
diffApply.Destroy = false
|
||||||
return true, nil
|
return true, nil
|
||||||
},
|
},
|
||||||
Node: EvalNoop{},
|
Then: EvalNoop{},
|
||||||
},
|
},
|
||||||
|
|
||||||
&EvalIf{
|
&EvalIf{
|
||||||
|
@ -301,7 +301,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
||||||
|
|
||||||
return createBeforeDestroyEnabled, nil
|
return createBeforeDestroyEnabled, nil
|
||||||
},
|
},
|
||||||
Node: &EvalDeposeState{
|
Then: &EvalDeposeState{
|
||||||
Name: n.stateId(),
|
Name: n.stateId(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -382,7 +382,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
||||||
failure := tainted || err != nil
|
failure := tainted || err != nil
|
||||||
return createBeforeDestroyEnabled && failure, nil
|
return createBeforeDestroyEnabled && failure, nil
|
||||||
},
|
},
|
||||||
Node: &EvalUndeposeState{
|
Then: &EvalUndeposeState{
|
||||||
Name: n.stateId(),
|
Name: n.stateId(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -395,14 +395,35 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
||||||
Diff: nil,
|
Diff: nil,
|
||||||
},
|
},
|
||||||
|
|
||||||
&EvalWriteState{
|
&EvalIf{
|
||||||
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
|
return tainted, nil
|
||||||
|
},
|
||||||
|
Then: &EvalSequence{
|
||||||
|
Nodes: []EvalNode{
|
||||||
|
&EvalWriteStateTainted{
|
||||||
Name: n.stateId(),
|
Name: n.stateId(),
|
||||||
ResourceType: n.Resource.Type,
|
ResourceType: n.Resource.Type,
|
||||||
Dependencies: n.DependentOn(),
|
Dependencies: n.DependentOn(),
|
||||||
State: &state,
|
State: &state,
|
||||||
Tainted: &tainted,
|
Index: -1,
|
||||||
TaintedIndex: -1,
|
},
|
||||||
TaintedClearPrimary: !n.Resource.Lifecycle.CreateBeforeDestroy,
|
&EvalIf{
|
||||||
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
|
return !n.Resource.Lifecycle.CreateBeforeDestroy, nil
|
||||||
|
},
|
||||||
|
Then: &EvalClearPrimaryState{
|
||||||
|
Name: n.stateId(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Else: &EvalWriteState{
|
||||||
|
Name: n.stateId(),
|
||||||
|
ResourceType: n.Resource.Type,
|
||||||
|
Dependencies: n.DependentOn(),
|
||||||
|
State: &state,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&EvalApplyPost{
|
&EvalApplyPost{
|
||||||
Info: info,
|
Info: info,
|
||||||
|
@ -480,18 +501,26 @@ func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
|
||||||
|
|
||||||
return true, EvalEarlyExitError{}
|
return true, EvalEarlyExitError{}
|
||||||
},
|
},
|
||||||
Node: EvalNoop{},
|
Then: EvalNoop{},
|
||||||
},
|
},
|
||||||
|
|
||||||
&EvalGetProvider{
|
&EvalGetProvider{
|
||||||
Name: n.ProvidedBy()[0],
|
Name: n.ProvidedBy()[0],
|
||||||
Output: &provider,
|
Output: &provider,
|
||||||
},
|
},
|
||||||
&EvalReadState{
|
&EvalIf{
|
||||||
|
If: func(ctx EvalContext) (bool, error) {
|
||||||
|
return n.Resource.Lifecycle.CreateBeforeDestroy, nil
|
||||||
|
},
|
||||||
|
Then: &EvalReadStateTainted{
|
||||||
Name: n.stateId(),
|
Name: n.stateId(),
|
||||||
Output: &state,
|
Output: &state,
|
||||||
Tainted: n.Resource.Lifecycle.CreateBeforeDestroy,
|
Index: -1,
|
||||||
TaintedIndex: -1,
|
},
|
||||||
|
Else: &EvalReadState{
|
||||||
|
Name: n.stateId(),
|
||||||
|
Output: &state,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&EvalRequireState{
|
&EvalRequireState{
|
||||||
State: &state,
|
State: &state,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TraintedTransformer is a GraphTransformer that adds tainted resources
|
// TaintedTransformer is a GraphTransformer that adds tainted resources
|
||||||
// to the graph.
|
// to the graph.
|
||||||
type TaintedTransformer struct {
|
type TaintedTransformer struct {
|
||||||
// State is the global state. We'll automatically find the correct
|
// State is the global state. We'll automatically find the correct
|
||||||
|
@ -14,12 +14,6 @@ type TaintedTransformer struct {
|
||||||
// View, if non-empty, is the ModuleState.View used around the state
|
// View, if non-empty, is the ModuleState.View used around the state
|
||||||
// to find tainted resources.
|
// to find tainted resources.
|
||||||
View string
|
View string
|
||||||
|
|
||||||
// Deposed, if set to true, assumes that the last tainted index
|
|
||||||
// represents a "deposed" resource, or a resource that was previously
|
|
||||||
// a primary but is now tainted since it is demoted.
|
|
||||||
Deposed bool
|
|
||||||
DeposedInclude bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TaintedTransformer) Transform(g *Graph) error {
|
func (t *TaintedTransformer) Transform(g *Graph) error {
|
||||||
|
@ -43,17 +37,6 @@ func (t *TaintedTransformer) Transform(g *Graph) error {
|
||||||
}
|
}
|
||||||
tainted := rs.Tainted
|
tainted := rs.Tainted
|
||||||
|
|
||||||
// If we expect a deposed resource, then shuffle a bit
|
|
||||||
if t.Deposed {
|
|
||||||
if t.DeposedInclude {
|
|
||||||
// Only include the deposed resource
|
|
||||||
tainted = rs.Tainted[len(rs.Tainted)-1:]
|
|
||||||
} else {
|
|
||||||
// Exclude the deposed resource
|
|
||||||
tainted = rs.Tainted[:len(rs.Tainted)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, _ := range tainted {
|
for i, _ := range tainted {
|
||||||
// Add the graph node and make the connection from any untainted
|
// Add the graph node and make the connection from any untainted
|
||||||
// resources with this name to the tainted resource, so that
|
// resources with this name to the tainted resource, so that
|
||||||
|
@ -88,7 +71,6 @@ func (n *graphNodeTaintedResource) ProvidedBy() []string {
|
||||||
func (n *graphNodeTaintedResource) EvalTree() EvalNode {
|
func (n *graphNodeTaintedResource) EvalTree() EvalNode {
|
||||||
var provider ResourceProvider
|
var provider ResourceProvider
|
||||||
var state *InstanceState
|
var state *InstanceState
|
||||||
tainted := true
|
|
||||||
|
|
||||||
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
|
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
|
||||||
|
|
||||||
|
@ -105,10 +87,9 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
|
||||||
Name: n.ProvidedBy()[0],
|
Name: n.ProvidedBy()[0],
|
||||||
Output: &provider,
|
Output: &provider,
|
||||||
},
|
},
|
||||||
&EvalReadState{
|
&EvalReadStateTainted{
|
||||||
Name: n.ResourceName,
|
Name: n.ResourceName,
|
||||||
Tainted: true,
|
Index: n.Index,
|
||||||
TaintedIndex: n.Index,
|
|
||||||
Output: &state,
|
Output: &state,
|
||||||
},
|
},
|
||||||
&EvalRefresh{
|
&EvalRefresh{
|
||||||
|
@ -117,12 +98,11 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
|
||||||
State: &state,
|
State: &state,
|
||||||
Output: &state,
|
Output: &state,
|
||||||
},
|
},
|
||||||
&EvalWriteState{
|
&EvalWriteStateTainted{
|
||||||
Name: n.ResourceName,
|
Name: n.ResourceName,
|
||||||
ResourceType: n.ResourceType,
|
ResourceType: n.ResourceType,
|
||||||
State: &state,
|
State: &state,
|
||||||
Tainted: &tainted,
|
Index: n.Index,
|
||||||
TaintedIndex: n.Index,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -138,10 +118,9 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
|
||||||
Name: n.ProvidedBy()[0],
|
Name: n.ProvidedBy()[0],
|
||||||
Output: &provider,
|
Output: &provider,
|
||||||
},
|
},
|
||||||
&EvalReadState{
|
&EvalReadStateTainted{
|
||||||
Name: n.ResourceName,
|
Name: n.ResourceName,
|
||||||
Tainted: true,
|
Index: n.Index,
|
||||||
TaintedIndex: n.Index,
|
|
||||||
Output: &state,
|
Output: &state,
|
||||||
},
|
},
|
||||||
&EvalDiffDestroy{
|
&EvalDiffDestroy{
|
||||||
|
@ -156,12 +135,11 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
|
||||||
Provider: &provider,
|
Provider: &provider,
|
||||||
Output: &state,
|
Output: &state,
|
||||||
},
|
},
|
||||||
&EvalWriteState{
|
&EvalWriteStateTainted{
|
||||||
Name: n.ResourceName,
|
Name: n.ResourceName,
|
||||||
ResourceType: n.ResourceType,
|
ResourceType: n.ResourceType,
|
||||||
State: &state,
|
State: &state,
|
||||||
Tainted: &tainted,
|
Index: n.Index,
|
||||||
TaintedIndex: n.Index,
|
|
||||||
},
|
},
|
||||||
&EvalUpdateStateHook{},
|
&EvalUpdateStateHook{},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
gem 'middleman-hashicorp', github: 'hashicorp/middleman-hashicorp'
|
gem 'middleman-hashicorp', git: 'https://github.com/hashicorp/middleman-hashicorp'
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
GIT
|
GIT
|
||||||
remote: git://github.com/hashicorp/middleman-hashicorp.git
|
remote: https://github.com/hashicorp/middleman-hashicorp
|
||||||
revision: 30c15f93fb501041cff97c490b60ddc96c8314c9
|
revision: 783fe9517dd02badb85e5ddfeda4d8e35bbd05a8
|
||||||
specs:
|
specs:
|
||||||
middleman-hashicorp (0.1.0)
|
middleman-hashicorp (0.1.0)
|
||||||
bootstrap-sass (~> 3.2)
|
bootstrap-sass (~> 3.3)
|
||||||
builder (~> 3.2)
|
builder (~> 3.2)
|
||||||
less (~> 2.6)
|
less (~> 2.6)
|
||||||
middleman (~> 3.3)
|
middleman (~> 3.3)
|
||||||
middleman-livereload (~> 3.3)
|
middleman-livereload (~> 3.4)
|
||||||
middleman-minify-html (~> 3.4)
|
middleman-minify-html (~> 3.4)
|
||||||
middleman-syntax (~> 2.0)
|
middleman-syntax (~> 2.0)
|
||||||
rack-contrib (~> 1.1)
|
rack-contrib (~> 1.2)
|
||||||
rack-rewrite (~> 1.5)
|
rack-rewrite (~> 1.5)
|
||||||
rack-ssl-enforcer (~> 0.2)
|
rack-ssl-enforcer (~> 0.2)
|
||||||
redcarpet (~> 3.1)
|
redcarpet (~> 3.2)
|
||||||
therubyracer (~> 0.12)
|
therubyracer (~> 0.12)
|
||||||
thin (~> 1.6)
|
thin (~> 1.6)
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ GEM
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
autoprefixer-rails (5.0.0.2)
|
autoprefixer-rails (5.1.7)
|
||||||
execjs
|
execjs
|
||||||
json
|
json
|
||||||
bootstrap-sass (3.3.3)
|
bootstrap-sass (3.3.3)
|
||||||
|
@ -35,11 +35,11 @@ GEM
|
||||||
builder (3.2.2)
|
builder (3.2.2)
|
||||||
celluloid (0.16.0)
|
celluloid (0.16.0)
|
||||||
timers (~> 4.0.0)
|
timers (~> 4.0.0)
|
||||||
chunky_png (1.3.3)
|
chunky_png (1.3.4)
|
||||||
coffee-script (2.3.0)
|
coffee-script (2.3.0)
|
||||||
coffee-script-source
|
coffee-script-source
|
||||||
execjs
|
execjs
|
||||||
coffee-script-source (1.8.0)
|
coffee-script-source (1.9.1)
|
||||||
commonjs (0.2.7)
|
commonjs (0.2.7)
|
||||||
compass (1.0.3)
|
compass (1.0.3)
|
||||||
chunky_png (~> 1.2)
|
chunky_png (~> 1.2)
|
||||||
|
@ -58,8 +58,8 @@ GEM
|
||||||
eventmachine (>= 0.12.9)
|
eventmachine (>= 0.12.9)
|
||||||
http_parser.rb (~> 0.6.0)
|
http_parser.rb (~> 0.6.0)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
eventmachine (1.0.4)
|
eventmachine (1.0.7)
|
||||||
execjs (2.2.2)
|
execjs (2.4.0)
|
||||||
ffi (1.9.6)
|
ffi (1.9.6)
|
||||||
haml (4.0.6)
|
haml (4.0.6)
|
||||||
tilt
|
tilt
|
||||||
|
@ -69,9 +69,9 @@ GEM
|
||||||
uber (~> 0.0.4)
|
uber (~> 0.0.4)
|
||||||
htmlcompressor (0.1.2)
|
htmlcompressor (0.1.2)
|
||||||
http_parser.rb (0.6.0)
|
http_parser.rb (0.6.0)
|
||||||
i18n (0.6.11)
|
i18n (0.7.0)
|
||||||
json (1.8.2)
|
json (1.8.2)
|
||||||
kramdown (1.5.0)
|
kramdown (1.6.0)
|
||||||
less (2.6.0)
|
less (2.6.0)
|
||||||
commonjs (~> 0.2.7)
|
commonjs (~> 0.2.7)
|
||||||
libv8 (3.16.14.7)
|
libv8 (3.16.14.7)
|
||||||
|
@ -79,23 +79,23 @@ GEM
|
||||||
celluloid (>= 0.15.2)
|
celluloid (>= 0.15.2)
|
||||||
rb-fsevent (>= 0.9.3)
|
rb-fsevent (>= 0.9.3)
|
||||||
rb-inotify (>= 0.9)
|
rb-inotify (>= 0.9)
|
||||||
middleman (3.3.7)
|
middleman (3.3.10)
|
||||||
coffee-script (~> 2.2)
|
coffee-script (~> 2.2)
|
||||||
compass (>= 1.0.0, < 2.0.0)
|
compass (>= 1.0.0, < 2.0.0)
|
||||||
compass-import-once (= 1.0.5)
|
compass-import-once (= 1.0.5)
|
||||||
execjs (~> 2.0)
|
execjs (~> 2.0)
|
||||||
haml (>= 4.0.5)
|
haml (>= 4.0.5)
|
||||||
kramdown (~> 1.2)
|
kramdown (~> 1.2)
|
||||||
middleman-core (= 3.3.7)
|
middleman-core (= 3.3.10)
|
||||||
middleman-sprockets (>= 3.1.2)
|
middleman-sprockets (>= 3.1.2)
|
||||||
sass (>= 3.4.0, < 4.0)
|
sass (>= 3.4.0, < 4.0)
|
||||||
uglifier (~> 2.5)
|
uglifier (~> 2.5)
|
||||||
middleman-core (3.3.7)
|
middleman-core (3.3.10)
|
||||||
activesupport (~> 4.1.0)
|
activesupport (~> 4.1.0)
|
||||||
bundler (~> 1.1)
|
bundler (~> 1.1)
|
||||||
erubis
|
erubis
|
||||||
hooks (~> 0.3)
|
hooks (~> 0.3)
|
||||||
i18n (~> 0.6.9)
|
i18n (~> 0.7.0)
|
||||||
listen (>= 2.7.9, < 3.0)
|
listen (>= 2.7.9, < 3.0)
|
||||||
padrino-helpers (~> 0.12.3)
|
padrino-helpers (~> 0.12.3)
|
||||||
rack (>= 1.4.5, < 2.0)
|
rack (>= 1.4.5, < 2.0)
|
||||||
|
@ -109,7 +109,7 @@ GEM
|
||||||
middleman-minify-html (3.4.0)
|
middleman-minify-html (3.4.0)
|
||||||
htmlcompressor (~> 0.1.0)
|
htmlcompressor (~> 0.1.0)
|
||||||
middleman-core (>= 3.2)
|
middleman-core (>= 3.2)
|
||||||
middleman-sprockets (3.4.1)
|
middleman-sprockets (3.4.2)
|
||||||
middleman-core (>= 3.3)
|
middleman-core (>= 3.3)
|
||||||
sprockets (~> 2.12.1)
|
sprockets (~> 2.12.1)
|
||||||
sprockets-helpers (~> 1.1.0)
|
sprockets-helpers (~> 1.1.0)
|
||||||
|
@ -118,12 +118,12 @@ GEM
|
||||||
middleman-core (~> 3.2)
|
middleman-core (~> 3.2)
|
||||||
rouge (~> 1.0)
|
rouge (~> 1.0)
|
||||||
minitest (5.5.1)
|
minitest (5.5.1)
|
||||||
multi_json (1.10.1)
|
multi_json (1.11.0)
|
||||||
padrino-helpers (0.12.4)
|
padrino-helpers (0.12.5)
|
||||||
i18n (~> 0.6, >= 0.6.7)
|
i18n (~> 0.6, >= 0.6.7)
|
||||||
padrino-support (= 0.12.4)
|
padrino-support (= 0.12.5)
|
||||||
tilt (~> 1.4.1)
|
tilt (~> 1.4.1)
|
||||||
padrino-support (0.12.4)
|
padrino-support (0.12.5)
|
||||||
activesupport (>= 3.1)
|
activesupport (>= 3.1)
|
||||||
rack (1.6.0)
|
rack (1.6.0)
|
||||||
rack-contrib (1.2.0)
|
rack-contrib (1.2.0)
|
||||||
|
@ -139,8 +139,8 @@ GEM
|
||||||
ffi (>= 0.5.0)
|
ffi (>= 0.5.0)
|
||||||
redcarpet (3.2.2)
|
redcarpet (3.2.2)
|
||||||
ref (1.0.5)
|
ref (1.0.5)
|
||||||
rouge (1.7.7)
|
rouge (1.8.0)
|
||||||
sass (3.4.10)
|
sass (3.4.13)
|
||||||
sprockets (2.12.3)
|
sprockets (2.12.3)
|
||||||
hike (~> 1.2)
|
hike (~> 1.2)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
|
@ -166,7 +166,7 @@ GEM
|
||||||
tzinfo (1.2.2)
|
tzinfo (1.2.2)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
uber (0.0.13)
|
uber (0.0.13)
|
||||||
uglifier (2.7.0)
|
uglifier (2.7.1)
|
||||||
execjs (>= 0.3.0)
|
execjs (>= 0.3.0)
|
||||||
json (>= 1.8.0)
|
json (>= 1.8.0)
|
||||||
|
|
||||||
|
|
|
@ -31,11 +31,10 @@ Available commands are:
|
||||||
init Initializes Terraform configuration from a module
|
init Initializes Terraform configuration from a module
|
||||||
output Read an output from a state file
|
output Read an output from a state file
|
||||||
plan Generate and show an execution plan
|
plan Generate and show an execution plan
|
||||||
pull Refreshes the local state copy from the remote server
|
|
||||||
push Uploads the the local state to the remote server
|
|
||||||
refresh Update local state file against real resources
|
refresh Update local state file against real resources
|
||||||
remote Configures remote state management
|
remote Configure remote state storage
|
||||||
show Inspect Terraform state or plan
|
show Inspect Terraform state or plan
|
||||||
|
taint Manually mark a resource for recreation
|
||||||
version Prints the Terraform version
|
version Prints the Terraform version
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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.
|
|
||||||
|
|
|
@ -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.
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue