Merge branch 'master' of github.com:hashicorp/terraform
This commit is contained in:
commit
a5d4ec65b5
|
@ -373,7 +373,7 @@ to a single resource. Most tests follow a similar structure.
|
||||||
|
|
||||||
1. Pre-flight checks are made to ensure that sufficient provider configuration
|
1. Pre-flight checks are made to ensure that sufficient provider configuration
|
||||||
is available to be able to proceed - for example in an acceptance test
|
is available to be able to proceed - for example in an acceptance test
|
||||||
targetting AWS, `AWS_ACCESS_KEY_ID` and `AWS_SECRET_KEY` must be set prior
|
targetting AWS, `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` must be set prior
|
||||||
to running acceptance tests. This is common to all tests exercising a single
|
to running acceptance tests. This is common to all tests exercising a single
|
||||||
provider.
|
provider.
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
## 0.7.1 (Unreleased)
|
## 0.7.1 (Unreleased)
|
||||||
|
|
||||||
FEATURES:
|
FEATURES:
|
||||||
|
* **New Resource:** `aws_vpn_gateway_attachment` [GH-7870]
|
||||||
|
|
||||||
IMPROVEMENTS:
|
IMPROVEMENTS:
|
||||||
* provider/vsphere: Improved SCSI controller handling in `vsphere_virtual_machine` [GH-7908]
|
* provider/vsphere: Improved SCSI controller handling in `vsphere_virtual_machine` [GH-7908]
|
||||||
|
* provider/aws: Introduce `aws_elasticsearch_domain` `elasticsearch_version` field (to specify ES version) [GH-7860]
|
||||||
|
|
||||||
BUG FIXES:
|
BUG FIXES:
|
||||||
* provider/aws: guard against missing image_digest in `aws_ecs_task_definition` [GH-7966]
|
* provider/aws: guard against missing image_digest in `aws_ecs_task_definition` [GH-7966]
|
||||||
|
* provider/aws: `aws_cloudformation_stack` now respects `timeout_in_minutes` field when waiting for CF API to finish an update operation [GH-7997]
|
||||||
* provider/aws: Add state filter to `aws_availability_zone`s data source [GH-7965]
|
* provider/aws: Add state filter to `aws_availability_zone`s data source [GH-7965]
|
||||||
* provider/aws: Handle lack of snapshot ID for a volume in `ami_copy` [GH-7995]
|
* provider/aws: Handle lack of snapshot ID for a volume in `ami_copy` [GH-7995]
|
||||||
* provider/aws: Retry association of IAM Role & instance profile [GH-7938]
|
* provider/aws: Retry association of IAM Role & instance profile [GH-7938]
|
||||||
|
* provider/aws: Fix `aws_s3_bucket` resource `redirect_all_requests_to` action [GH-7883]
|
||||||
* provider/google: Use resource specific project when making queries/changes [GH-7029]
|
* provider/google: Use resource specific project when making queries/changes [GH-7029]
|
||||||
|
|
||||||
## 0.7.0 (August 2, 2016)
|
## 0.7.0 (August 2, 2016)
|
||||||
|
|
|
@ -282,6 +282,7 @@ func Provider() terraform.ResourceProvider {
|
||||||
"aws_vpn_connection": resourceAwsVpnConnection(),
|
"aws_vpn_connection": resourceAwsVpnConnection(),
|
||||||
"aws_vpn_connection_route": resourceAwsVpnConnectionRoute(),
|
"aws_vpn_connection_route": resourceAwsVpnConnectionRoute(),
|
||||||
"aws_vpn_gateway": resourceAwsVpnGateway(),
|
"aws_vpn_gateway": resourceAwsVpnGateway(),
|
||||||
|
"aws_vpn_gateway_attachment": resourceAwsVpnGatewayAttachment(),
|
||||||
},
|
},
|
||||||
ConfigureFunc: providerConfigure,
|
ConfigureFunc: providerConfigure,
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,6 +268,7 @@ func resourceAwsCloudFormationStackRead(d *schema.ResourceData, meta interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsCloudFormationStackUpdate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsCloudFormationStackUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
retryTimeout := int64(30)
|
||||||
conn := meta.(*AWSClient).cfconn
|
conn := meta.(*AWSClient).cfconn
|
||||||
|
|
||||||
input := &cloudformation.UpdateStackInput{
|
input := &cloudformation.UpdateStackInput{
|
||||||
|
@ -314,6 +315,13 @@ func resourceAwsCloudFormationStackUpdate(d *schema.ResourceData, meta interface
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v, ok := d.GetOk("timeout_in_minutes"); ok {
|
||||||
|
m := int64(v.(int))
|
||||||
|
if m > retryTimeout {
|
||||||
|
retryTimeout = m + 5
|
||||||
|
log.Printf("[DEBUG] CloudFormation timeout: %d", retryTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
wait := resource.StateChangeConf{
|
wait := resource.StateChangeConf{
|
||||||
Pending: []string{
|
Pending: []string{
|
||||||
"UPDATE_COMPLETE_CLEANUP_IN_PROGRESS",
|
"UPDATE_COMPLETE_CLEANUP_IN_PROGRESS",
|
||||||
|
@ -323,7 +331,7 @@ func resourceAwsCloudFormationStackUpdate(d *schema.ResourceData, meta interface
|
||||||
"UPDATE_ROLLBACK_COMPLETE",
|
"UPDATE_ROLLBACK_COMPLETE",
|
||||||
},
|
},
|
||||||
Target: []string{"UPDATE_COMPLETE"},
|
Target: []string{"UPDATE_COMPLETE"},
|
||||||
Timeout: 15 * time.Minute,
|
Timeout: time.Duration(retryTimeout) * time.Minute,
|
||||||
MinTimeout: 5 * time.Second,
|
MinTimeout: 5 * time.Second,
|
||||||
Refresh: func() (interface{}, string, error) {
|
Refresh: func() (interface{}, string, error) {
|
||||||
resp, err := conn.DescribeStacks(&cloudformation.DescribeStacksInput{
|
resp, err := conn.DescribeStacks(&cloudformation.DescribeStacksInput{
|
||||||
|
|
|
@ -129,6 +129,13 @@ func resourceAwsElasticSearchDomain() *schema.Resource {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"elasticsearch_version": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "1.5",
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
"tags": tagsSchema(),
|
"tags": tagsSchema(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -138,7 +145,8 @@ func resourceAwsElasticSearchDomainCreate(d *schema.ResourceData, meta interface
|
||||||
conn := meta.(*AWSClient).esconn
|
conn := meta.(*AWSClient).esconn
|
||||||
|
|
||||||
input := elasticsearch.CreateElasticsearchDomainInput{
|
input := elasticsearch.CreateElasticsearchDomainInput{
|
||||||
DomainName: aws.String(d.Get("domain_name").(string)),
|
DomainName: aws.String(d.Get("domain_name").(string)),
|
||||||
|
ElasticsearchVersion: aws.String(d.Get("elasticsearch_version").(string)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := d.GetOk("access_policies"); ok {
|
if v, ok := d.GetOk("access_policies"); ok {
|
||||||
|
@ -262,8 +270,9 @@ func resourceAwsElasticSearchDomainRead(d *schema.ResourceData, meta interface{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.Set("domain_id", *ds.DomainId)
|
d.Set("domain_id", ds.DomainId)
|
||||||
d.Set("domain_name", *ds.DomainName)
|
d.Set("domain_name", ds.DomainName)
|
||||||
|
d.Set("elasticsearch_version", ds.ElasticsearchVersion)
|
||||||
if ds.Endpoint != nil {
|
if ds.Endpoint != nil {
|
||||||
d.Set("endpoint", *ds.Endpoint)
|
d.Set("endpoint", *ds.Endpoint)
|
||||||
}
|
}
|
||||||
|
@ -282,7 +291,7 @@ func resourceAwsElasticSearchDomainRead(d *schema.ResourceData, meta interface{}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Set("arn", *ds.ARN)
|
d.Set("arn", ds.ARN)
|
||||||
|
|
||||||
listOut, err := conn.ListTags(&elasticsearch.ListTagsInput{
|
listOut, err := conn.ListTags(&elasticsearch.ListTagsInput{
|
||||||
ARN: ds.ARN,
|
ARN: ds.ARN,
|
||||||
|
|
|
@ -7,12 +7,14 @@ import (
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice"
|
elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice"
|
||||||
|
"github.com/hashicorp/terraform/helper/acctest"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAWSElasticSearchDomain_basic(t *testing.T) {
|
func TestAccAWSElasticSearchDomain_basic(t *testing.T) {
|
||||||
var domain elasticsearch.ElasticsearchDomainStatus
|
var domain elasticsearch.ElasticsearchDomainStatus
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -20,9 +22,32 @@ func TestAccAWSElasticSearchDomain_basic(t *testing.T) {
|
||||||
CheckDestroy: testAccCheckESDomainDestroy,
|
CheckDestroy: testAccCheckESDomainDestroy,
|
||||||
Steps: []resource.TestStep{
|
Steps: []resource.TestStep{
|
||||||
resource.TestStep{
|
resource.TestStep{
|
||||||
Config: testAccESDomainConfig,
|
Config: testAccESDomainConfig(ri),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
|
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"aws_elasticsearch_domain.example", "elasticsearch_version", "1.5"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccAWSElasticSearchDomain_v23(t *testing.T) {
|
||||||
|
var domain elasticsearch.ElasticsearchDomainStatus
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckESDomainDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccESDomainConfigV23(ri),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"aws_elasticsearch_domain.example", "elasticsearch_version", "2.3"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -31,6 +56,7 @@ func TestAccAWSElasticSearchDomain_basic(t *testing.T) {
|
||||||
|
|
||||||
func TestAccAWSElasticSearchDomain_complex(t *testing.T) {
|
func TestAccAWSElasticSearchDomain_complex(t *testing.T) {
|
||||||
var domain elasticsearch.ElasticsearchDomainStatus
|
var domain elasticsearch.ElasticsearchDomainStatus
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -38,7 +64,7 @@ func TestAccAWSElasticSearchDomain_complex(t *testing.T) {
|
||||||
CheckDestroy: testAccCheckESDomainDestroy,
|
CheckDestroy: testAccCheckESDomainDestroy,
|
||||||
Steps: []resource.TestStep{
|
Steps: []resource.TestStep{
|
||||||
resource.TestStep{
|
resource.TestStep{
|
||||||
Config: testAccESDomainConfig_complex,
|
Config: testAccESDomainConfig_complex(ri),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
|
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
|
||||||
),
|
),
|
||||||
|
@ -50,6 +76,7 @@ func TestAccAWSElasticSearchDomain_complex(t *testing.T) {
|
||||||
func TestAccAWSElasticSearch_tags(t *testing.T) {
|
func TestAccAWSElasticSearch_tags(t *testing.T) {
|
||||||
var domain elasticsearch.ElasticsearchDomainStatus
|
var domain elasticsearch.ElasticsearchDomainStatus
|
||||||
var td elasticsearch.ListTagsOutput
|
var td elasticsearch.ListTagsOutput
|
||||||
|
ri := acctest.RandInt()
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -57,14 +84,14 @@ func TestAccAWSElasticSearch_tags(t *testing.T) {
|
||||||
CheckDestroy: testAccCheckAWSELBDestroy,
|
CheckDestroy: testAccCheckAWSELBDestroy,
|
||||||
Steps: []resource.TestStep{
|
Steps: []resource.TestStep{
|
||||||
resource.TestStep{
|
resource.TestStep{
|
||||||
Config: testAccESDomainConfig,
|
Config: testAccESDomainConfig(ri),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
|
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
resource.TestStep{
|
resource.TestStep{
|
||||||
Config: testAccESDomainConfig_TagUpdate,
|
Config: testAccESDomainConfig_TagUpdate(ri),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
|
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
|
||||||
testAccLoadESTags(&domain, &td),
|
testAccLoadESTags(&domain, &td),
|
||||||
|
@ -144,26 +171,31 @@ func testAccCheckESDomainDestroy(s *terraform.State) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const testAccESDomainConfig = `
|
func testAccESDomainConfig(randInt int) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
resource "aws_elasticsearch_domain" "example" {
|
resource "aws_elasticsearch_domain" "example" {
|
||||||
domain_name = "tf-test-1"
|
domain_name = "tf-test-%d"
|
||||||
|
}
|
||||||
|
`, randInt)
|
||||||
}
|
}
|
||||||
`
|
|
||||||
|
|
||||||
const testAccESDomainConfig_TagUpdate = `
|
func testAccESDomainConfig_TagUpdate(randInt int) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
resource "aws_elasticsearch_domain" "example" {
|
resource "aws_elasticsearch_domain" "example" {
|
||||||
domain_name = "tf-test-1"
|
domain_name = "tf-test-%d"
|
||||||
|
|
||||||
tags {
|
tags {
|
||||||
foo = "bar"
|
foo = "bar"
|
||||||
new = "type"
|
new = "type"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`, randInt)
|
||||||
|
}
|
||||||
|
|
||||||
const testAccESDomainConfig_complex = `
|
func testAccESDomainConfig_complex(randInt int) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
resource "aws_elasticsearch_domain" "example" {
|
resource "aws_elasticsearch_domain" "example" {
|
||||||
domain_name = "tf-test-2"
|
domain_name = "tf-test-%d"
|
||||||
|
|
||||||
advanced_options {
|
advanced_options {
|
||||||
"indices.fielddata.cache.size" = 80
|
"indices.fielddata.cache.size" = 80
|
||||||
|
@ -186,4 +218,14 @@ resource "aws_elasticsearch_domain" "example" {
|
||||||
bar = "complex"
|
bar = "complex"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`, randInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccESDomainConfigV23(randInt int) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "aws_elasticsearch_domain" "example" {
|
||||||
|
domain_name = "tf-test-%d"
|
||||||
|
elasticsearch_version = "2.3"
|
||||||
|
}
|
||||||
|
`, randInt)
|
||||||
|
}
|
||||||
|
|
|
@ -495,8 +495,20 @@ func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
if v.Protocol == nil {
|
if v.Protocol == nil {
|
||||||
w["redirect_all_requests_to"] = *v.HostName
|
w["redirect_all_requests_to"] = *v.HostName
|
||||||
} else {
|
} else {
|
||||||
|
var host string
|
||||||
|
var path string
|
||||||
|
parsedHostName, err := url.Parse(*v.HostName)
|
||||||
|
if err == nil {
|
||||||
|
host = parsedHostName.Host
|
||||||
|
path = parsedHostName.Path
|
||||||
|
} else {
|
||||||
|
host = *v.HostName
|
||||||
|
path = ""
|
||||||
|
}
|
||||||
|
|
||||||
w["redirect_all_requests_to"] = (&url.URL{
|
w["redirect_all_requests_to"] = (&url.URL{
|
||||||
Host: *v.HostName,
|
Host: host,
|
||||||
|
Path: path,
|
||||||
Scheme: *v.Protocol,
|
Scheme: *v.Protocol,
|
||||||
}).String()
|
}).String()
|
||||||
}
|
}
|
||||||
|
@ -947,7 +959,12 @@ func resourceAwsS3BucketWebsitePut(s3conn *s3.S3, d *schema.ResourceData, websit
|
||||||
if redirectAllRequestsTo != "" {
|
if redirectAllRequestsTo != "" {
|
||||||
redirect, err := url.Parse(redirectAllRequestsTo)
|
redirect, err := url.Parse(redirectAllRequestsTo)
|
||||||
if err == nil && redirect.Scheme != "" {
|
if err == nil && redirect.Scheme != "" {
|
||||||
websiteConfiguration.RedirectAllRequestsTo = &s3.RedirectAllRequestsTo{HostName: aws.String(redirect.Host), Protocol: aws.String(redirect.Scheme)}
|
var redirectHostBuf bytes.Buffer
|
||||||
|
redirectHostBuf.WriteString(redirect.Host)
|
||||||
|
if redirect.Path != "" {
|
||||||
|
redirectHostBuf.WriteString(redirect.Path)
|
||||||
|
}
|
||||||
|
websiteConfiguration.RedirectAllRequestsTo = &s3.RedirectAllRequestsTo{HostName: aws.String(redirectHostBuf.String()), Protocol: aws.String(redirect.Scheme)}
|
||||||
} else {
|
} else {
|
||||||
websiteConfiguration.RedirectAllRequestsTo = &s3.RedirectAllRequestsTo{HostName: aws.String(redirectAllRequestsTo)}
|
websiteConfiguration.RedirectAllRequestsTo = &s3.RedirectAllRequestsTo{HostName: aws.String(redirectAllRequestsTo)}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ func resourceAwsVpnGateway() *schema.Resource {
|
||||||
"vpc_id": &schema.Schema{
|
"vpc_id": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"tags": tagsSchema(),
|
"tags": tagsSchema(),
|
||||||
|
@ -80,17 +81,18 @@ func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
vpnGateway := resp.VpnGateways[0]
|
vpnGateway := resp.VpnGateways[0]
|
||||||
if vpnGateway == nil {
|
if vpnGateway == nil || *vpnGateway.State == "deleted" {
|
||||||
// Seems we have lost our VPN gateway
|
// Seems we have lost our VPN gateway
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(vpnGateway.VpcAttachments) == 0 || *vpnGateway.VpcAttachments[0].State == "detached" || *vpnGateway.VpcAttachments[0].State == "deleted" {
|
vpnAttachment := vpnGatewayGetAttachment(vpnGateway)
|
||||||
|
if len(vpnGateway.VpcAttachments) == 0 || *vpnAttachment.State == "detached" {
|
||||||
// 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", vpnGateway.VpcAttachments[0].VpcId)
|
d.Set("vpc_id", *vpnAttachment.VpcId)
|
||||||
}
|
}
|
||||||
d.Set("availability_zone", vpnGateway.AvailabilityZone)
|
d.Set("availability_zone", vpnGateway.AvailabilityZone)
|
||||||
d.Set("tags", tagsToMap(vpnGateway.Tags))
|
d.Set("tags", tagsToMap(vpnGateway.Tags))
|
||||||
|
@ -301,12 +303,21 @@ func vpnGatewayAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string)
|
||||||
}
|
}
|
||||||
|
|
||||||
vpnGateway := resp.VpnGateways[0]
|
vpnGateway := resp.VpnGateways[0]
|
||||||
|
|
||||||
if len(vpnGateway.VpcAttachments) == 0 {
|
if len(vpnGateway.VpcAttachments) == 0 {
|
||||||
// No attachments, we're detached
|
// No attachments, we're detached
|
||||||
return vpnGateway, "detached", nil
|
return vpnGateway, "detached", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return vpnGateway, *vpnGateway.VpcAttachments[0].State, nil
|
vpnAttachment := vpnGatewayGetAttachment(vpnGateway)
|
||||||
|
return vpnGateway, *vpnAttachment.State, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func vpnGatewayGetAttachment(vgw *ec2.VpnGateway) *ec2.VpcAttachment {
|
||||||
|
for _, v := range vgw.VpcAttachments {
|
||||||
|
if *v.State == "attached" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ec2.VpcAttachment{State: aws.String("detached")}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceAwsVpnGatewayAttachment() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceAwsVpnGatewayAttachmentCreate,
|
||||||
|
Read: resourceAwsVpnGatewayAttachmentRead,
|
||||||
|
Delete: resourceAwsVpnGatewayAttachmentDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"vpc_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"vpn_gateway_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceAwsVpnGatewayAttachmentCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
conn := meta.(*AWSClient).ec2conn
|
||||||
|
|
||||||
|
vpcId := d.Get("vpc_id").(string)
|
||||||
|
vgwId := d.Get("vpn_gateway_id").(string)
|
||||||
|
|
||||||
|
createOpts := &ec2.AttachVpnGatewayInput{
|
||||||
|
VpcId: aws.String(vpcId),
|
||||||
|
VpnGatewayId: aws.String(vgwId),
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] VPN Gateway attachment options: %#v", *createOpts)
|
||||||
|
|
||||||
|
_, err := conn.AttachVpnGateway(createOpts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error attaching VPN Gateway %q to VPC %q: %s",
|
||||||
|
vgwId, vpcId, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(vpnGatewayAttachmentId(vpcId, vgwId))
|
||||||
|
log.Printf("[INFO] VPN Gateway %q attachment ID: %s", vgwId, d.Id())
|
||||||
|
|
||||||
|
stateConf := &resource.StateChangeConf{
|
||||||
|
Pending: []string{"detached", "attaching"},
|
||||||
|
Target: []string{"attached"},
|
||||||
|
Refresh: vpnGatewayAttachmentStateRefresh(conn, vpcId, vgwId),
|
||||||
|
Timeout: 5 * time.Minute,
|
||||||
|
Delay: 10 * time.Second,
|
||||||
|
MinTimeout: 3 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stateConf.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for VPN Gateway %q to attach to VPC %q: %s",
|
||||||
|
vgwId, vpcId, err)
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] VPN Gateway %q attached to VPC %q.", vgwId, vpcId)
|
||||||
|
|
||||||
|
return resourceAwsVpnGatewayAttachmentRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceAwsVpnGatewayAttachmentRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
conn := meta.(*AWSClient).ec2conn
|
||||||
|
|
||||||
|
vgwId := d.Get("vpn_gateway_id").(string)
|
||||||
|
|
||||||
|
resp, err := conn.DescribeVpnGateways(&ec2.DescribeVpnGatewaysInput{
|
||||||
|
VpnGatewayIds: []*string{aws.String(vgwId)},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
awsErr, ok := err.(awserr.Error)
|
||||||
|
if ok && awsErr.Code() == "InvalidVPNGatewayID.NotFound" {
|
||||||
|
log.Printf("[WARN] VPN Gateway %q not found.", vgwId)
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
vgw := resp.VpnGateways[0]
|
||||||
|
if *vgw.State == "deleted" {
|
||||||
|
log.Printf("[INFO] VPN Gateway %q appears to have been deleted.", vgwId)
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vga := vpnGatewayGetAttachment(vgw)
|
||||||
|
if len(vgw.VpcAttachments) == 0 || *vga.State == "detached" {
|
||||||
|
d.Set("vpc_id", "")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("vpc_id", *vga.VpcId)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceAwsVpnGatewayAttachmentDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
conn := meta.(*AWSClient).ec2conn
|
||||||
|
|
||||||
|
vpcId := d.Get("vpc_id").(string)
|
||||||
|
vgwId := d.Get("vpn_gateway_id").(string)
|
||||||
|
|
||||||
|
if vpcId == "" {
|
||||||
|
log.Printf("[DEBUG] Not detaching VPN Gateway %q as no VPC ID is set.", vgwId)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := conn.DetachVpnGateway(&ec2.DetachVpnGatewayInput{
|
||||||
|
VpcId: aws.String(vpcId),
|
||||||
|
VpnGatewayId: aws.String(vgwId),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
awsErr, ok := err.(awserr.Error)
|
||||||
|
if ok {
|
||||||
|
switch awsErr.Code() {
|
||||||
|
case "InvalidVPNGatewayID.NotFound":
|
||||||
|
log.Printf("[WARN] VPN Gateway %q not found.", vgwId)
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
case "InvalidVpnGatewayAttachment.NotFound":
|
||||||
|
log.Printf(
|
||||||
|
"[WARN] VPN Gateway %q attachment to VPC %q not found.",
|
||||||
|
vgwId, vpcId)
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Error detaching VPN Gateway %q from VPC %q: %s",
|
||||||
|
vgwId, vpcId, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stateConf := &resource.StateChangeConf{
|
||||||
|
Pending: []string{"attached", "detaching"},
|
||||||
|
Target: []string{"detached"},
|
||||||
|
Refresh: vpnGatewayAttachmentStateRefresh(conn, vpcId, vgwId),
|
||||||
|
Timeout: 5 * time.Minute,
|
||||||
|
Delay: 10 * time.Second,
|
||||||
|
MinTimeout: 3 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stateConf.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for VPN Gateway %q to detach from VPC %q: %s",
|
||||||
|
vgwId, vpcId, err)
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] VPN Gateway %q detached from VPC %q.", vgwId, vpcId)
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vpnGatewayAttachmentStateRefresh(conn *ec2.EC2, vpcId, vgwId string) resource.StateRefreshFunc {
|
||||||
|
return func() (interface{}, string, error) {
|
||||||
|
resp, err := conn.DescribeVpnGateways(&ec2.DescribeVpnGatewaysInput{
|
||||||
|
Filters: []*ec2.Filter{
|
||||||
|
&ec2.Filter{
|
||||||
|
Name: aws.String("attachment.vpc-id"),
|
||||||
|
Values: []*string{aws.String(vpcId)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VpnGatewayIds: []*string{aws.String(vgwId)},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
awsErr, ok := err.(awserr.Error)
|
||||||
|
if ok {
|
||||||
|
switch awsErr.Code() {
|
||||||
|
case "InvalidVPNGatewayID.NotFound":
|
||||||
|
fallthrough
|
||||||
|
case "InvalidVpnGatewayAttachment.NotFound":
|
||||||
|
return nil, "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
vgw := resp.VpnGateways[0]
|
||||||
|
if len(vgw.VpcAttachments) == 0 {
|
||||||
|
return vgw, "detached", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vga := vpnGatewayGetAttachment(vgw)
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] VPN Gateway %q attachment status: %s", vgwId, *vga.State)
|
||||||
|
return vgw, *vga.State, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func vpnGatewayAttachmentId(vpcId, vgwId string) string {
|
||||||
|
return fmt.Sprintf("vpn-attachment-%x", hashcode.String(fmt.Sprintf("%s-%s", vpcId, vgwId)))
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccAWSVpnGatewayAttachment_basic(t *testing.T) {
|
||||||
|
var vpc ec2.Vpc
|
||||||
|
var vgw ec2.VpnGateway
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
IDRefreshName: "aws_vpn_gateway_attachment.test",
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckVpnGatewayAttachmentDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccVpnGatewayAttachmentConfig,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckVpcExists(
|
||||||
|
"aws_vpc.test",
|
||||||
|
&vpc),
|
||||||
|
testAccCheckVpnGatewayExists(
|
||||||
|
"aws_vpn_gateway.test",
|
||||||
|
&vgw),
|
||||||
|
testAccCheckVpnGatewayAttachmentExists(
|
||||||
|
"aws_vpn_gateway_attachment.test",
|
||||||
|
&vpc, &vgw),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccAWSVpnGatewayAttachment_deleted(t *testing.T) {
|
||||||
|
var vpc ec2.Vpc
|
||||||
|
var vgw ec2.VpnGateway
|
||||||
|
|
||||||
|
testDeleted := func(n string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
_, ok := s.RootModule().Resources[n]
|
||||||
|
if ok {
|
||||||
|
return fmt.Errorf("Expected VPN Gateway attachment resource %q to be deleted.", n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
IDRefreshName: "aws_vpn_gateway_attachment.test",
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckVpnGatewayAttachmentDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccVpnGatewayAttachmentConfig,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckVpcExists(
|
||||||
|
"aws_vpc.test",
|
||||||
|
&vpc),
|
||||||
|
testAccCheckVpnGatewayExists(
|
||||||
|
"aws_vpn_gateway.test",
|
||||||
|
&vgw),
|
||||||
|
testAccCheckVpnGatewayAttachmentExists(
|
||||||
|
"aws_vpn_gateway_attachment.test",
|
||||||
|
&vpc, &vgw),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccNoVpnGatewayAttachmentConfig,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testDeleted("aws_vpn_gateway_attachment.test"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckVpnGatewayAttachmentExists(n string, vpc *ec2.Vpc, vgw *ec2.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 ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
vpcId := rs.Primary.Attributes["vpc_id"]
|
||||||
|
vgwId := rs.Primary.Attributes["vpn_gateway_id"]
|
||||||
|
|
||||||
|
if len(vgw.VpcAttachments) == 0 {
|
||||||
|
return fmt.Errorf("VPN Gateway %q has no attachments.", vgwId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *vgw.VpcAttachments[0].State != "attached" {
|
||||||
|
return fmt.Errorf("Expected VPN Gateway %q to be in attached state, but got: %q",
|
||||||
|
vgwId, *vgw.VpcAttachments[0].State)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *vgw.VpcAttachments[0].VpcId != *vpc.VpcId {
|
||||||
|
return fmt.Errorf("Expected VPN Gateway %q to be attached to VPC %q, but got: %q",
|
||||||
|
vgwId, vpcId, *vgw.VpcAttachments[0].VpcId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckVpnGatewayAttachmentDestroy(s *terraform.State) error {
|
||||||
|
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "aws_vpn_gateway_attachment" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
vgwId := rs.Primary.Attributes["vpn_gateway_id"]
|
||||||
|
|
||||||
|
resp, err := conn.DescribeVpnGateways(&ec2.DescribeVpnGatewaysInput{
|
||||||
|
VpnGatewayIds: []*string{aws.String(vgwId)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
vgw := resp.VpnGateways[0]
|
||||||
|
if *vgw.VpcAttachments[0].State != "detached" {
|
||||||
|
return fmt.Errorf("Expected VPN Gateway %q to be in detached state, but got: %q",
|
||||||
|
vgwId, *vgw.VpcAttachments[0].State)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccNoVpnGatewayAttachmentConfig = `
|
||||||
|
resource "aws_vpc" "test" {
|
||||||
|
cidr_block = "10.0.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_vpn_gateway" "test" { }
|
||||||
|
`
|
||||||
|
|
||||||
|
const testAccVpnGatewayAttachmentConfig = `
|
||||||
|
resource "aws_vpc" "test" {
|
||||||
|
cidr_block = "10.0.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_vpn_gateway" "test" { }
|
||||||
|
|
||||||
|
resource "aws_vpn_gateway_attachment" "test" {
|
||||||
|
vpc_id = "${aws_vpc.test.id}"
|
||||||
|
vpn_gateway_id = "${aws_vpn_gateway.test.id}"
|
||||||
|
}
|
||||||
|
`
|
|
@ -16,10 +16,10 @@ func TestAccAWSVpnGateway_basic(t *testing.T) {
|
||||||
|
|
||||||
testNotEqual := func(*terraform.State) error {
|
testNotEqual := func(*terraform.State) error {
|
||||||
if len(v.VpcAttachments) == 0 {
|
if len(v.VpcAttachments) == 0 {
|
||||||
return fmt.Errorf("VPN gateway A is not attached")
|
return fmt.Errorf("VPN Gateway A is not attached")
|
||||||
}
|
}
|
||||||
if len(v2.VpcAttachments) == 0 {
|
if len(v2.VpcAttachments) == 0 {
|
||||||
return fmt.Errorf("VPN gateway B is not attached")
|
return fmt.Errorf("VPN Gateway B is not attached")
|
||||||
}
|
}
|
||||||
|
|
||||||
id1 := v.VpcAttachments[0].VpcId
|
id1 := v.VpcAttachments[0].VpcId
|
||||||
|
@ -58,20 +58,38 @@ func TestAccAWSVpnGateway_basic(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccAWSVpnGateway_reattach(t *testing.T) {
|
func TestAccAWSVpnGateway_reattach(t *testing.T) {
|
||||||
var v ec2.VpnGateway
|
var vpc1, vpc2 ec2.Vpc
|
||||||
|
var vgw1, vgw2 ec2.VpnGateway
|
||||||
|
|
||||||
genTestStateFunc := func(expectedState string) func(*terraform.State) error {
|
testAttachmentFunc := func(vgw *ec2.VpnGateway, vpc *ec2.Vpc) func(*terraform.State) error {
|
||||||
return func(*terraform.State) error {
|
return func(*terraform.State) error {
|
||||||
if len(v.VpcAttachments) == 0 {
|
if len(vgw.VpcAttachments) == 0 {
|
||||||
if expectedState != "detached" {
|
return fmt.Errorf("VPN Gateway %q has no VPC attachments.",
|
||||||
return fmt.Errorf("VPN gateway has no VPC attachments")
|
*vgw.VpnGatewayId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(vgw.VpcAttachments) > 1 {
|
||||||
|
count := 0
|
||||||
|
for _, v := range vgw.VpcAttachments {
|
||||||
|
if *v.State == "attached" {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if len(v.VpcAttachments) == 1 {
|
if count > 1 {
|
||||||
if *v.VpcAttachments[0].State != expectedState {
|
return fmt.Errorf(
|
||||||
return fmt.Errorf("Expected VPC gateway VPC attachment to be in '%s' state, but was not: %s", expectedState, v)
|
"VPN Gateway %q has an unexpected number of VPC attachments (more than 1): %#v",
|
||||||
|
*vgw.VpnGatewayId, vgw.VpcAttachments)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
return fmt.Errorf("VPN gateway has unexpected number of VPC attachments(more than 1): %s", v)
|
|
||||||
|
if *vgw.VpcAttachments[0].State != "attached" {
|
||||||
|
return fmt.Errorf("Expected VPN Gateway %q to be attached.",
|
||||||
|
*vgw.VpnGatewayId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *vgw.VpcAttachments[0].VpcId != *vpc.VpcId {
|
||||||
|
return fmt.Errorf("Expected VPN Gateway %q to be attached to VPC %q, but got: %q",
|
||||||
|
*vgw.VpnGatewayId, *vpc.VpcId, *vgw.VpcAttachments[0].VpcId)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -84,27 +102,38 @@ func TestAccAWSVpnGateway_reattach(t *testing.T) {
|
||||||
CheckDestroy: testAccCheckVpnGatewayDestroy,
|
CheckDestroy: testAccCheckVpnGatewayDestroy,
|
||||||
Steps: []resource.TestStep{
|
Steps: []resource.TestStep{
|
||||||
resource.TestStep{
|
resource.TestStep{
|
||||||
Config: testAccVpnGatewayConfig,
|
Config: testAccCheckVpnGatewayConfigReattach,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckVpcExists("aws_vpc.foo", &vpc1),
|
||||||
|
testAccCheckVpcExists("aws_vpc.bar", &vpc2),
|
||||||
testAccCheckVpnGatewayExists(
|
testAccCheckVpnGatewayExists(
|
||||||
"aws_vpn_gateway.foo", &v),
|
"aws_vpn_gateway.foo", &vgw1),
|
||||||
genTestStateFunc("attached"),
|
testAccCheckVpnGatewayExists(
|
||||||
|
"aws_vpn_gateway.bar", &vgw2),
|
||||||
|
testAttachmentFunc(&vgw1, &vpc1),
|
||||||
|
testAttachmentFunc(&vgw2, &vpc2),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
resource.TestStep{
|
resource.TestStep{
|
||||||
Config: testAccVpnGatewayConfigDetach,
|
Config: testAccCheckVpnGatewayConfigReattachChange,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckVpnGatewayExists(
|
testAccCheckVpnGatewayExists(
|
||||||
"aws_vpn_gateway.foo", &v),
|
"aws_vpn_gateway.foo", &vgw1),
|
||||||
genTestStateFunc("detached"),
|
testAccCheckVpnGatewayExists(
|
||||||
|
"aws_vpn_gateway.bar", &vgw2),
|
||||||
|
testAttachmentFunc(&vgw2, &vpc1),
|
||||||
|
testAttachmentFunc(&vgw1, &vpc2),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
resource.TestStep{
|
resource.TestStep{
|
||||||
Config: testAccVpnGatewayConfig,
|
Config: testAccCheckVpnGatewayConfigReattach,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckVpnGatewayExists(
|
testAccCheckVpnGatewayExists(
|
||||||
"aws_vpn_gateway.foo", &v),
|
"aws_vpn_gateway.foo", &vgw1),
|
||||||
genTestStateFunc("attached"),
|
testAccCheckVpnGatewayExists(
|
||||||
|
"aws_vpn_gateway.bar", &vgw2),
|
||||||
|
testAttachmentFunc(&vgw1, &vpc1),
|
||||||
|
testAttachmentFunc(&vgw2, &vpc2),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -118,7 +147,7 @@ func TestAccAWSVpnGateway_delete(t *testing.T) {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
_, ok := s.RootModule().Resources[r]
|
_, ok := s.RootModule().Resources[r]
|
||||||
if ok {
|
if ok {
|
||||||
return fmt.Errorf("VPN Gateway %q should have been deleted", r)
|
return fmt.Errorf("VPN Gateway %q should have been deleted.", r)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -159,7 +188,6 @@ func TestAccAWSVpnGateway_tags(t *testing.T) {
|
||||||
testAccCheckTags(&v.Tags, "foo", "bar"),
|
testAccCheckTags(&v.Tags, "foo", "bar"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
resource.TestStep{
|
resource.TestStep{
|
||||||
Config: testAccCheckVpnGatewayConfigTagsUpdate,
|
Config: testAccCheckVpnGatewayConfigTagsUpdate,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
@ -198,7 +226,7 @@ func testAccCheckVpnGatewayDestroy(s *terraform.State) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if *v.State != "deleted" {
|
if *v.State != "deleted" {
|
||||||
return fmt.Errorf("Expected VpnGateway to be in deleted state, but was not: %s", v)
|
return fmt.Errorf("Expected VPN Gateway to be in deleted state, but was not: %s", v)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -235,7 +263,7 @@ func testAccCheckVpnGatewayExists(n string, ig *ec2.VpnGateway) resource.TestChe
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(resp.VpnGateways) == 0 {
|
if len(resp.VpnGateways) == 0 {
|
||||||
return fmt.Errorf("VPNGateway not found")
|
return fmt.Errorf("VPN Gateway not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
*ig = *resp.VpnGateways[0]
|
*ig = *resp.VpnGateways[0]
|
||||||
|
@ -270,16 +298,6 @@ resource "aws_vpn_gateway" "foo" {
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const testAccVpnGatewayConfigDetach = `
|
|
||||||
resource "aws_vpc" "foo" {
|
|
||||||
cidr_block = "10.1.0.0/16"
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "aws_vpn_gateway" "foo" {
|
|
||||||
vpc_id = ""
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const testAccCheckVpnGatewayConfigTags = `
|
const testAccCheckVpnGatewayConfigTags = `
|
||||||
resource "aws_vpc" "foo" {
|
resource "aws_vpc" "foo" {
|
||||||
cidr_block = "10.1.0.0/16"
|
cidr_block = "10.1.0.0/16"
|
||||||
|
@ -305,3 +323,39 @@ resource "aws_vpn_gateway" "foo" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testAccCheckVpnGatewayConfigReattach = `
|
||||||
|
resource "aws_vpc" "foo" {
|
||||||
|
cidr_block = "10.1.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_vpc" "bar" {
|
||||||
|
cidr_block = "10.2.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_vpn_gateway" "foo" {
|
||||||
|
vpc_id = "${aws_vpc.foo.id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_vpn_gateway" "bar" {
|
||||||
|
vpc_id = "${aws_vpc.bar.id}"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testAccCheckVpnGatewayConfigReattachChange = `
|
||||||
|
resource "aws_vpc" "foo" {
|
||||||
|
cidr_block = "10.1.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_vpc" "bar" {
|
||||||
|
cidr_block = "10.2.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_vpn_gateway" "foo" {
|
||||||
|
vpc_id = "${aws_vpc.bar.id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_vpn_gateway" "bar" {
|
||||||
|
vpc_id = "${aws_vpc.foo.id}"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
|
@ -122,8 +122,8 @@ func TestValidateVarsAttribute(t *testing.T) {
|
||||||
func TestTemplateSharedMemoryRace(t *testing.T) {
|
func TestTemplateSharedMemoryRace(t *testing.T) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
go func(wg *sync.WaitGroup, t *testing.T, i int) {
|
wg.Add(1)
|
||||||
wg.Add(1)
|
go func(t *testing.T, i int) {
|
||||||
out, err := execute("don't panic!", map[string]interface{}{})
|
out, err := execute("don't panic!", map[string]interface{}{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -132,7 +132,7 @@ func TestTemplateSharedMemoryRace(t *testing.T) {
|
||||||
t.Fatalf("bad output: %s", out)
|
t.Fatalf("bad output: %s", out)
|
||||||
}
|
}
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}(&wg, t, i)
|
}(t, i)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package vsphere
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/vmware/govmomi"
|
"github.com/vmware/govmomi"
|
||||||
|
@ -13,10 +14,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type file struct {
|
type file struct {
|
||||||
datacenter string
|
sourceDatacenter string
|
||||||
datastore string
|
datacenter string
|
||||||
sourceFile string
|
sourceDatastore string
|
||||||
destinationFile string
|
datastore string
|
||||||
|
sourceFile string
|
||||||
|
destinationFile string
|
||||||
|
createDirectories bool
|
||||||
|
copyFile bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceVSphereFile() *schema.Resource {
|
func resourceVSphereFile() *schema.Resource {
|
||||||
|
@ -30,10 +35,20 @@ func resourceVSphereFile() *schema.Resource {
|
||||||
"datacenter": {
|
"datacenter": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"source_datacenter": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"datastore": {
|
"datastore": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"source_datastore": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
|
@ -49,6 +64,11 @@ func resourceVSphereFile() *schema.Resource {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"create_directories": {
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,10 +80,20 @@ func resourceVSphereFileCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
f := file{}
|
f := file{}
|
||||||
|
|
||||||
|
if v, ok := d.GetOk("source_datacenter"); ok {
|
||||||
|
f.sourceDatacenter = v.(string)
|
||||||
|
f.copyFile = true
|
||||||
|
}
|
||||||
|
|
||||||
if v, ok := d.GetOk("datacenter"); ok {
|
if v, ok := d.GetOk("datacenter"); ok {
|
||||||
f.datacenter = v.(string)
|
f.datacenter = v.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v, ok := d.GetOk("source_datastore"); ok {
|
||||||
|
f.sourceDatastore = v.(string)
|
||||||
|
f.copyFile = true
|
||||||
|
}
|
||||||
|
|
||||||
if v, ok := d.GetOk("datastore"); ok {
|
if v, ok := d.GetOk("datastore"); ok {
|
||||||
f.datastore = v.(string)
|
f.datastore = v.(string)
|
||||||
} else {
|
} else {
|
||||||
|
@ -82,6 +112,10 @@ func resourceVSphereFileCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
return fmt.Errorf("destination_file argument is required")
|
return fmt.Errorf("destination_file argument is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v, ok := d.GetOk("create_directories"); ok {
|
||||||
|
f.createDirectories = v.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
err := createFile(client, &f)
|
err := createFile(client, &f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -108,16 +142,53 @@ func createFile(client *govmomi.Client, f *file) error {
|
||||||
return fmt.Errorf("error %s", err)
|
return fmt.Errorf("error %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dsurl, err := ds.URL(context.TODO(), dc, f.destinationFile)
|
if f.copyFile {
|
||||||
if err != nil {
|
// Copying file from withing vSphere
|
||||||
return err
|
source_dc, err := finder.Datacenter(context.TODO(), f.sourceDatacenter)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error %s", err)
|
||||||
|
}
|
||||||
|
finder = finder.SetDatacenter(dc)
|
||||||
|
|
||||||
|
source_ds, err := getDatastore(finder, f.sourceDatastore)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fm := object.NewFileManager(client.Client)
|
||||||
|
if f.createDirectories {
|
||||||
|
directoryPathIndex := strings.LastIndex(f.destinationFile, "/")
|
||||||
|
path := f.destinationFile[0:directoryPathIndex]
|
||||||
|
err = fm.MakeDirectory(context.TODO(), ds.Path(path), dc, true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task, err := fm.CopyDatastoreFile(context.TODO(), source_ds.Path(f.sourceFile), source_dc, ds.Path(f.destinationFile), dc, true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = task.WaitForResult(context.TODO(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Uploading file to vSphere
|
||||||
|
dsurl, err := ds.URL(context.TODO(), dc, f.destinationFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := soap.DefaultUpload
|
||||||
|
err = client.Client.UploadFile(f.sourceFile, dsurl, &p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p := soap.DefaultUpload
|
|
||||||
err = client.Client.UploadFile(f.sourceFile, dsurl, &p)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error %s", err)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,10 +197,18 @@ func resourceVSphereFileRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
log.Printf("[DEBUG] reading file: %#v", d)
|
log.Printf("[DEBUG] reading file: %#v", d)
|
||||||
f := file{}
|
f := file{}
|
||||||
|
|
||||||
|
if v, ok := d.GetOk("source_datacenter"); ok {
|
||||||
|
f.sourceDatacenter = v.(string)
|
||||||
|
}
|
||||||
|
|
||||||
if v, ok := d.GetOk("datacenter"); ok {
|
if v, ok := d.GetOk("datacenter"); ok {
|
||||||
f.datacenter = v.(string)
|
f.datacenter = v.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v, ok := d.GetOk("source_datastore"); ok {
|
||||||
|
f.sourceDatastore = v.(string)
|
||||||
|
}
|
||||||
|
|
||||||
if v, ok := d.GetOk("datastore"); ok {
|
if v, ok := d.GetOk("datastore"); ok {
|
||||||
f.datastore = v.(string)
|
f.datastore = v.(string)
|
||||||
} else {
|
} else {
|
||||||
|
@ -179,57 +258,69 @@ func resourceVSphereFileRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
func resourceVSphereFileUpdate(d *schema.ResourceData, meta interface{}) error {
|
func resourceVSphereFileUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
log.Printf("[DEBUG] updating file: %#v", d)
|
log.Printf("[DEBUG] updating file: %#v", d)
|
||||||
if d.HasChange("destination_file") {
|
|
||||||
oldDestinationFile, newDestinationFile := d.GetChange("destination_file")
|
|
||||||
f := file{}
|
|
||||||
|
|
||||||
if v, ok := d.GetOk("datacenter"); ok {
|
if d.HasChange("destination_file") || d.HasChange("datacenter") || d.HasChange("datastore") {
|
||||||
f.datacenter = v.(string)
|
// File needs to be moved, get old and new destination changes
|
||||||
}
|
var oldDataceneter, newDatacenter, oldDatastore, newDatastore, oldDestinationFile, newDestinationFile string
|
||||||
|
if d.HasChange("datacenter") {
|
||||||
if v, ok := d.GetOk("datastore"); ok {
|
tmpOldDataceneter, tmpNewDatacenter := d.GetChange("datacenter")
|
||||||
f.datastore = v.(string)
|
oldDataceneter = tmpOldDataceneter.(string)
|
||||||
|
newDatacenter = tmpNewDatacenter.(string)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("datastore argument is required")
|
if v, ok := d.GetOk("datacenter"); ok {
|
||||||
|
oldDataceneter = v.(string)
|
||||||
|
newDatacenter = oldDataceneter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if d.HasChange("datastore") {
|
||||||
if v, ok := d.GetOk("source_file"); ok {
|
tmpOldDatastore, tmpNewDatastore := d.GetChange("datastore")
|
||||||
f.sourceFile = v.(string)
|
oldDatastore = tmpOldDatastore.(string)
|
||||||
|
newDatastore = tmpNewDatastore.(string)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("source_file argument is required")
|
oldDatastore = d.Get("datastore").(string)
|
||||||
|
newDatastore = oldDatastore
|
||||||
}
|
}
|
||||||
|
if d.HasChange("destination_file") {
|
||||||
if v, ok := d.GetOk("destination_file"); ok {
|
tmpOldDestinationFile, tmpNewDestinationFile := d.GetChange("destination_file")
|
||||||
f.destinationFile = v.(string)
|
oldDestinationFile = tmpOldDestinationFile.(string)
|
||||||
|
newDestinationFile = tmpNewDestinationFile.(string)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("destination_file argument is required")
|
oldDestinationFile = d.Get("destination_file").(string)
|
||||||
|
newDestinationFile = oldDestinationFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get old and new dataceter and datastore
|
||||||
client := meta.(*govmomi.Client)
|
client := meta.(*govmomi.Client)
|
||||||
dc, err := getDatacenter(client, f.datacenter)
|
dcOld, err := getDatacenter(client, oldDataceneter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dcNew, err := getDatacenter(client, newDatacenter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
finder := find.NewFinder(client.Client, true)
|
finder := find.NewFinder(client.Client, true)
|
||||||
finder = finder.SetDatacenter(dc)
|
finder = finder.SetDatacenter(dcOld)
|
||||||
|
dsOld, err := getDatastore(finder, oldDatastore)
|
||||||
ds, err := getDatastore(finder, f.datastore)
|
if err != nil {
|
||||||
|
return fmt.Errorf("error %s", err)
|
||||||
|
}
|
||||||
|
finder = finder.SetDatacenter(dcNew)
|
||||||
|
dsNew, err := getDatastore(finder, newDatastore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error %s", err)
|
return fmt.Errorf("error %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move file between old/new dataceter, datastore and path (destination_file)
|
||||||
fm := object.NewFileManager(client.Client)
|
fm := object.NewFileManager(client.Client)
|
||||||
task, err := fm.MoveDatastoreFile(context.TODO(), ds.Path(oldDestinationFile.(string)), dc, ds.Path(newDestinationFile.(string)), dc, true)
|
task, err := fm.MoveDatastoreFile(context.TODO(), dsOld.Path(oldDestinationFile), dcOld, dsNew.Path(newDestinationFile), dcNew, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = task.WaitForResult(context.TODO(), nil)
|
_, err = task.WaitForResult(context.TODO(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Basic file creation
|
// Basic file creation (upload to vSphere)
|
||||||
func TestAccVSphereFile_basic(t *testing.T) {
|
func TestAccVSphereFile_basic(t *testing.T) {
|
||||||
testVmdkFileData := []byte("# Disk DescriptorFile\n")
|
testVmdkFileData := []byte("# Disk DescriptorFile\n")
|
||||||
testVmdkFile := "/tmp/tf_test.vmdk"
|
testVmdkFile := "/tmp/tf_test.vmdk"
|
||||||
|
@ -55,6 +55,59 @@ func TestAccVSphereFile_basic(t *testing.T) {
|
||||||
os.Remove(testVmdkFile)
|
os.Remove(testVmdkFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Basic file copy within vSphere
|
||||||
|
func TestAccVSphereFile_basicUploadAndCopy(t *testing.T) {
|
||||||
|
testVmdkFileData := []byte("# Disk DescriptorFile\n")
|
||||||
|
sourceFile := "/tmp/tf_test.vmdk"
|
||||||
|
uploadResourceName := "myfileupload"
|
||||||
|
copyResourceName := "myfilecopy"
|
||||||
|
sourceDatacenter := os.Getenv("VSPHERE_DATACENTER")
|
||||||
|
datacenter := sourceDatacenter
|
||||||
|
sourceDatastore := os.Getenv("VSPHERE_DATASTORE")
|
||||||
|
datastore := sourceDatastore
|
||||||
|
destinationFile := "tf_file_test.vmdk"
|
||||||
|
sourceFileCopy := "${vsphere_file." + uploadResourceName + ".destination_file}"
|
||||||
|
destinationFileCopy := "tf_file_test_copy.vmdk"
|
||||||
|
|
||||||
|
err := ioutil.WriteFile(sourceFile, testVmdkFileData, 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckVSphereFileDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: fmt.Sprintf(
|
||||||
|
testAccCheckVSphereFileCopyConfig,
|
||||||
|
uploadResourceName,
|
||||||
|
datacenter,
|
||||||
|
datastore,
|
||||||
|
sourceFile,
|
||||||
|
destinationFile,
|
||||||
|
copyResourceName,
|
||||||
|
datacenter,
|
||||||
|
datacenter,
|
||||||
|
datastore,
|
||||||
|
datastore,
|
||||||
|
sourceFileCopy,
|
||||||
|
destinationFileCopy,
|
||||||
|
),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckVSphereFileExists("vsphere_file."+uploadResourceName, destinationFile, true),
|
||||||
|
testAccCheckVSphereFileExists("vsphere_file."+copyResourceName, destinationFileCopy, true),
|
||||||
|
resource.TestCheckResourceAttr("vsphere_file."+uploadResourceName, "destination_file", destinationFile),
|
||||||
|
resource.TestCheckResourceAttr("vsphere_file."+copyResourceName, "destination_file", destinationFileCopy),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
os.Remove(sourceFile)
|
||||||
|
}
|
||||||
|
|
||||||
// file creation followed by a rename of file (update)
|
// file creation followed by a rename of file (update)
|
||||||
func TestAccVSphereFile_renamePostCreation(t *testing.T) {
|
func TestAccVSphereFile_renamePostCreation(t *testing.T) {
|
||||||
testVmdkFileData := []byte("# Disk DescriptorFile\n")
|
testVmdkFileData := []byte("# Disk DescriptorFile\n")
|
||||||
|
@ -67,7 +120,7 @@ func TestAccVSphereFile_renamePostCreation(t *testing.T) {
|
||||||
|
|
||||||
datacenter := os.Getenv("VSPHERE_DATACENTER")
|
datacenter := os.Getenv("VSPHERE_DATACENTER")
|
||||||
datastore := os.Getenv("VSPHERE_DATASTORE")
|
datastore := os.Getenv("VSPHERE_DATASTORE")
|
||||||
testMethod := "basic"
|
testMethod := "create_upgrade"
|
||||||
resourceName := "vsphere_file." + testMethod
|
resourceName := "vsphere_file." + testMethod
|
||||||
destinationFile := "tf_test_file.vmdk"
|
destinationFile := "tf_test_file.vmdk"
|
||||||
destinationFileMoved := "tf_test_file_moved.vmdk"
|
destinationFileMoved := "tf_test_file_moved.vmdk"
|
||||||
|
@ -76,7 +129,7 @@ func TestAccVSphereFile_renamePostCreation(t *testing.T) {
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
Providers: testAccProviders,
|
Providers: testAccProviders,
|
||||||
CheckDestroy: testAccCheckVSphereFolderDestroy,
|
CheckDestroy: testAccCheckVSphereFileDestroy,
|
||||||
Steps: []resource.TestStep{
|
Steps: []resource.TestStep{
|
||||||
{
|
{
|
||||||
Config: fmt.Sprintf(
|
Config: fmt.Sprintf(
|
||||||
|
@ -113,6 +166,84 @@ func TestAccVSphereFile_renamePostCreation(t *testing.T) {
|
||||||
os.Remove(testVmdkFile)
|
os.Remove(testVmdkFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// file upload, then copy, finally the copy is renamed (moved) (update)
|
||||||
|
func TestAccVSphereFile_uploadAndCopyAndUpdate(t *testing.T) {
|
||||||
|
testVmdkFileData := []byte("# Disk DescriptorFile\n")
|
||||||
|
sourceFile := "/tmp/tf_test.vmdk"
|
||||||
|
uploadResourceName := "myfileupload"
|
||||||
|
copyResourceName := "myfilecopy"
|
||||||
|
sourceDatacenter := os.Getenv("VSPHERE_DATACENTER")
|
||||||
|
datacenter := sourceDatacenter
|
||||||
|
sourceDatastore := os.Getenv("VSPHERE_DATASTORE")
|
||||||
|
datastore := sourceDatastore
|
||||||
|
destinationFile := "tf_file_test.vmdk"
|
||||||
|
sourceFileCopy := "${vsphere_file." + uploadResourceName + ".destination_file}"
|
||||||
|
destinationFileCopy := "tf_file_test_copy.vmdk"
|
||||||
|
destinationFileMoved := "tf_test_file_moved.vmdk"
|
||||||
|
|
||||||
|
err := ioutil.WriteFile(sourceFile, testVmdkFileData, 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckVSphereFileDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: fmt.Sprintf(
|
||||||
|
testAccCheckVSphereFileCopyConfig,
|
||||||
|
uploadResourceName,
|
||||||
|
datacenter,
|
||||||
|
datastore,
|
||||||
|
sourceFile,
|
||||||
|
destinationFile,
|
||||||
|
copyResourceName,
|
||||||
|
datacenter,
|
||||||
|
datacenter,
|
||||||
|
datastore,
|
||||||
|
datastore,
|
||||||
|
sourceFileCopy,
|
||||||
|
destinationFileCopy,
|
||||||
|
),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckVSphereFileExists("vsphere_file."+uploadResourceName, destinationFile, true),
|
||||||
|
testAccCheckVSphereFileExists("vsphere_file."+copyResourceName, destinationFileCopy, true),
|
||||||
|
resource.TestCheckResourceAttr("vsphere_file."+uploadResourceName, "destination_file", destinationFile),
|
||||||
|
resource.TestCheckResourceAttr("vsphere_file."+copyResourceName, "destination_file", destinationFileCopy),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: fmt.Sprintf(
|
||||||
|
testAccCheckVSphereFileCopyConfig,
|
||||||
|
uploadResourceName,
|
||||||
|
datacenter,
|
||||||
|
datastore,
|
||||||
|
sourceFile,
|
||||||
|
destinationFile,
|
||||||
|
copyResourceName,
|
||||||
|
datacenter,
|
||||||
|
datacenter,
|
||||||
|
datastore,
|
||||||
|
datastore,
|
||||||
|
sourceFileCopy,
|
||||||
|
destinationFileMoved,
|
||||||
|
),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckVSphereFileExists("vsphere_file."+uploadResourceName, destinationFile, true),
|
||||||
|
testAccCheckVSphereFileExists("vsphere_file."+copyResourceName, destinationFileCopy, false),
|
||||||
|
testAccCheckVSphereFileExists("vsphere_file."+copyResourceName, destinationFileMoved, true),
|
||||||
|
resource.TestCheckResourceAttr("vsphere_file."+uploadResourceName, "destination_file", destinationFile),
|
||||||
|
resource.TestCheckResourceAttr("vsphere_file."+copyResourceName, "destination_file", destinationFileMoved),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
os.Remove(sourceFile)
|
||||||
|
}
|
||||||
|
|
||||||
func testAccCheckVSphereFileDestroy(s *terraform.State) error {
|
func testAccCheckVSphereFileDestroy(s *terraform.State) error {
|
||||||
client := testAccProvider.Meta().(*govmomi.Client)
|
client := testAccProvider.Meta().(*govmomi.Client)
|
||||||
finder := find.NewFinder(client.Client, true)
|
finder := find.NewFinder(client.Client, true)
|
||||||
|
@ -201,3 +332,19 @@ resource "vsphere_file" "%s" {
|
||||||
destination_file = "%s"
|
destination_file = "%s"
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
const testAccCheckVSphereFileCopyConfig = `
|
||||||
|
resource "vsphere_file" "%s" {
|
||||||
|
datacenter = "%s"
|
||||||
|
datastore = "%s"
|
||||||
|
source_file = "%s"
|
||||||
|
destination_file = "%s"
|
||||||
|
}
|
||||||
|
resource "vsphere_file" "%s" {
|
||||||
|
source_datacenter = "%s"
|
||||||
|
datacenter = "%s"
|
||||||
|
source_datastore = "%s"
|
||||||
|
datastore = "%s"
|
||||||
|
source_file = "%s"
|
||||||
|
destination_file = "%s"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
|
@ -47,6 +47,22 @@ func (c *PushCommand) Run(args []string) int {
|
||||||
overwriteMap[v] = struct{}{}
|
overwriteMap[v] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is a map of variables specifically from the CLI that we want to overwrite.
|
||||||
|
// We need this because there is a chance that the user is trying to modify
|
||||||
|
// a variable we don't see in our context, but which exists in this atlas
|
||||||
|
// environment.
|
||||||
|
cliVars := make(map[string]string)
|
||||||
|
for k, v := range c.variables {
|
||||||
|
if _, ok := overwriteMap[k]; ok {
|
||||||
|
if val, ok := v.(string); ok {
|
||||||
|
cliVars[k] = val
|
||||||
|
} else {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Error reading value for variable: %s", k))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The pwd is used for the configuration path if one is not given
|
// The pwd is used for the configuration path if one is not given
|
||||||
pwd, err := os.Getwd()
|
pwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -145,19 +161,14 @@ func (c *PushCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter any overwrites from the atlas vars
|
|
||||||
for k := range overwriteMap {
|
|
||||||
delete(atlasVars, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set remote variables in the context if we don't have a value here. These
|
// Set remote variables in the context if we don't have a value here. These
|
||||||
// don't have to be correct, it just prevents the Input walk from prompting
|
// don't have to be correct, it just prevents the Input walk from prompting
|
||||||
// the user for input, The atlas variable may be an hcl-encoded object, but
|
// the user for input.
|
||||||
// we're just going to set it as the raw string value.
|
|
||||||
ctxVars := ctx.Variables()
|
ctxVars := ctx.Variables()
|
||||||
for k, av := range atlasVars {
|
atlasVarSentry := "ATLAS_78AC153CA649EAA44815DAD6CBD4816D"
|
||||||
|
for k, _ := range atlasVars {
|
||||||
if _, ok := ctxVars[k]; !ok {
|
if _, ok := ctxVars[k]; !ok {
|
||||||
ctx.SetVariable(k, av.Value)
|
ctx.SetVariable(k, atlasVarSentry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,23 +214,47 @@ func (c *PushCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output to the user the variables that will be uploaded
|
// List of the vars we're uploading to display to the user.
|
||||||
|
// We always upload all vars from atlas, but only report them if they are overwritten.
|
||||||
var setVars []string
|
var setVars []string
|
||||||
|
|
||||||
// variables to upload
|
// variables to upload
|
||||||
var uploadVars []atlas.TFVar
|
var uploadVars []atlas.TFVar
|
||||||
|
|
||||||
// Now we can combine the vars for upload to atlas and list the variables
|
// first add all the variables we want to send which have been serialized
|
||||||
// we're uploading for the user
|
// from the local context.
|
||||||
for _, sv := range serializedVars {
|
for _, sv := range serializedVars {
|
||||||
if av, ok := atlasVars[sv.Key]; ok {
|
_, inOverwrite := overwriteMap[sv.Key]
|
||||||
// this belongs to Atlas
|
_, inAtlas := atlasVars[sv.Key]
|
||||||
uploadVars = append(uploadVars, av)
|
|
||||||
} else {
|
// We have a variable that's not in atlas, so always send it.
|
||||||
// we're uploading our local version
|
if !inAtlas {
|
||||||
setVars = append(setVars, sv.Key)
|
|
||||||
uploadVars = append(uploadVars, sv)
|
uploadVars = append(uploadVars, sv)
|
||||||
|
setVars = append(setVars, sv.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We're overwriting an atlas variable.
|
||||||
|
// We also want to check that we
|
||||||
|
// don't send the dummy sentry value back to atlas. This could happen
|
||||||
|
// if it's specified as an overwrite on the cli, but we didn't set a
|
||||||
|
// new value.
|
||||||
|
if inAtlas && inOverwrite && sv.Value != atlasVarSentry {
|
||||||
|
uploadVars = append(uploadVars, sv)
|
||||||
|
setVars = append(setVars, sv.Key)
|
||||||
|
|
||||||
|
// remove this value from the atlas vars, because we're going to
|
||||||
|
// send back the remainder regardless.
|
||||||
|
delete(atlasVars, sv.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now send back all the existing atlas vars, inserting any overwrites from the cli.
|
||||||
|
for k, av := range atlasVars {
|
||||||
|
if v, ok := cliVars[k]; ok {
|
||||||
|
av.Value = v
|
||||||
|
setVars = append(setVars, k)
|
||||||
|
}
|
||||||
|
uploadVars = append(uploadVars, av)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(setVars)
|
sort.Strings(setVars)
|
||||||
|
|
|
@ -264,6 +264,97 @@ func TestPush_localOverride(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This tests that the push command will override Atlas variables
|
||||||
|
// even if we don't have it defined locally
|
||||||
|
func TestPush_remoteOverride(t *testing.T) {
|
||||||
|
// Disable test mode so input would be asked and setup the
|
||||||
|
// input reader/writers.
|
||||||
|
test = false
|
||||||
|
defer func() { test = true }()
|
||||||
|
defaultInputReader = bytes.NewBufferString("nope\n")
|
||||||
|
defaultInputWriter = new(bytes.Buffer)
|
||||||
|
|
||||||
|
tmp, cwd := testCwd(t)
|
||||||
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
|
// Create remote state file, this should be pulled
|
||||||
|
conf, srv := testRemoteState(t, testState(), 200)
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
// Persist local remote state
|
||||||
|
s := terraform.NewState()
|
||||||
|
s.Serial = 5
|
||||||
|
s.Remote = conf
|
||||||
|
testStateFileRemote(t, s)
|
||||||
|
|
||||||
|
// Path where the archive will be "uploaded" to
|
||||||
|
archivePath := testTempFile(t)
|
||||||
|
defer os.Remove(archivePath)
|
||||||
|
|
||||||
|
client := &mockPushClient{File: archivePath}
|
||||||
|
// Provided vars should override existing ones
|
||||||
|
client.GetResult = map[string]atlas.TFVar{
|
||||||
|
"remote": atlas.TFVar{
|
||||||
|
Key: "remote",
|
||||||
|
Value: "old",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &PushCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
|
||||||
|
path := testFixturePath("push-tfvars")
|
||||||
|
args := []string{
|
||||||
|
"-var-file", path + "/terraform.tfvars",
|
||||||
|
"-vcs=false",
|
||||||
|
"-overwrite=remote",
|
||||||
|
"-var",
|
||||||
|
"remote=new",
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
|
||||||
|
if code := c.Run(args); code != 0 {
|
||||||
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := testArchiveStr(t, archivePath)
|
||||||
|
expected := []string{
|
||||||
|
".terraform/",
|
||||||
|
".terraform/terraform.tfstate",
|
||||||
|
"main.tf",
|
||||||
|
"terraform.tfvars",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.UpsertOptions.Name != "foo" {
|
||||||
|
t.Fatalf("bad: %#v", client.UpsertOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
// find the "remote" var and make sure we're going to set it
|
||||||
|
for _, tfVar := range client.UpsertOptions.TFVars {
|
||||||
|
if tfVar.Key == "remote" {
|
||||||
|
found = true
|
||||||
|
if tfVar.Value != "new" {
|
||||||
|
t.Log("'remote' variable should be set to 'new'")
|
||||||
|
t.Fatalf("sending instead: %#v", tfVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("'remote' variable not being sent to atlas")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This tests that the push command prefers Atlas variables over
|
// This tests that the push command prefers Atlas variables over
|
||||||
// local ones.
|
// local ones.
|
||||||
func TestPush_preferAtlas(t *testing.T) {
|
func TestPush_preferAtlas(t *testing.T) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
const (
|
const (
|
||||||
// defaultAtlasServer is used when no address is given
|
// defaultAtlasServer is used when no address is given
|
||||||
defaultAtlasServer = "https://atlas.hashicorp.com/"
|
defaultAtlasServer = "https://atlas.hashicorp.com/"
|
||||||
|
atlasTokenHeader = "X-Atlas-Token"
|
||||||
)
|
)
|
||||||
|
|
||||||
func atlasFactory(conf map[string]string) (Client, error) {
|
func atlasFactory(conf map[string]string) (Client, error) {
|
||||||
|
@ -92,6 +93,8 @@ func (c *AtlasClient) Get() (*Payload, error) {
|
||||||
return nil, fmt.Errorf("Failed to make HTTP request: %v", err)
|
return nil, fmt.Errorf("Failed to make HTTP request: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.Header.Set(atlasTokenHeader, c.AccessToken)
|
||||||
|
|
||||||
// Request the url
|
// Request the url
|
||||||
client, err := c.http()
|
client, err := c.http()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -170,6 +173,7 @@ func (c *AtlasClient) Put(state []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the request
|
// Prepare the request
|
||||||
|
req.Header.Set(atlasTokenHeader, c.AccessToken)
|
||||||
req.Header.Set("Content-MD5", b64)
|
req.Header.Set("Content-MD5", b64)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.ContentLength = int64(len(state))
|
req.ContentLength = int64(len(state))
|
||||||
|
@ -204,6 +208,7 @@ func (c *AtlasClient) Delete() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to make HTTP request: %v", err)
|
return fmt.Errorf("Failed to make HTTP request: %v", err)
|
||||||
}
|
}
|
||||||
|
req.Header.Set(atlasTokenHeader, c.AccessToken)
|
||||||
|
|
||||||
// Make the request
|
// Make the request
|
||||||
client, err := c.http()
|
client, err := c.http()
|
||||||
|
@ -249,7 +254,6 @@ func (c *AtlasClient) url() *url.URL {
|
||||||
values := url.Values{}
|
values := url.Values{}
|
||||||
|
|
||||||
values.Add("atlas_run_id", c.RunId)
|
values.Add("atlas_run_id", c.RunId)
|
||||||
values.Add("access_token", c.AccessToken)
|
|
||||||
|
|
||||||
return &url.URL{
|
return &url.URL{
|
||||||
Scheme: c.ServerURL.Scheme,
|
Scheme: c.ServerURL.Scheme,
|
||||||
|
|
|
@ -218,6 +218,17 @@ func (f *fakeAtlas) NoConflictAllowed(b bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeAtlas) handler(resp http.ResponseWriter, req *http.Request) {
|
func (f *fakeAtlas) handler(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
// access tokens should only be sent as a header
|
||||||
|
if req.FormValue("access_token") != "" {
|
||||||
|
http.Error(resp, "access_token in request params", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Header.Get(atlasTokenHeader) == "" {
|
||||||
|
http.Error(resp, "missing access token", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "GET":
|
case "GET":
|
||||||
// Respond with the current stored state.
|
// Respond with the current stored state.
|
||||||
|
|
|
@ -14,6 +14,7 @@ description: |-
|
||||||
```
|
```
|
||||||
resource "aws_elasticsearch_domain" "es" {
|
resource "aws_elasticsearch_domain" "es" {
|
||||||
domain_name = "tf-test"
|
domain_name = "tf-test"
|
||||||
|
elasticsearch_version = "1.5"
|
||||||
advanced_options {
|
advanced_options {
|
||||||
"rest.action.multi.allow_explicit_index" = true
|
"rest.action.multi.allow_explicit_index" = true
|
||||||
}
|
}
|
||||||
|
@ -54,6 +55,7 @@ The following arguments are supported:
|
||||||
* `ebs_options` - (Optional) EBS related options, see below.
|
* `ebs_options` - (Optional) EBS related options, see below.
|
||||||
* `cluster_config` - (Optional) Cluster configuration of the domain, see below.
|
* `cluster_config` - (Optional) Cluster configuration of the domain, see below.
|
||||||
* `snapshot_options` - (Optional) Snapshot related options, see below.
|
* `snapshot_options` - (Optional) Snapshot related options, see below.
|
||||||
|
* `elasticsearch_version` - (Optional) The version of ElasticSearch to deploy. Only valid values are `1.5` and `2.3`. Defaults to `1.5`
|
||||||
* `tags` - (Optional) A mapping of tags to assign to the resource
|
* `tags` - (Optional) A mapping of tags to assign to the resource
|
||||||
|
|
||||||
**ebs_options** supports the following attributes:
|
**ebs_options** supports the following attributes:
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
layout: "aws"
|
||||||
|
page_title: "AWS: aws_vpn_gateway_attachment"
|
||||||
|
sidebar_current: "docs-aws-resource-vpn-gateway-attachment"
|
||||||
|
description: |-
|
||||||
|
Provides a Virtual Private Gateway attachment resource.
|
||||||
|
---
|
||||||
|
|
||||||
|
# aws\_vpn\_gateway\_attachment
|
||||||
|
|
||||||
|
Provides a Virtual Private Gateway attachment resource, allowing for an existing
|
||||||
|
hardware VPN gateway to be attached and/or detached from a VPC.
|
||||||
|
|
||||||
|
-> **Note:** The [`aws_vpn_gateway`](vpn_gateway.html)
|
||||||
|
resource can also automatically attach the Virtual Private Gateway it creates
|
||||||
|
to an existing VPC by setting the [`vpc_id`](vpn_gateway.html#vpc_id) attribute accordingly.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "aws_vpc" "network" {
|
||||||
|
cidr_block = "10.0.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_vpn_gateway" "vpn" {
|
||||||
|
tags {
|
||||||
|
Name = "example-vpn-gateway"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_vpn_gateway_attachment" "vpn_attachment" {
|
||||||
|
vpc_id = "${aws_vpc.network.id}"
|
||||||
|
vpn_gateway_id = "${aws_vpn_gateway.vpn.id}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Virtual Private Cloud](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Introduction.html)
|
||||||
|
and [Virtual Private Gateway](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_VPN.html) user
|
||||||
|
guides for more information.
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `vpc_id` - (Required) The ID of the VPC.
|
||||||
|
* `vpn_gateway_id` - (Required) The ID of the Virtual Private Gateway.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `vpc_id` - The ID of the VPC that Virtual Private Gateway is attached to.
|
||||||
|
* `vpn_gateway_id` - The ID of the Virtual Private Gateway.
|
||||||
|
|
||||||
|
## Import
|
||||||
|
|
||||||
|
This resource does not support importing.
|
|
@ -3,28 +3,49 @@ layout: "vsphere"
|
||||||
page_title: "VMware vSphere: vsphere_file"
|
page_title: "VMware vSphere: vsphere_file"
|
||||||
sidebar_current: "docs-vsphere-resource-file"
|
sidebar_current: "docs-vsphere-resource-file"
|
||||||
description: |-
|
description: |-
|
||||||
Provides a VMware vSphere virtual machine file resource. This can be used to upload files (e.g. vmdk disks) from the Terraform host machine to a remote vSphere.
|
Provides a VMware vSphere virtual machine file resource. This can be used to upload files (e.g. vmdk disks) from the Terraform host machine to a remote vSphere or copy fields withing vSphere.
|
||||||
---
|
---
|
||||||
|
|
||||||
# vsphere\_file
|
# vsphere\_file
|
||||||
|
|
||||||
Provides a VMware vSphere virtual machine file resource. This can be used to upload files (e.g. vmdk disks) from the Terraform host machine to a remote vSphere.
|
Provides a VMware vSphere virtual machine file resource. This can be used to upload files (e.g. vmdk disks) from the Terraform host machine to a remote vSphere. The file resource can also be used to copy files within vSphere. Files can be copied between Datacenters and/or Datastores.
|
||||||
|
|
||||||
## Example Usage
|
Updates to file resources will handle moving a file to a new destination (datacenter and/or datastore and/or destination_file). If any source parameter (e.g. `source_datastore`, `source_datacenter` or `source_file`) are changed, this results in a new resource (new file uploaded or copied and old one being deleted).
|
||||||
|
|
||||||
|
## Example Usages
|
||||||
|
|
||||||
|
**Upload file to vSphere:**
|
||||||
```
|
```
|
||||||
resource "vsphere_file" "ubuntu_disk" {
|
resource "vsphere_file" "ubuntu_disk_upload" {
|
||||||
|
datacenter = "my_datacenter"
|
||||||
datastore = "local"
|
datastore = "local"
|
||||||
source_file = "/home/ubuntu/my_disks/custom_ubuntu.vmdk"
|
source_file = "/home/ubuntu/my_disks/custom_ubuntu.vmdk"
|
||||||
destination_file = "/my_path/disks/custom_ubuntu.vmdk"
|
destination_file = "/my_path/disks/custom_ubuntu.vmdk"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Copy file within vSphere:**
|
||||||
|
```
|
||||||
|
resource "vsphere_file" "ubuntu_disk_copy" {
|
||||||
|
source_datacenter = "my_datacenter"
|
||||||
|
datacenter = "my_datacenter"
|
||||||
|
source_datastore = "local"
|
||||||
|
datastore = "local"
|
||||||
|
source_file = "/my_path/disks/custom_ubuntu.vmdk"
|
||||||
|
destination_file = "/my_path/custom_ubuntu_id.vmdk"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Argument Reference
|
## Argument Reference
|
||||||
|
|
||||||
|
If `source_datacenter` and `source_datastore` are not provided, the file resource will upload the file from Terraform host. If either `source_datacenter` or `source_datastore` are provided, the file resource will copy from within specified locations in vSphere.
|
||||||
|
|
||||||
The following arguments are supported:
|
The following arguments are supported:
|
||||||
|
|
||||||
* `source_file` - (Required) The path to the file on the Terraform host that will be uploaded to vSphere.
|
* `source_file` - (Required) The path to the file being uploaded from the Terraform host to vSphere or copied within vSphere.
|
||||||
* `destination_file` - (Required) The path to where the file should be uploaded to on vSphere.
|
* `destination_file` - (Required) The path to where the file should be uploaded or copied to on vSphere.
|
||||||
* `datacenter` - (Optional) The name of a Datacenter in which the file will be created/uploaded to.
|
* `source_datacenter` - (Optional) The name of a Datacenter in which the file will be copied from.
|
||||||
* `datastore` - (Required) The name of the Datastore in which to create/upload the file to.
|
* `datacenter` - (Optional) The name of a Datacenter in which the file will be uploaded to.
|
||||||
|
* `source_datastore` - (Optional) The name of the Datastore in which file will be copied from.
|
||||||
|
* `datastore` - (Required) The name of the Datastore in which to upload the file to.
|
||||||
|
* `create_directories` - (Optional) Create directories in `destination_file` path parameter if any missing for copy operation. *Note: Directories are not deleted on destroy operation.
|
|
@ -885,6 +885,10 @@
|
||||||
<a href="/docs/providers/aws/r/vpn_gateway.html">aws_vpn_gateway</a>
|
<a href="/docs/providers/aws/r/vpn_gateway.html">aws_vpn_gateway</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-aws-resource-vpn-gateway-attachment") %>>
|
||||||
|
<a href="/docs/providers/aws/r/vpn_gateway_attachment.html">aws_vpn_gateway_attachment</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue