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
|
||||
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
|
||||
provider.
|
||||
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
## 0.7.1 (Unreleased)
|
||||
|
||||
FEATURES:
|
||||
* **New Resource:** `aws_vpn_gateway_attachment` [GH-7870]
|
||||
|
||||
IMPROVEMENTS:
|
||||
* 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:
|
||||
* 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: 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: Fix `aws_s3_bucket` resource `redirect_all_requests_to` action [GH-7883]
|
||||
* provider/google: Use resource specific project when making queries/changes [GH-7029]
|
||||
|
||||
## 0.7.0 (August 2, 2016)
|
||||
|
|
|
@ -282,6 +282,7 @@ func Provider() terraform.ResourceProvider {
|
|||
"aws_vpn_connection": resourceAwsVpnConnection(),
|
||||
"aws_vpn_connection_route": resourceAwsVpnConnectionRoute(),
|
||||
"aws_vpn_gateway": resourceAwsVpnGateway(),
|
||||
"aws_vpn_gateway_attachment": resourceAwsVpnGatewayAttachment(),
|
||||
},
|
||||
ConfigureFunc: providerConfigure,
|
||||
}
|
||||
|
|
|
@ -268,6 +268,7 @@ func resourceAwsCloudFormationStackRead(d *schema.ResourceData, meta interface{}
|
|||
}
|
||||
|
||||
func resourceAwsCloudFormationStackUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
retryTimeout := int64(30)
|
||||
conn := meta.(*AWSClient).cfconn
|
||||
|
||||
input := &cloudformation.UpdateStackInput{
|
||||
|
@ -314,6 +315,13 @@ func resourceAwsCloudFormationStackUpdate(d *schema.ResourceData, meta interface
|
|||
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{
|
||||
Pending: []string{
|
||||
"UPDATE_COMPLETE_CLEANUP_IN_PROGRESS",
|
||||
|
@ -323,7 +331,7 @@ func resourceAwsCloudFormationStackUpdate(d *schema.ResourceData, meta interface
|
|||
"UPDATE_ROLLBACK_COMPLETE",
|
||||
},
|
||||
Target: []string{"UPDATE_COMPLETE"},
|
||||
Timeout: 15 * time.Minute,
|
||||
Timeout: time.Duration(retryTimeout) * time.Minute,
|
||||
MinTimeout: 5 * time.Second,
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
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(),
|
||||
},
|
||||
}
|
||||
|
@ -138,7 +145,8 @@ func resourceAwsElasticSearchDomainCreate(d *schema.ResourceData, meta interface
|
|||
conn := meta.(*AWSClient).esconn
|
||||
|
||||
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 {
|
||||
|
@ -262,8 +270,9 @@ func resourceAwsElasticSearchDomainRead(d *schema.ResourceData, meta interface{}
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Set("domain_id", *ds.DomainId)
|
||||
d.Set("domain_name", *ds.DomainName)
|
||||
d.Set("domain_id", ds.DomainId)
|
||||
d.Set("domain_name", ds.DomainName)
|
||||
d.Set("elasticsearch_version", ds.ElasticsearchVersion)
|
||||
if ds.Endpoint != nil {
|
||||
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{
|
||||
ARN: ds.ARN,
|
||||
|
|
|
@ -7,12 +7,14 @@ import (
|
|||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
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/terraform"
|
||||
)
|
||||
|
||||
func TestAccAWSElasticSearchDomain_basic(t *testing.T) {
|
||||
var domain elasticsearch.ElasticsearchDomainStatus
|
||||
ri := acctest.RandInt()
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
|
@ -20,9 +22,32 @@ func TestAccAWSElasticSearchDomain_basic(t *testing.T) {
|
|||
CheckDestroy: testAccCheckESDomainDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccESDomainConfig,
|
||||
Config: testAccESDomainConfig(ri),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
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) {
|
||||
var domain elasticsearch.ElasticsearchDomainStatus
|
||||
ri := acctest.RandInt()
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
|
@ -38,7 +64,7 @@ func TestAccAWSElasticSearchDomain_complex(t *testing.T) {
|
|||
CheckDestroy: testAccCheckESDomainDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccESDomainConfig_complex,
|
||||
Config: testAccESDomainConfig_complex(ri),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
|
||||
),
|
||||
|
@ -50,6 +76,7 @@ func TestAccAWSElasticSearchDomain_complex(t *testing.T) {
|
|||
func TestAccAWSElasticSearch_tags(t *testing.T) {
|
||||
var domain elasticsearch.ElasticsearchDomainStatus
|
||||
var td elasticsearch.ListTagsOutput
|
||||
ri := acctest.RandInt()
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
|
@ -57,14 +84,14 @@ func TestAccAWSElasticSearch_tags(t *testing.T) {
|
|||
CheckDestroy: testAccCheckAWSELBDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccESDomainConfig,
|
||||
Config: testAccESDomainConfig(ri),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
|
||||
),
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
Config: testAccESDomainConfig_TagUpdate,
|
||||
Config: testAccESDomainConfig_TagUpdate(ri),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
|
||||
testAccLoadESTags(&domain, &td),
|
||||
|
@ -144,26 +171,31 @@ func testAccCheckESDomainDestroy(s *terraform.State) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
const testAccESDomainConfig = `
|
||||
func testAccESDomainConfig(randInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
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" {
|
||||
domain_name = "tf-test-1"
|
||||
domain_name = "tf-test-%d"
|
||||
|
||||
tags {
|
||||
foo = "bar"
|
||||
new = "type"
|
||||
}
|
||||
}
|
||||
`
|
||||
`, randInt)
|
||||
}
|
||||
|
||||
const testAccESDomainConfig_complex = `
|
||||
func testAccESDomainConfig_complex(randInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "aws_elasticsearch_domain" "example" {
|
||||
domain_name = "tf-test-2"
|
||||
domain_name = "tf-test-%d"
|
||||
|
||||
advanced_options {
|
||||
"indices.fielddata.cache.size" = 80
|
||||
|
@ -186,4 +218,14 @@ resource "aws_elasticsearch_domain" "example" {
|
|||
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 {
|
||||
w["redirect_all_requests_to"] = *v.HostName
|
||||
} 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{
|
||||
Host: *v.HostName,
|
||||
Host: host,
|
||||
Path: path,
|
||||
Scheme: *v.Protocol,
|
||||
}).String()
|
||||
}
|
||||
|
@ -947,7 +959,12 @@ func resourceAwsS3BucketWebsitePut(s3conn *s3.S3, d *schema.ResourceData, websit
|
|||
if redirectAllRequestsTo != "" {
|
||||
redirect, err := url.Parse(redirectAllRequestsTo)
|
||||
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 {
|
||||
websiteConfiguration.RedirectAllRequestsTo = &s3.RedirectAllRequestsTo{HostName: aws.String(redirectAllRequestsTo)}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ func resourceAwsVpnGateway() *schema.Resource {
|
|||
"vpc_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"tags": tagsSchema(),
|
||||
|
@ -80,17 +81,18 @@ func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error {
|
|||
}
|
||||
|
||||
vpnGateway := resp.VpnGateways[0]
|
||||
if vpnGateway == nil {
|
||||
if vpnGateway == nil || *vpnGateway.State == "deleted" {
|
||||
// Seems we have lost our VPN gateway
|
||||
d.SetId("")
|
||||
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
|
||||
d.Set("vpc_id", "")
|
||||
} else {
|
||||
d.Set("vpc_id", vpnGateway.VpcAttachments[0].VpcId)
|
||||
d.Set("vpc_id", *vpnAttachment.VpcId)
|
||||
}
|
||||
d.Set("availability_zone", vpnGateway.AvailabilityZone)
|
||||
d.Set("tags", tagsToMap(vpnGateway.Tags))
|
||||
|
@ -301,12 +303,21 @@ func vpnGatewayAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string)
|
|||
}
|
||||
|
||||
vpnGateway := resp.VpnGateways[0]
|
||||
|
||||
if len(vpnGateway.VpcAttachments) == 0 {
|
||||
// No attachments, we're detached
|
||||
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 {
|
||||
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 {
|
||||
return fmt.Errorf("VPN gateway B is not attached")
|
||||
return fmt.Errorf("VPN Gateway B is not attached")
|
||||
}
|
||||
|
||||
id1 := v.VpcAttachments[0].VpcId
|
||||
|
@ -58,20 +58,38 @@ func TestAccAWSVpnGateway_basic(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 {
|
||||
if len(v.VpcAttachments) == 0 {
|
||||
if expectedState != "detached" {
|
||||
return fmt.Errorf("VPN gateway has no VPC attachments")
|
||||
if len(vgw.VpcAttachments) == 0 {
|
||||
return fmt.Errorf("VPN Gateway %q 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 *v.VpcAttachments[0].State != expectedState {
|
||||
return fmt.Errorf("Expected VPC gateway VPC attachment to be in '%s' state, but was not: %s", expectedState, v)
|
||||
if count > 1 {
|
||||
return fmt.Errorf(
|
||||
"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
|
||||
}
|
||||
|
@ -84,27 +102,38 @@ func TestAccAWSVpnGateway_reattach(t *testing.T) {
|
|||
CheckDestroy: testAccCheckVpnGatewayDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccVpnGatewayConfig,
|
||||
Config: testAccCheckVpnGatewayConfigReattach,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckVpcExists("aws_vpc.foo", &vpc1),
|
||||
testAccCheckVpcExists("aws_vpc.bar", &vpc2),
|
||||
testAccCheckVpnGatewayExists(
|
||||
"aws_vpn_gateway.foo", &v),
|
||||
genTestStateFunc("attached"),
|
||||
"aws_vpn_gateway.foo", &vgw1),
|
||||
testAccCheckVpnGatewayExists(
|
||||
"aws_vpn_gateway.bar", &vgw2),
|
||||
testAttachmentFunc(&vgw1, &vpc1),
|
||||
testAttachmentFunc(&vgw2, &vpc2),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccVpnGatewayConfigDetach,
|
||||
Config: testAccCheckVpnGatewayConfigReattachChange,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckVpnGatewayExists(
|
||||
"aws_vpn_gateway.foo", &v),
|
||||
genTestStateFunc("detached"),
|
||||
"aws_vpn_gateway.foo", &vgw1),
|
||||
testAccCheckVpnGatewayExists(
|
||||
"aws_vpn_gateway.bar", &vgw2),
|
||||
testAttachmentFunc(&vgw2, &vpc1),
|
||||
testAttachmentFunc(&vgw1, &vpc2),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccVpnGatewayConfig,
|
||||
Config: testAccCheckVpnGatewayConfigReattach,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckVpnGatewayExists(
|
||||
"aws_vpn_gateway.foo", &v),
|
||||
genTestStateFunc("attached"),
|
||||
"aws_vpn_gateway.foo", &vgw1),
|
||||
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 {
|
||||
_, ok := s.RootModule().Resources[r]
|
||||
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
|
||||
}
|
||||
|
@ -159,7 +188,6 @@ func TestAccAWSVpnGateway_tags(t *testing.T) {
|
|||
testAccCheckTags(&v.Tags, "foo", "bar"),
|
||||
),
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
Config: testAccCheckVpnGatewayConfigTagsUpdate,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
|
@ -198,7 +226,7 @@ func testAccCheckVpnGatewayDestroy(s *terraform.State) error {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -235,7 +263,7 @@ func testAccCheckVpnGatewayExists(n string, ig *ec2.VpnGateway) resource.TestChe
|
|||
return err
|
||||
}
|
||||
if len(resp.VpnGateways) == 0 {
|
||||
return fmt.Errorf("VPNGateway not found")
|
||||
return fmt.Errorf("VPN Gateway not found")
|
||||
}
|
||||
|
||||
*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 = `
|
||||
resource "aws_vpc" "foo" {
|
||||
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) {
|
||||
var wg sync.WaitGroup
|
||||
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{}{})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -132,7 +132,7 @@ func TestTemplateSharedMemoryRace(t *testing.T) {
|
|||
t.Fatalf("bad output: %s", out)
|
||||
}
|
||||
wg.Done()
|
||||
}(&wg, t, i)
|
||||
}(t, i)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package vsphere
|
|||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/vmware/govmomi"
|
||||
|
@ -13,10 +14,14 @@ import (
|
|||
)
|
||||
|
||||
type file struct {
|
||||
datacenter string
|
||||
datastore string
|
||||
sourceFile string
|
||||
destinationFile string
|
||||
sourceDatacenter string
|
||||
datacenter string
|
||||
sourceDatastore string
|
||||
datastore string
|
||||
sourceFile string
|
||||
destinationFile string
|
||||
createDirectories bool
|
||||
copyFile bool
|
||||
}
|
||||
|
||||
func resourceVSphereFile() *schema.Resource {
|
||||
|
@ -30,10 +35,20 @@ func resourceVSphereFile() *schema.Resource {
|
|||
"datacenter": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
"source_datacenter": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"datastore": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"source_datastore": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
|
@ -49,6 +64,11 @@ func resourceVSphereFile() *schema.Resource {
|
|||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"create_directories": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -60,10 +80,20 @@ func resourceVSphereFileCreate(d *schema.ResourceData, meta interface{}) error {
|
|||
|
||||
f := file{}
|
||||
|
||||
if v, ok := d.GetOk("source_datacenter"); ok {
|
||||
f.sourceDatacenter = v.(string)
|
||||
f.copyFile = true
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("datacenter"); ok {
|
||||
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 {
|
||||
f.datastore = v.(string)
|
||||
} else {
|
||||
|
@ -82,6 +112,10 @@ func resourceVSphereFileCreate(d *schema.ResourceData, meta interface{}) error {
|
|||
return fmt.Errorf("destination_file argument is required")
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("create_directories"); ok {
|
||||
f.createDirectories = v.(bool)
|
||||
}
|
||||
|
||||
err := createFile(client, &f)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -108,16 +142,53 @@ func createFile(client *govmomi.Client, f *file) error {
|
|||
return fmt.Errorf("error %s", err)
|
||||
}
|
||||
|
||||
dsurl, err := ds.URL(context.TODO(), dc, f.destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
if f.copyFile {
|
||||
// Copying file from withing vSphere
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -126,10 +197,18 @@ func resourceVSphereFileRead(d *schema.ResourceData, meta interface{}) error {
|
|||
log.Printf("[DEBUG] reading file: %#v", d)
|
||||
f := file{}
|
||||
|
||||
if v, ok := d.GetOk("source_datacenter"); ok {
|
||||
f.sourceDatacenter = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("datacenter"); ok {
|
||||
f.datacenter = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("source_datastore"); ok {
|
||||
f.sourceDatastore = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("datastore"); ok {
|
||||
f.datastore = v.(string)
|
||||
} else {
|
||||
|
@ -179,57 +258,69 @@ func resourceVSphereFileRead(d *schema.ResourceData, meta interface{}) error {
|
|||
func resourceVSphereFileUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
|
||||
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 {
|
||||
f.datacenter = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("datastore"); ok {
|
||||
f.datastore = v.(string)
|
||||
if d.HasChange("destination_file") || d.HasChange("datacenter") || d.HasChange("datastore") {
|
||||
// File needs to be moved, get old and new destination changes
|
||||
var oldDataceneter, newDatacenter, oldDatastore, newDatastore, oldDestinationFile, newDestinationFile string
|
||||
if d.HasChange("datacenter") {
|
||||
tmpOldDataceneter, tmpNewDatacenter := d.GetChange("datacenter")
|
||||
oldDataceneter = tmpOldDataceneter.(string)
|
||||
newDatacenter = tmpNewDatacenter.(string)
|
||||
} else {
|
||||
return fmt.Errorf("datastore argument is required")
|
||||
if v, ok := d.GetOk("datacenter"); ok {
|
||||
oldDataceneter = v.(string)
|
||||
newDatacenter = oldDataceneter
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("source_file"); ok {
|
||||
f.sourceFile = v.(string)
|
||||
if d.HasChange("datastore") {
|
||||
tmpOldDatastore, tmpNewDatastore := d.GetChange("datastore")
|
||||
oldDatastore = tmpOldDatastore.(string)
|
||||
newDatastore = tmpNewDatastore.(string)
|
||||
} else {
|
||||
return fmt.Errorf("source_file argument is required")
|
||||
oldDatastore = d.Get("datastore").(string)
|
||||
newDatastore = oldDatastore
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("destination_file"); ok {
|
||||
f.destinationFile = v.(string)
|
||||
if d.HasChange("destination_file") {
|
||||
tmpOldDestinationFile, tmpNewDestinationFile := d.GetChange("destination_file")
|
||||
oldDestinationFile = tmpOldDestinationFile.(string)
|
||||
newDestinationFile = tmpNewDestinationFile.(string)
|
||||
} 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)
|
||||
dc, err := getDatacenter(client, f.datacenter)
|
||||
dcOld, err := getDatacenter(client, oldDataceneter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dcNew, err := getDatacenter(client, newDatacenter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finder := find.NewFinder(client.Client, true)
|
||||
finder = finder.SetDatacenter(dc)
|
||||
|
||||
ds, err := getDatastore(finder, f.datastore)
|
||||
finder = finder.SetDatacenter(dcOld)
|
||||
dsOld, err := getDatastore(finder, oldDatastore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error %s", err)
|
||||
}
|
||||
finder = finder.SetDatacenter(dcNew)
|
||||
dsNew, err := getDatastore(finder, newDatastore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error %s", err)
|
||||
}
|
||||
|
||||
// Move file between old/new dataceter, datastore and path (destination_file)
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = task.WaitForResult(context.TODO(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Basic file creation
|
||||
// Basic file creation (upload to vSphere)
|
||||
func TestAccVSphereFile_basic(t *testing.T) {
|
||||
testVmdkFileData := []byte("# Disk DescriptorFile\n")
|
||||
testVmdkFile := "/tmp/tf_test.vmdk"
|
||||
|
@ -55,6 +55,59 @@ func TestAccVSphereFile_basic(t *testing.T) {
|
|||
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)
|
||||
func TestAccVSphereFile_renamePostCreation(t *testing.T) {
|
||||
testVmdkFileData := []byte("# Disk DescriptorFile\n")
|
||||
|
@ -67,7 +120,7 @@ func TestAccVSphereFile_renamePostCreation(t *testing.T) {
|
|||
|
||||
datacenter := os.Getenv("VSPHERE_DATACENTER")
|
||||
datastore := os.Getenv("VSPHERE_DATASTORE")
|
||||
testMethod := "basic"
|
||||
testMethod := "create_upgrade"
|
||||
resourceName := "vsphere_file." + testMethod
|
||||
destinationFile := "tf_test_file.vmdk"
|
||||
destinationFileMoved := "tf_test_file_moved.vmdk"
|
||||
|
@ -76,7 +129,7 @@ func TestAccVSphereFile_renamePostCreation(t *testing.T) {
|
|||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckVSphereFolderDestroy,
|
||||
CheckDestroy: testAccCheckVSphereFileDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fmt.Sprintf(
|
||||
|
@ -113,6 +166,84 @@ func TestAccVSphereFile_renamePostCreation(t *testing.T) {
|
|||
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 {
|
||||
client := testAccProvider.Meta().(*govmomi.Client)
|
||||
finder := find.NewFinder(client.Client, true)
|
||||
|
@ -201,3 +332,19 @@ resource "vsphere_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{}{}
|
||||
}
|
||||
|
||||
// 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
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
|
@ -145,19 +161,14 @@ func (c *PushCommand) Run(args []string) int {
|
|||
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
|
||||
// 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
|
||||
// we're just going to set it as the raw string value.
|
||||
// the user for input.
|
||||
ctxVars := ctx.Variables()
|
||||
for k, av := range atlasVars {
|
||||
atlasVarSentry := "ATLAS_78AC153CA649EAA44815DAD6CBD4816D"
|
||||
for k, _ := range atlasVars {
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// variables to upload
|
||||
var uploadVars []atlas.TFVar
|
||||
|
||||
// Now we can combine the vars for upload to atlas and list the variables
|
||||
// we're uploading for the user
|
||||
// first add all the variables we want to send which have been serialized
|
||||
// from the local context.
|
||||
for _, sv := range serializedVars {
|
||||
if av, ok := atlasVars[sv.Key]; ok {
|
||||
// this belongs to Atlas
|
||||
uploadVars = append(uploadVars, av)
|
||||
} else {
|
||||
// we're uploading our local version
|
||||
setVars = append(setVars, sv.Key)
|
||||
_, inOverwrite := overwriteMap[sv.Key]
|
||||
_, inAtlas := atlasVars[sv.Key]
|
||||
|
||||
// We have a variable that's not in atlas, so always send it.
|
||||
if !inAtlas {
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
// local ones.
|
||||
func TestPush_preferAtlas(t *testing.T) {
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
const (
|
||||
// defaultAtlasServer is used when no address is given
|
||||
defaultAtlasServer = "https://atlas.hashicorp.com/"
|
||||
atlasTokenHeader = "X-Atlas-Token"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
req.Header.Set(atlasTokenHeader, c.AccessToken)
|
||||
|
||||
// Request the url
|
||||
client, err := c.http()
|
||||
if err != nil {
|
||||
|
@ -170,6 +173,7 @@ func (c *AtlasClient) Put(state []byte) error {
|
|||
}
|
||||
|
||||
// Prepare the request
|
||||
req.Header.Set(atlasTokenHeader, c.AccessToken)
|
||||
req.Header.Set("Content-MD5", b64)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.ContentLength = int64(len(state))
|
||||
|
@ -204,6 +208,7 @@ func (c *AtlasClient) Delete() error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("Failed to make HTTP request: %v", err)
|
||||
}
|
||||
req.Header.Set(atlasTokenHeader, c.AccessToken)
|
||||
|
||||
// Make the request
|
||||
client, err := c.http()
|
||||
|
@ -249,7 +254,6 @@ func (c *AtlasClient) url() *url.URL {
|
|||
values := url.Values{}
|
||||
|
||||
values.Add("atlas_run_id", c.RunId)
|
||||
values.Add("access_token", c.AccessToken)
|
||||
|
||||
return &url.URL{
|
||||
Scheme: c.ServerURL.Scheme,
|
||||
|
|
|
@ -218,6 +218,17 @@ func (f *fakeAtlas) NoConflictAllowed(b bool) {
|
|||
}
|
||||
|
||||
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 {
|
||||
case "GET":
|
||||
// Respond with the current stored state.
|
||||
|
|
|
@ -14,6 +14,7 @@ description: |-
|
|||
```
|
||||
resource "aws_elasticsearch_domain" "es" {
|
||||
domain_name = "tf-test"
|
||||
elasticsearch_version = "1.5"
|
||||
advanced_options {
|
||||
"rest.action.multi.allow_explicit_index" = true
|
||||
}
|
||||
|
@ -54,6 +55,7 @@ The following arguments are supported:
|
|||
* `ebs_options` - (Optional) EBS related options, see below.
|
||||
* `cluster_config` - (Optional) Cluster configuration of the domain, 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
|
||||
|
||||
**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"
|
||||
sidebar_current: "docs-vsphere-resource-file"
|
||||
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
|
||||
|
||||
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"
|
||||
source_file = "/home/ubuntu/my_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
|
||||
|
||||
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:
|
||||
|
||||
* `source_file` - (Required) The path to the file on the Terraform host that will be uploaded to vSphere.
|
||||
* `destination_file` - (Required) The path to where the file should be uploaded to on vSphere.
|
||||
* `datacenter` - (Optional) The name of a Datacenter in which the file will be created/uploaded to.
|
||||
* `datastore` - (Required) The name of the Datastore in which to create/upload the file to.
|
||||
* `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 or copied to on vSphere.
|
||||
* `source_datacenter` - (Optional) The name of a Datacenter in which the file will be copied from.
|
||||
* `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>
|
||||
</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>
|
||||
</li>
|
||||
|
||||
|
|
Loading…
Reference in New Issue