Merge branch 'master' of github.com:hashicorp/terraform

This commit is contained in:
stack72 2016-08-07 19:29:20 +12:00
commit a5d4ec65b5
No known key found for this signature in database
GPG Key ID: 8619A619B085CB16
22 changed files with 1114 additions and 132 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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,
}

View File

@ -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{

View File

@ -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,

View File

@ -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)
}

View File

@ -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)}
}

View File

@ -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")}
}

View File

@ -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)))
}

View File

@ -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}"
}
`

View File

@ -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}"
}
`

View File

@ -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()
}

View File

@ -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

View File

@ -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"
}
`

View File

@ -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)

View File

@ -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) {

View File

@ -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,

View File

@ -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.

View File

@ -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:

View File

@ -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.

View File

@ -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.

View File

@ -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>