diff --git a/CHANGELOG.md b/CHANGELOG.md index 932980236..a548f290e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,31 +1,30 @@ -## 0.6.1 (Unreleased) +## 0.6.2 (Unreleased) + +IMPROVEMENTS: + + * core: Add resource IDs to errors coming from `apply`/`refresh` [GH-2815] + * provider/aws: Validate credentials before walking the graph [GH-2730] + * provider/aws: Added website_domain for S3 buckets [GH-2210] + * provider/aws: ELB names are now optional, and generated by Terraform if omitted [GH-2571] + * provider/aws: Downcase RDS engine names to prevent continuous diffs [GH-2745] + * provider/aws: Added `source_dest_check` attribute to the aws_network_interface [GH-2741] + * provider/aws: Clean up externally removed Launch Configurations [GH-2806] + +BUG FIXES: + + * core: Prevent error duplication in `apply` [GH-2815] + * provider/aws: Fix issue with toggling monitoring in AWS Instances [GH-2794] + * provider/aws: Fix issue with Spot Instance Requests and cancellation [GH-2805] + * provider/aws: Fixx issue when unable to find a Root Block Device name of an Instance Backed + AMI [GH-2646] + +## 0.6.1 (July 20, 2015) FEATURES: * **New resource: `google_container_cluster`** [GH-2357] * **New resource: `aws_vpc_endpoint`** [GH-2695] -BUG FIXES: - - * core: don't prompt for variables with defaults [GH-2613] - * core: Return correct number of planned updates [GH-2620] - * core: Fix "provider not found" error that can occur while running - a destroy plan with grandchildren modules [GH-2755] - * connection/ssh: fix issue on machines with an SSH Agent available - preventing `key_file` from being read without explicitly - setting `agent = false` [GH-2615] - * provider/aws: Allow uppercase characters in `aws_elb.name` [GH-2580] - * provider/aws: Allow underscores in `aws_db_subnet_group.name` (undocumented by AWS) [GH-2604] - * provider/aws: Allow dots in `aws_db_subnet_group.name` (undocumented by AWS) [GH-2665] - * provider/aws: Fix issue with pending Spot Instance requests [GH-2640] - * provider/aws: Fix issue in AWS Classic environment with referencing external - Security Groups [GH-2644] - * provider/aws: Bump internet gateway detach timeout [GH-2669] - * provider/aws: `ecs_cluster` rename (recreation) and deletion is handled correctly [GH-2698] - * provider/aws: `aws_route_table` ignores routes generated for VPC endpoints [GH-2695] - * provider/aws: Fix issue with Launch Configurations and enable_monitoring [GH-2735] - * provider/openstack: allow empty api_key and endpoint_type [GH-2626] - IMPROVEMENTS: * connection/ssh: Print SSH bastion host details to output [GH-2684] @@ -36,6 +35,35 @@ IMPROVEMENTS: Auto Scaling Groups updates [GH-2724] * provider/google: Add metadata_startup_script to google_compute_instance [GH-2375] +BUG FIXES: + + * core: don't prompt for variables with defaults [GH-2613] + * core: Return correct number of planned updates [GH-2620] + * core: Fix "provider not found" error that can occur while running + a destroy plan with grandchildren modules [GH-2755] + * core: Fix UUID showing up in diff for computed splat (`foo.*.bar`) + variables. [GH-2788] + * core: Orphan modules that contain no resources (only other modules) + are properly destroyed up to arbitrary depth [GH-2786] + * core: Fix "attribute not available" during destroy plans in + cases where the parameter is passed between modules [GH-2775] + * connection/ssh: fix issue on machines with an SSH Agent available + preventing `key_file` from being read without explicitly + setting `agent = false` [GH-2615] + * provider/aws: Allow uppercase characters in `aws_elb.name` [GH-2580] + * provider/aws: Allow underscores in `aws_db_subnet_group.name` (undocumented by AWS) [GH-2604] + * provider/aws: Allow dots in `aws_db_subnet_group.name` (undocumented by AWS) [GH-2665] + * provider/aws: Fix issue with pending Spot Instance requests [GH-2640] + * provider/aws: Fix issue in AWS Classic environment with referencing external + Security Groups [GH-2644] + * provider/aws: Bump internet gateway detach timeout [GH-2669] + * provider/aws: Fix issue with detecting differences in DB Parameters [GH-2728] + * provider/aws: `ecs_cluster` rename (recreation) and deletion is handled correctly [GH-2698] + * provider/aws: `aws_route_table` ignores routes generated for VPC endpoints [GH-2695] + * provider/aws: Fix issue with Launch Configurations and enable_monitoring [GH-2735] + * provider/openstack: allow empty api_key and endpoint_type [GH-2626] + * provisioner/chef: Fix permission denied error with ohai hints [GH-2781] + ## 0.6.0 (June 30, 2015) BACKWARDS INCOMPATIBILITIES: diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index bc465394c..f58938285 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform/helper/multierror" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/aws/aws-sdk-go/service/cloudwatch" @@ -85,6 +86,14 @@ func (c *Config) Client() (interface{}, error) { MaxRetries: c.MaxRetries, } + log.Println("[INFO] Initializing IAM Connection") + client.iamconn = iam.New(awsConfig) + + err := c.ValidateCredentials(client.iamconn) + if err != nil { + errs = append(errs, err) + } + log.Println("[INFO] Initializing DynamoDB connection") client.dynamodbconn = dynamodb.New(awsConfig) @@ -103,15 +112,12 @@ func (c *Config) Client() (interface{}, error) { log.Println("[INFO] Initializing RDS Connection") client.rdsconn = rds.New(awsConfig) - log.Println("[INFO] Initializing IAM Connection") - client.iamconn = iam.New(awsConfig) - log.Println("[INFO] Initializing Kinesis Connection") client.kinesisconn = kinesis.New(awsConfig) - err := c.ValidateAccountId(client.iamconn) - if err != nil { - errs = append(errs, err) + authErr := c.ValidateAccountId(client.iamconn) + if authErr != nil { + errs = append(errs, authErr) } log.Println("[INFO] Initializing AutoScaling connection") @@ -165,6 +171,19 @@ func (c *Config) ValidateRegion() error { return fmt.Errorf("Not a valid region: %s", c.Region) } +// Validate credentials early and fail before we do any graph walking +func (c *Config) ValidateCredentials(iamconn *iam.IAM) error { + _, err := iamconn.GetUser(nil) + + if awsErr, ok := err.(awserr.Error); ok { + if awsErr.Code() == "SignatureDoesNotMatch" { + return fmt.Errorf("Failed authenticating with AWS: please verify credentials") + } + } + + return err +} + // ValidateAccountId returns a context-specific error if the configured account // id is explicitly forbidden or not authorised; and nil if it is authorised. func (c *Config) ValidateAccountId(iamconn *iam.IAM) error { diff --git a/builtin/providers/aws/resource_aws_customer_gateway_test.go b/builtin/providers/aws/resource_aws_customer_gateway_test.go index 33e370946..9e3daec6d 100644 --- a/builtin/providers/aws/resource_aws_customer_gateway_test.go +++ b/builtin/providers/aws/resource_aws_customer_gateway_test.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccCustomerGateway_basic(t *testing.T) { +func TestAccAWSCustomerGateway_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index 47a98b73d..6ca1916b5 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -46,6 +46,10 @@ func resourceAwsDbInstance() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, + StateFunc: func(v interface{}) string { + value := v.(string) + return strings.ToLower(value) + }, }, "engine_version": &schema.Schema{ diff --git a/builtin/providers/aws/resource_aws_db_instance_test.go b/builtin/providers/aws/resource_aws_db_instance_test.go index ef1931b03..b60a01da9 100644 --- a/builtin/providers/aws/resource_aws_db_instance_test.go +++ b/builtin/providers/aws/resource_aws_db_instance_test.go @@ -176,7 +176,7 @@ resource "aws_db_instance" "bar" { identifier = "foobarbaz-test-terraform-%d" allocated_storage = 10 - engine = "mysql" + engine = "MySQL" engine_version = "5.6.21" instance_class = "db.t1.micro" name = "baz" diff --git a/builtin/providers/aws/resource_aws_db_parameter_group.go b/builtin/providers/aws/resource_aws_db_parameter_group.go index 9489394dc..b274605a7 100644 --- a/builtin/providers/aws/resource_aws_db_parameter_group.go +++ b/builtin/providers/aws/resource_aws_db_parameter_group.go @@ -166,7 +166,7 @@ func resourceAwsDbParameterGroupUpdate(d *schema.ResourceData, meta interface{}) Parameters: parameters, } - log.Printf("[DEBUG] Modify DB Parameter Group: %#v", modifyOpts) + log.Printf("[DEBUG] Modify DB Parameter Group: %s", modifyOpts) _, err = rdsconn.ModifyDBParameterGroup(&modifyOpts) if err != nil { return fmt.Errorf("Error modifying DB Parameter Group: %s", err) diff --git a/builtin/providers/aws/resource_aws_elb.go b/builtin/providers/aws/resource_aws_elb.go index 07e7423a5..8819a14d0 100644 --- a/builtin/providers/aws/resource_aws_elb.go +++ b/builtin/providers/aws/resource_aws_elb.go @@ -11,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elb" "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -24,7 +25,8 @@ func resourceAwsElb() *schema.Resource { Schema: map[string]*schema.Schema{ "name": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, ForceNew: true, ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { value := v.(string) @@ -211,10 +213,18 @@ func resourceAwsElbCreate(d *schema.ResourceData, meta interface{}) error { return err } + var elbName string + if v, ok := d.GetOk("name"); ok { + elbName = v.(string) + } else { + elbName = resource.PrefixedUniqueId("tf-lb-") + d.Set("name", elbName) + } + tags := tagsFromMapELB(d.Get("tags").(map[string]interface{})) // Provision the elb elbOpts := &elb.CreateLoadBalancerInput{ - LoadBalancerName: aws.String(d.Get("name").(string)), + LoadBalancerName: aws.String(elbName), Listeners: listeners, Tags: tags, } @@ -241,7 +251,7 @@ func resourceAwsElbCreate(d *schema.ResourceData, meta interface{}) error { } // Assign the elb's unique identifier for use later - d.SetId(d.Get("name").(string)) + d.SetId(elbName) log.Printf("[INFO] ELB ID: %s", d.Id()) // Enable partial mode and record what we set diff --git a/builtin/providers/aws/resource_aws_flow_log_test.go b/builtin/providers/aws/resource_aws_flow_log_test.go index b938a6fea..a7dd9ad32 100644 --- a/builtin/providers/aws/resource_aws_flow_log_test.go +++ b/builtin/providers/aws/resource_aws_flow_log_test.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccFlowLog_basic(t *testing.T) { +func TestAccAWSFlowLog_basic(t *testing.T) { var flowLog ec2.FlowLog lgn := os.Getenv("LOG_GROUP_NAME") @@ -31,7 +31,7 @@ func TestAccFlowLog_basic(t *testing.T) { }) } -func TestAccFlowLog_subnet(t *testing.T) { +func TestAccAWSFlowLog_subnet(t *testing.T) { var flowLog ec2.FlowLog lgn := os.Getenv("LOG_GROUP_NAME") diff --git a/builtin/providers/aws/resource_aws_iam_server_certificate_test.go b/builtin/providers/aws/resource_aws_iam_server_certificate_test.go index 3165416fc..5982a8b6c 100644 --- a/builtin/providers/aws/resource_aws_iam_server_certificate_test.go +++ b/builtin/providers/aws/resource_aws_iam_server_certificate_test.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccIAMServerCertificate_basic(t *testing.T) { +func TestAccAWSIAMServerCertificate_basic(t *testing.T) { var cert iam.ServerCertificate resource.Test(t, resource.TestCase{ diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index 9a4306acb..d6ec35af1 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -579,6 +579,24 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error { } } + if d.HasChange("monitoring") { + var mErr error + if d.Get("monitoring").(bool) { + log.Printf("[DEBUG] Enabling monitoring for Instance (%s)", d.Id()) + _, mErr = conn.MonitorInstances(&ec2.MonitorInstancesInput{ + InstanceIDs: []*string{aws.String(d.Id())}, + }) + } else { + log.Printf("[DEBUG] Disabling monitoring for Instance (%s)", d.Id()) + _, mErr = conn.UnmonitorInstances(&ec2.UnmonitorInstancesInput{ + InstanceIDs: []*string{aws.String(d.Id())}, + }) + } + if mErr != nil { + return fmt.Errorf("[WARN] Error updating Instance monitoring: %s", mErr) + } + } + // TODO(mitchellh): wait for the attributes we modified to // persist the change... @@ -760,6 +778,10 @@ func fetchRootDeviceName(ami string, conn *ec2.EC2) (*string, error) { rootDeviceName = image.BlockDeviceMappings[0].DeviceName } + if rootDeviceName == nil { + return nil, fmt.Errorf("[WARN] Error finding Root Device Name for AMI (%s)", ami) + } + return rootDeviceName, nil } diff --git a/builtin/providers/aws/resource_aws_kinesis_stream_test.go b/builtin/providers/aws/resource_aws_kinesis_stream_test.go index 01d14d3d0..814ea92d2 100644 --- a/builtin/providers/aws/resource_aws_kinesis_stream_test.go +++ b/builtin/providers/aws/resource_aws_kinesis_stream_test.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccKinesisStream_basic(t *testing.T) { +func TestAccAWSKinesisStream_basic(t *testing.T) { var stream kinesis.StreamDescription resource.Test(t, resource.TestCase{ diff --git a/builtin/providers/aws/resource_aws_lambda_function_test.go b/builtin/providers/aws/resource_aws_lambda_function_test.go index d85bd7e8d..12edb99e0 100644 --- a/builtin/providers/aws/resource_aws_lambda_function_test.go +++ b/builtin/providers/aws/resource_aws_lambda_function_test.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccAWSLambdaFunction_normal(t *testing.T) { +func TestAccAWSLambdaFunction_basic(t *testing.T) { var conf lambda.GetFunctionOutput resource.Test(t, resource.TestCase{ diff --git a/builtin/providers/aws/resource_aws_launch_configuration.go b/builtin/providers/aws/resource_aws_launch_configuration.go index ab6350b38..85630aa5a 100644 --- a/builtin/providers/aws/resource_aws_launch_configuration.go +++ b/builtin/providers/aws/resource_aws_launch_configuration.go @@ -480,7 +480,8 @@ func resourceAwsLaunchConfigurationDelete(d *schema.ResourceData, meta interface }) if err != nil { autoscalingerr, ok := err.(awserr.Error) - if ok && autoscalingerr.Code() == "InvalidConfiguration.NotFound" { + if ok && (autoscalingerr.Code() == "InvalidConfiguration.NotFound" || autoscalingerr.Code() == "ValidationError") { + log.Printf("[DEBUG] Launch configuration (%s) not found", d.Id()) return nil } diff --git a/builtin/providers/aws/resource_aws_network_interface.go b/builtin/providers/aws/resource_aws_network_interface.go index aee50f0aa..2cf7b13c3 100644 --- a/builtin/providers/aws/resource_aws_network_interface.go +++ b/builtin/providers/aws/resource_aws_network_interface.go @@ -46,6 +46,12 @@ func resourceAwsNetworkInterface() *schema.Resource { Set: schema.HashString, }, + "source_dest_check": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "attachment": &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -127,6 +133,7 @@ func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) e d.Set("subnet_id", eni.SubnetID) d.Set("private_ips", flattenNetworkInterfacesPrivateIPAddesses(eni.PrivateIPAddresses)) d.Set("security_groups", flattenGroupIdentifiers(eni.Groups)) + d.Set("source_dest_check", eni.SourceDestCheck) // Tags d.Set("tags", tagsToMap(eni.TagSet)) @@ -221,6 +228,18 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) d.SetPartial("attachment") } + request := &ec2.ModifyNetworkInterfaceAttributeInput{ + NetworkInterfaceID: aws.String(d.Id()), + SourceDestCheck: &ec2.AttributeBooleanValue{Value: aws.Boolean(d.Get("source_dest_check").(bool))}, + } + + _, err := conn.ModifyNetworkInterfaceAttribute(request) + if err != nil { + return fmt.Errorf("Failure updating ENI: %s", err) + } + + d.SetPartial("source_dest_check") + if d.HasChange("security_groups") { request := &ec2.ModifyNetworkInterfaceAttributeInput{ NetworkInterfaceID: aws.String(d.Id()), diff --git a/builtin/providers/aws/resource_aws_network_interface_test.go b/builtin/providers/aws/resource_aws_network_interface_test.go index f83698abf..a444a0136 100644 --- a/builtin/providers/aws/resource_aws_network_interface_test.go +++ b/builtin/providers/aws/resource_aws_network_interface_test.go @@ -57,6 +57,26 @@ func TestAccAWSENI_attached(t *testing.T) { }) } +func TestAccAWSENI_sourceDestCheck(t *testing.T) { + var conf ec2.NetworkInterface + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSENIDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSENIConfigWithSourceDestCheck, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSENIExists("aws_network_interface.bar", &conf), + resource.TestCheckResourceAttr( + "aws_network_interface.bar", "source_dest_check", "false"), + ), + }, + }, + }) +} + func testAccCheckAWSENIExists(n string, res *ec2.NetworkInterface) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -108,6 +128,10 @@ func testAccCheckAWSENIAttributes(conf *ec2.NetworkInterface) resource.TestCheck return fmt.Errorf("expected private ip to be 172.16.10.100, but was %s", *conf.PrivateIPAddress) } + if *conf.SourceDestCheck != true { + return fmt.Errorf("expected source_dest_check to be true, but was %t", *conf.SourceDestCheck) + } + if len(conf.TagSet) == 0 { return fmt.Errorf("expected tags") } @@ -201,6 +225,24 @@ resource "aws_network_interface" "bar" { } ` +const testAccAWSENIConfigWithSourceDestCheck = ` +resource "aws_vpc" "foo" { + cidr_block = "172.16.0.0/16" +} + +resource "aws_subnet" "foo" { + vpc_id = "${aws_vpc.foo.id}" + cidr_block = "172.16.10.0/24" + availability_zone = "us-west-2a" +} + +resource "aws_network_interface" "bar" { + subnet_id = "${aws_subnet.foo.id}" + source_dest_check = false + private_ips = ["172.16.10.100"] +} +` + const testAccAWSENIConfigWithAttachment = ` resource "aws_vpc" "foo" { cidr_block = "172.16.0.0/16" diff --git a/builtin/providers/aws/resource_aws_route53_delegation_set_test.go b/builtin/providers/aws/resource_aws_route53_delegation_set_test.go index 8de0e1b83..f9bf3cd87 100644 --- a/builtin/providers/aws/resource_aws_route53_delegation_set_test.go +++ b/builtin/providers/aws/resource_aws_route53_delegation_set_test.go @@ -13,7 +13,7 @@ import ( "github.com/aws/aws-sdk-go/service/route53" ) -func TestAccRoute53DelegationSet_basic(t *testing.T) { +func TestAccAWSRoute53DelegationSet_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -29,7 +29,7 @@ func TestAccRoute53DelegationSet_basic(t *testing.T) { }) } -func TestAccRoute53DelegationSet_withZones(t *testing.T) { +func TestAccAWSRoute53DelegationSet_withZones(t *testing.T) { var zone route53.GetHostedZoneOutput resource.Test(t, resource.TestCase{ diff --git a/builtin/providers/aws/resource_aws_route53_health_check_test.go b/builtin/providers/aws/resource_aws_route53_health_check_test.go index 32286cc41..e27de56d4 100644 --- a/builtin/providers/aws/resource_aws_route53_health_check_test.go +++ b/builtin/providers/aws/resource_aws_route53_health_check_test.go @@ -10,7 +10,7 @@ import ( "github.com/aws/aws-sdk-go/service/route53" ) -func TestAccRoute53HealthCheck_basic(t *testing.T) { +func TestAccAWSRoute53HealthCheck_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -34,7 +34,7 @@ func TestAccRoute53HealthCheck_basic(t *testing.T) { }) } -func TestAccRoute53HealthCheck_IpConfig(t *testing.T) { +func TestAccAWSRoute53HealthCheck_IpConfig(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, diff --git a/builtin/providers/aws/resource_aws_route53_record_test.go b/builtin/providers/aws/resource_aws_route53_record_test.go index eaa6abb7a..0435dffb1 100644 --- a/builtin/providers/aws/resource_aws_route53_record_test.go +++ b/builtin/providers/aws/resource_aws_route53_record_test.go @@ -50,7 +50,7 @@ func TestExpandRecordName(t *testing.T) { } } -func TestAccRoute53Record_basic(t *testing.T) { +func TestAccAWSRoute53Record_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -66,7 +66,7 @@ func TestAccRoute53Record_basic(t *testing.T) { }) } -func TestAccRoute53Record_txtSupport(t *testing.T) { +func TestAccAWSRoute53Record_txtSupport(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -82,7 +82,7 @@ func TestAccRoute53Record_txtSupport(t *testing.T) { }) } -func TestAccRoute53Record_generatesSuffix(t *testing.T) { +func TestAccAWSRoute53Record_generatesSuffix(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -98,7 +98,7 @@ func TestAccRoute53Record_generatesSuffix(t *testing.T) { }) } -func TestAccRoute53Record_wildcard(t *testing.T) { +func TestAccAWSRoute53Record_wildcard(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -122,7 +122,7 @@ func TestAccRoute53Record_wildcard(t *testing.T) { }) } -func TestAccRoute53Record_weighted(t *testing.T) { +func TestAccAWSRoute53Record_weighted(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -139,7 +139,7 @@ func TestAccRoute53Record_weighted(t *testing.T) { }) } -func TestAccRoute53Record_alias(t *testing.T) { +func TestAccAWSRoute53Record_alias(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -155,7 +155,23 @@ func TestAccRoute53Record_alias(t *testing.T) { }) } -func TestAccRoute53Record_weighted_alias(t *testing.T) { +func TestAccAWSRoute53Record_s3_alias(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRoute53RecordDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccRoute53S3AliasRecord, + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53RecordExists("aws_route53_record.alias"), + ), + }, + }, + }) +} + +func TestAccAWSRoute53Record_weighted_alias(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -182,7 +198,7 @@ func TestAccRoute53Record_weighted_alias(t *testing.T) { }) } -func TestAccRoute53Record_TypeChange(t *testing.T) { +func TestAccAWSRoute53Record_TypeChange(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -449,6 +465,32 @@ resource "aws_route53_record" "alias" { } ` +const testAccRoute53S3AliasRecord = ` +resource "aws_route53_zone" "main" { + name = "notexample.com" +} + +resource "aws_s3_bucket" "website" { + bucket = "website.notexample.com" + acl = "public-read" + website { + index_document = "index.html" + } +} + +resource "aws_route53_record" "alias" { + zone_id = "${aws_route53_zone.main.zone_id}" + name = "www" + type = "A" + + alias { + zone_id = "${aws_s3_bucket.website.hosted_zone_id}" + name = "${aws_s3_bucket.website.website_domain}" + evaluate_target_health = true + } +} +` + const testAccRoute53WeightedElbAliasRecord = ` resource "aws_route53_zone" "main" { name = "notexample.com" diff --git a/builtin/providers/aws/resource_aws_route53_zone_association_test.go b/builtin/providers/aws/resource_aws_route53_zone_association_test.go index e929d4d31..b04b3bb51 100644 --- a/builtin/providers/aws/resource_aws_route53_zone_association_test.go +++ b/builtin/providers/aws/resource_aws_route53_zone_association_test.go @@ -12,7 +12,7 @@ import ( "github.com/aws/aws-sdk-go/service/route53" ) -func TestAccRoute53ZoneAssociation_basic(t *testing.T) { +func TestAccAWSRoute53ZoneAssociation_basic(t *testing.T) { var zone route53.HostedZone resource.Test(t, resource.TestCase{ @@ -30,7 +30,7 @@ func TestAccRoute53ZoneAssociation_basic(t *testing.T) { }) } -func TestAccRoute53ZoneAssociation_region(t *testing.T) { +func TestAccAWSRoute53ZoneAssociation_region(t *testing.T) { var zone route53.HostedZone // record the initialized providers so that we can use them to diff --git a/builtin/providers/aws/resource_aws_route53_zone_test.go b/builtin/providers/aws/resource_aws_route53_zone_test.go index 92ab8a217..4fa1d51a8 100644 --- a/builtin/providers/aws/resource_aws_route53_zone_test.go +++ b/builtin/providers/aws/resource_aws_route53_zone_test.go @@ -64,7 +64,7 @@ func TestCleanChangeID(t *testing.T) { } } -func TestAccRoute53Zone_basic(t *testing.T) { +func TestAccAWSRoute53Zone_basic(t *testing.T) { var zone route53.GetHostedZoneOutput var td route53.ResourceTagSet @@ -85,7 +85,7 @@ func TestAccRoute53Zone_basic(t *testing.T) { }) } -func TestAccRoute53Zone_private_basic(t *testing.T) { +func TestAccAWSRoute53Zone_private_basic(t *testing.T) { var zone route53.GetHostedZoneOutput resource.Test(t, resource.TestCase{ @@ -104,7 +104,7 @@ func TestAccRoute53Zone_private_basic(t *testing.T) { }) } -func TestAccRoute53Zone_private_region(t *testing.T) { +func TestAccAWSRoute53Zone_private_region(t *testing.T) { var zone route53.GetHostedZoneOutput // record the initialized providers so that we can use them to diff --git a/builtin/providers/aws/resource_aws_s3_bucket.go b/builtin/providers/aws/resource_aws_s3_bucket.go index 93bf24390..af60e2214 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket.go +++ b/builtin/providers/aws/resource_aws_s3_bucket.go @@ -77,12 +77,16 @@ func resourceAwsS3Bucket() *schema.Resource { Optional: true, Computed: true, }, - "website_endpoint": &schema.Schema{ Type: schema.TypeString, Optional: true, Computed: true, }, + "website_domain": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, "tags": tagsSchema(), @@ -237,12 +241,17 @@ func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error { } // Add website_endpoint as an attribute - endpoint, err := websiteEndpoint(s3conn, d) + websiteEndpoint, err := websiteEndpoint(s3conn, d) if err != nil { return err } - if err := d.Set("website_endpoint", endpoint); err != nil { - return err + if websiteEndpoint != nil { + if err := d.Set("website_endpoint", websiteEndpoint.Endpoint); err != nil { + return err + } + if err := d.Set("website_domain", websiteEndpoint.Domain); err != nil { + return err + } } tagSet, err := getTagSetS3(s3conn, d.Id()) @@ -405,11 +414,11 @@ func resourceAwsS3BucketWebsiteDelete(s3conn *s3.S3, d *schema.ResourceData) err return nil } -func websiteEndpoint(s3conn *s3.S3, d *schema.ResourceData) (string, error) { +func websiteEndpoint(s3conn *s3.S3, d *schema.ResourceData) (*S3Website, error) { // If the bucket doesn't have a website configuration, return an empty // endpoint if _, ok := d.GetOk("website"); !ok { - return "", nil + return nil, nil } bucket := d.Get("bucket").(string) @@ -421,26 +430,31 @@ func websiteEndpoint(s3conn *s3.S3, d *schema.ResourceData) (string, error) { }, ) if err != nil { - return "", err + return nil, err } var region string if location.LocationConstraint != nil { region = *location.LocationConstraint } - return WebsiteEndpointUrl(bucket, region), nil + return WebsiteEndpoint(bucket, region), nil } -func WebsiteEndpointUrl(bucket string, region string) string { +func WebsiteEndpoint(bucket string, region string) *S3Website { + domain := WebsiteDomainUrl(region) + return &S3Website{Endpoint: fmt.Sprintf("%s.%s", bucket, domain), Domain: domain} +} + +func WebsiteDomainUrl(region string) string { region = normalizeRegion(region) // Frankfurt(and probably future) regions uses different syntax for website endpoints // http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteEndpoints.html if region == "eu-central-1" { - return fmt.Sprintf("%s.s3-website.%s.amazonaws.com", bucket, region) + return fmt.Sprintf("s3-website.%s.amazonaws.com", region) } - return fmt.Sprintf("%s.s3-website-%s.amazonaws.com", bucket, region) + return fmt.Sprintf("s3-website-%s.amazonaws.com", region) } func normalizeJson(jsonString interface{}) string { @@ -465,3 +479,7 @@ func normalizeRegion(region string) string { return region } + +type S3Website struct { + Endpoint, Domain string +} diff --git a/builtin/providers/aws/resource_aws_spot_instance_request.go b/builtin/providers/aws/resource_aws_spot_instance_request.go index 464a3de0c..bed4c42cb 100644 --- a/builtin/providers/aws/resource_aws_spot_instance_request.go +++ b/builtin/providers/aws/resource_aws_spot_instance_request.go @@ -160,7 +160,7 @@ func resourceAwsSpotInstanceRequestRead(d *schema.ResourceData, meta interface{} request := resp.SpotInstanceRequests[0] // if the request is cancelled, then it is gone - if *request.State == "canceled" { + if *request.State == "cancelled" { d.SetId("") return nil } diff --git a/builtin/providers/aws/resource_aws_vpc_dhcp_options_test.go b/builtin/providers/aws/resource_aws_vpc_dhcp_options_test.go index 988c8b3c4..e47f4d89d 100644 --- a/builtin/providers/aws/resource_aws_vpc_dhcp_options_test.go +++ b/builtin/providers/aws/resource_aws_vpc_dhcp_options_test.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccDHCPOptions_basic(t *testing.T) { +func TestAccAWSDHCPOptions_basic(t *testing.T) { var d ec2.DHCPOptions resource.Test(t, resource.TestCase{ diff --git a/builtin/providers/aws/resource_aws_vpc_endpoint_test.go b/builtin/providers/aws/resource_aws_vpc_endpoint_test.go index 6a29b688e..3d3055494 100644 --- a/builtin/providers/aws/resource_aws_vpc_endpoint_test.go +++ b/builtin/providers/aws/resource_aws_vpc_endpoint_test.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccVpcEndpoint_basic(t *testing.T) { +func TestAccAWSVpcEndpoint_basic(t *testing.T) { var endpoint ec2.VPCEndpoint resource.Test(t, resource.TestCase{ @@ -29,7 +29,7 @@ func TestAccVpcEndpoint_basic(t *testing.T) { }) } -func TestAccVpcEndpoint_withRouteTableAndPolicy(t *testing.T) { +func TestAccAWSVpcEndpoint_withRouteTableAndPolicy(t *testing.T) { var endpoint ec2.VPCEndpoint var routeTable ec2.RouteTable diff --git a/builtin/providers/aws/resource_aws_vpc_test.go b/builtin/providers/aws/resource_aws_vpc_test.go index 95121ec6f..a55fc2af7 100644 --- a/builtin/providers/aws/resource_aws_vpc_test.go +++ b/builtin/providers/aws/resource_aws_vpc_test.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccVpc_basic(t *testing.T) { +func TestAccAWSVpc_basic(t *testing.T) { var vpc ec2.VPC resource.Test(t, resource.TestCase{ @@ -32,7 +32,7 @@ func TestAccVpc_basic(t *testing.T) { }) } -func TestAccVpc_dedicatedTenancy(t *testing.T) { +func TestAccAWSVpc_dedicatedTenancy(t *testing.T) { var vpc ec2.VPC resource.Test(t, resource.TestCase{ @@ -52,7 +52,7 @@ func TestAccVpc_dedicatedTenancy(t *testing.T) { }) } -func TestAccVpc_tags(t *testing.T) { +func TestAccAWSVpc_tags(t *testing.T) { var vpc ec2.VPC resource.Test(t, resource.TestCase{ @@ -83,7 +83,7 @@ func TestAccVpc_tags(t *testing.T) { }) } -func TestAccVpcUpdate(t *testing.T) { +func TestAccAWSVpc_update(t *testing.T) { var vpc ec2.VPC resource.Test(t, resource.TestCase{ @@ -187,7 +187,7 @@ func testAccCheckVpcExists(n string, vpc *ec2.VPC) resource.TestCheckFunc { } // https://github.com/hashicorp/terraform/issues/1301 -func TestAccVpc_bothDnsOptionsSet(t *testing.T) { +func TestAccAWSVpc_bothDnsOptionsSet(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, diff --git a/builtin/providers/aws/resource_aws_vpn_gateway_test.go b/builtin/providers/aws/resource_aws_vpn_gateway_test.go index 6b1d3fc48..c39970c93 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway_test.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway_test.go @@ -87,7 +87,7 @@ func TestAccAWSVpnGateway_delete(t *testing.T) { }) } -func TestAccVpnGateway_tags(t *testing.T) { +func TestAccAWSVpnGateway_tags(t *testing.T) { var v ec2.VPNGateway resource.Test(t, resource.TestCase{ diff --git a/builtin/providers/aws/structure.go b/builtin/providers/aws/structure.go index 59bc61b14..b22a0f1c8 100644 --- a/builtin/providers/aws/structure.go +++ b/builtin/providers/aws/structure.go @@ -186,13 +186,17 @@ func expandIPPerms( // Takes the result of flatmap.Expand for an array of parameters and // returns Parameter API compatible objects func expandParameters(configured []interface{}) ([]*rds.Parameter, error) { - parameters := make([]*rds.Parameter, 0, len(configured)) + var parameters []*rds.Parameter // Loop over our configured parameters and create // an array of aws-sdk-go compatabile objects for _, pRaw := range configured { data := pRaw.(map[string]interface{}) + if data["name"].(string) == "" { + continue + } + p := &rds.Parameter{ ApplyMethod: aws.String(data["apply_method"].(string)), ParameterName: aws.String(data["name"].(string)), diff --git a/builtin/providers/aws/website_endpoint_url_test.go b/builtin/providers/aws/website_endpoint_url_test.go index 2f4ce5249..bbe282e2c 100644 --- a/builtin/providers/aws/website_endpoint_url_test.go +++ b/builtin/providers/aws/website_endpoint_url_test.go @@ -20,9 +20,9 @@ var websiteEndpoints = []struct { func TestWebsiteEndpointUrl(t *testing.T) { for _, tt := range websiteEndpoints { - s := WebsiteEndpointUrl("bucket-name", tt.in) - if s != tt.out { - t.Errorf("WebsiteEndpointUrl(\"bucket-name\", %q) => %q, want %q", tt.in, s, tt.out) + s := WebsiteEndpoint("bucket-name", tt.in) + if s.Endpoint != tt.out { + t.Errorf("WebsiteEndpointUrl(\"bucket-name\", %q) => %q, want %q", tt.in, s.Endpoint, tt.out) } } } diff --git a/builtin/provisioners/chef/linux_provisioner.go b/builtin/provisioners/chef/linux_provisioner.go index 2a1e92eab..b7a3b2813 100644 --- a/builtin/provisioners/chef/linux_provisioner.go +++ b/builtin/provisioners/chef/linux_provisioner.go @@ -71,12 +71,29 @@ func (p *Provisioner) linuxCreateConfigFiles( return err } + // Make sure we have enough rights to upload the hints if using sudo + if p.useSudo { + if err := p.runCommand(o, comm, "chmod 777 "+hintsDir); err != nil { + return err + } + } + if err := p.deployOhaiHints(o, comm, hintsDir); err != nil { return err } + + // When done copying the hints restore the rights and make sure root is owner + if p.useSudo { + if err := p.runCommand(o, comm, "chmod 755 "+hintsDir); err != nil { + return err + } + if err := p.runCommand(o, comm, "chown -R root.root "+hintsDir); err != nil { + return err + } + } } - // When done copying the files restore the rights and make sure root is owner + // When done copying all files restore the rights and make sure root is owner if p.useSudo { if err := p.runCommand(o, comm, "chmod 755 "+linuxConfDir); err != nil { return err diff --git a/builtin/provisioners/chef/linux_provisioner_test.go b/builtin/provisioners/chef/linux_provisioner_test.go index 2d509f3cb..27a1dbba0 100644 --- a/builtin/provisioners/chef/linux_provisioner_test.go +++ b/builtin/provisioners/chef/linux_provisioner_test.go @@ -163,11 +163,14 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) { }), Commands: map[string]bool{ - "sudo mkdir -p " + linuxConfDir: true, - "sudo chmod 777 " + linuxConfDir: true, - "sudo mkdir -p " + path.Join(linuxConfDir, "ohai/hints"): true, - "sudo chmod 755 " + linuxConfDir: true, - "sudo chown -R root.root " + linuxConfDir: true, + "sudo mkdir -p " + linuxConfDir: true, + "sudo chmod 777 " + linuxConfDir: true, + "sudo mkdir -p " + path.Join(linuxConfDir, "ohai/hints"): true, + "sudo chmod 777 " + path.Join(linuxConfDir, "ohai/hints"): true, + "sudo chmod 755 " + path.Join(linuxConfDir, "ohai/hints"): true, + "sudo chown -R root.root " + path.Join(linuxConfDir, "ohai/hints"): true, + "sudo chmod 755 " + linuxConfDir: true, + "sudo chown -R root.root " + linuxConfDir: true, }, Uploads: map[string]string{ diff --git a/command/hook_ui.go b/command/hook_ui.go index c9543b3f6..6a10ceefb 100644 --- a/command/hook_ui.go +++ b/command/hook_ui.go @@ -140,7 +140,8 @@ func (h *UiHook) PostApply( } if applyerr != nil { - msg = fmt.Sprintf("Error: %s", applyerr) + // Errors are collected and printed in ApplyCommand, no need to duplicate + return terraform.HookActionContinue, nil } h.ui.Output(h.Colorize.Color(fmt.Sprintf( diff --git a/deps/v0-6-1.json b/deps/v0-6-1.json new file mode 100644 index 000000000..69bbd10dc --- /dev/null +++ b/deps/v0-6-1.json @@ -0,0 +1,385 @@ +{ + "ImportPath": "github.com/hashicorp/terraform", + "GoVersion": "go1.4.2", + "Packages": [ + "./..." + ], + "Deps": [ + { + "ImportPath": "code.google.com/p/go-uuid/uuid", + "Comment": "null-15", + "Rev": "35bc42037350f0078e3c974c6ea690f1926603ab" + }, + { + "ImportPath": "github.com/Azure/azure-sdk-for-go/core/http", + "Comment": "v1.2-216-g9197765", + "Rev": "91977650587a7bc48318c0430649d7fea886f111" + }, + { + "ImportPath": "github.com/Azure/azure-sdk-for-go/core/tls", + "Comment": "v1.2-216-g9197765", + "Rev": "91977650587a7bc48318c0430649d7fea886f111" + }, + { + "ImportPath": "github.com/Azure/azure-sdk-for-go/management", + "Comment": "v1.2-216-g9197765", + "Rev": "91977650587a7bc48318c0430649d7fea886f111" + }, + { + "ImportPath": "github.com/Azure/azure-sdk-for-go/storage", + "Comment": "v1.2-216-g9197765", + "Rev": "91977650587a7bc48318c0430649d7fea886f111" + }, + { + "ImportPath": "github.com/Azure/go-pkcs12", + "Rev": "a635c0684cd517745ca5c9552a312627791d5ba0" + }, + { + "ImportPath": "github.com/armon/circbuf", + "Rev": "f092b4f207b6e5cce0569056fba9e1a2735cb6cf" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/aws", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/endpoints", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/ec2query", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/json/jsonutil", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/jsonrpc", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/query", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/rest", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/restjson", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/restxml", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/signer/v4", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/autoscaling", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/cloudwatch", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/dynamodb", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/ec2", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/ecs", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/elasticache", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/elb", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/iam", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/kinesis", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/lambda", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/rds", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/route53", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/s3", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/sns", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/sqs", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/awslabs/aws-sdk-go/aws/credentials", + "Comment": "v0.6.7-3-g2a6648c", + "Rev": "2a6648c479175ce005bca95780f948a196a46062" + }, + { + "ImportPath": "github.com/cyberdelia/heroku-go/v3", + "Rev": "594d483b9b6a8ddc7cd2f1e3e7d1de92fa2de665" + }, + { + "ImportPath": "github.com/dylanmei/iso8601", + "Rev": "2075bf119b58e5576c6ed9f867b8f3d17f2e54d4" + }, + { + "ImportPath": "github.com/dylanmei/winrmtest", + "Rev": "3e9661c52c45dab9a8528966a23d421922fca9b9" + }, + { + "ImportPath": "github.com/fsouza/go-dockerclient", + "Rev": "f6e9f5396e0e8f34472efe443d0cb7f9af162b88" + }, + { + "ImportPath": "github.com/hashicorp/atlas-go/archive", + "Comment": "20141209094003-71-g785958f", + "Rev": "785958ffcd6a8857890651f3f4d9a289ddc27633" + }, + { + "ImportPath": "github.com/hashicorp/atlas-go/v1", + "Comment": "20141209094003-71-g785958f", + "Rev": "785958ffcd6a8857890651f3f4d9a289ddc27633" + }, + { + "ImportPath": "github.com/hashicorp/consul/api", + "Comment": "v0.5.2-159-gc34bcb4", + "Rev": "c34bcb45c670af076846826ea72c436fbd0e2c35" + }, + { + "ImportPath": "github.com/hashicorp/errwrap", + "Rev": "7554cd9344cec97297fa6649b055a8c98c2a1e55" + }, + { + "ImportPath": "github.com/hashicorp/go-checkpoint", + "Rev": "88326f6851319068e7b34981032128c0b1a6524d" + }, + { + "ImportPath": "github.com/hashicorp/go-multierror", + "Rev": "56912fb08d85084aa318edcf2bba735b97cf35c5" + }, + { + "ImportPath": "github.com/hashicorp/go-version", + "Rev": "999359b6b7a041ce16e695d51e92145b83f01087" + }, + { + "ImportPath": "github.com/hashicorp/hcl", + "Rev": "54864211433d45cb780682431585b3e573b49e4a" + }, + { + "ImportPath": "github.com/hashicorp/yamux", + "Rev": "8e00b30457b1486b012f204b82ec92ae6b547de8" + }, + { + "ImportPath": "github.com/imdario/mergo", + "Comment": "0.2.0-5-g61a5285", + "Rev": "61a52852277811e93e06d28e0d0c396284a7730b" + }, + { + "ImportPath": "github.com/masterzen/simplexml/dom", + "Rev": "95ba30457eb1121fa27753627c774c7cd4e90083" + }, + { + "ImportPath": "github.com/masterzen/winrm/soap", + "Rev": "23128e7b3dc1f8091aeff7aae82cb2112ce53c75" + }, + { + "ImportPath": "github.com/masterzen/winrm/winrm", + "Rev": "23128e7b3dc1f8091aeff7aae82cb2112ce53c75" + }, + { + "ImportPath": "github.com/masterzen/xmlpath", + "Rev": "13f4951698adc0fa9c1dda3e275d489a24201161" + }, + { + "ImportPath": "github.com/mitchellh/cli", + "Rev": "8102d0ed5ea2709ade1243798785888175f6e415" + }, + { + "ImportPath": "github.com/mitchellh/colorstring", + "Rev": "61164e49940b423ba1f12ddbdf01632ac793e5e9" + }, + { + "ImportPath": "github.com/mitchellh/copystructure", + "Rev": "6fc66267e9da7d155a9d3bd489e00dad02666dc6" + }, + { + "ImportPath": "github.com/mitchellh/go-homedir", + "Rev": "1f6da4a72e57d4e7edd4a7295a585e0a3999a2d4" + }, + { + "ImportPath": "github.com/mitchellh/go-linereader", + "Rev": "07bab5fdd9580500aea6ada0e09df4aa28e68abd" + }, + { + "ImportPath": "github.com/mitchellh/mapstructure", + "Rev": "281073eb9eb092240d33ef253c404f1cca550309" + }, + { + "ImportPath": "github.com/mitchellh/osext", + "Rev": "0dd3f918b21bec95ace9dc86c7e70266cfc5c702" + }, + { + "ImportPath": "github.com/mitchellh/packer/common/uuid", + "Comment": "v0.8.2-4-g2010a0c", + "Rev": "2010a0c966175b3c0fa8d158a879c10acbba0d76" + }, + { + "ImportPath": "github.com/mitchellh/panicwrap", + "Rev": "45cbfd3bae250c7676c077fb275be1a2968e066a" + }, + { + "ImportPath": "github.com/mitchellh/prefixedio", + "Rev": "89d9b535996bf0a185f85b59578f2e245f9e1724" + }, + { + "ImportPath": "github.com/mitchellh/reflectwalk", + "Rev": "eecf4c70c626c7cfbb95c90195bc34d386c74ac6" + }, + { + "ImportPath": "github.com/nu7hatch/gouuid", + "Rev": "179d4d0c4d8d407a32af483c2354df1d2c91e6c3" + }, + { + "ImportPath": "github.com/packer-community/winrmcp/winrmcp", + "Rev": "743b1afe5ee3f6d5ba71a0d50673fa0ba2123d6b" + }, + { + "ImportPath": "github.com/pearkes/cloudflare", + "Rev": "19e280b056f3742e535ea12ae92a37ea7767ea82" + }, + { + "ImportPath": "github.com/pearkes/digitalocean", + "Rev": "e966f00c2d9de5743e87697ab77c7278f5998914" + }, + { + "ImportPath": "github.com/pearkes/dnsimple", + "Rev": "2a807d118c9e52e94819f414a6ec0293b45cad01" + }, + { + "ImportPath": "github.com/pearkes/mailgun", + "Rev": "5b02e7e9ffee9869f81393e80db138f6ff726260" + }, + { + "ImportPath": "github.com/rackspace/gophercloud", + "Comment": "v1.0.0-623-ge83aa01", + "Rev": "e83aa011e019917c7bd951444d61c42431b4d21d" + }, + { + "ImportPath": "github.com/satori/go.uuid", + "Rev": "afe1e2ddf0f05b7c29d388a3f8e76cb15c2231ca" + }, + { + "ImportPath": "github.com/soniah/dnsmadeeasy", + "Comment": "v1.1-2-g5578a8c", + "Rev": "5578a8c15e33958c61cf7db720b6181af65f4a9e" + }, + { + "ImportPath": "github.com/vaughan0/go-ini", + "Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1" + }, + { + "ImportPath": "github.com/xanzy/go-cloudstack/cloudstack", + "Comment": "v1.2.0-36-g0031956", + "Rev": "00319560eeca5e6ffef3ba048c97c126a465854f" + }, + { + "ImportPath": "golang.org/x/crypto/ssh", + "Rev": "7d5b0be716b9d6d4269afdaae10032bb296d3cdf" + }, + { + "ImportPath": "golang.org/x/net/context", + "Rev": "f0cf018861e2b54077eced91659e255072b5f215" + }, + { + "ImportPath": "golang.org/x/oauth2", + "Rev": "8914e5017ca260f2a3a1575b1e6868874050d95e" + }, + { + "ImportPath": "google.golang.org/api/compute/v1", + "Rev": "18450f4e95c7e76ce3a5dc3a8cb7178ab6d56121" + }, + { + "ImportPath": "google.golang.org/api/container/v1", + "Rev": "18450f4e95c7e76ce3a5dc3a8cb7178ab6d56121" + }, + { + "ImportPath": "google.golang.org/api/dns/v1", + "Rev": "18450f4e95c7e76ce3a5dc3a8cb7178ab6d56121" + }, + { + "ImportPath": "google.golang.org/api/googleapi", + "Rev": "18450f4e95c7e76ce3a5dc3a8cb7178ab6d56121" + }, + { + "ImportPath": "google.golang.org/api/storage/v1", + "Rev": "18450f4e95c7e76ce3a5dc3a8cb7178ab6d56121" + }, + { + "ImportPath": "google.golang.org/cloud/compute/metadata", + "Rev": "522a8ceb4bb83c2def27baccf31d646bce11a4b2" + }, + { + "ImportPath": "google.golang.org/cloud/internal", + "Rev": "522a8ceb4bb83c2def27baccf31d646bce11a4b2" + } + ] +} diff --git a/helper/resource/id.go b/helper/resource/id.go index b3af422fc..ea0ae1916 100644 --- a/helper/resource/id.go +++ b/helper/resource/id.go @@ -9,13 +9,18 @@ import ( const UniqueIdPrefix = `terraform-` -// Helper for a resource to generate a unique identifier +// Helper for a resource to generate a unique identifier w/ default prefix +func UniqueId() string { + return PrefixedUniqueId(UniqueIdPrefix) +} + +// Helper for a resource to generate a unique identifier w/ given prefix // // This uses a simple RFC 4122 v4 UUID with some basic cosmetic filters // applied (base32, remove padding, downcase) to make visually distinguishing // identifiers easier. -func UniqueId() string { - return fmt.Sprintf("%s%s", UniqueIdPrefix, +func PrefixedUniqueId(prefix string) string { + return fmt.Sprintf("%s%s", prefix, strings.ToLower( strings.Replace( base32.StdEncoding.EncodeToString(uuidV4()), diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 2f4d43296..ac252abd2 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -215,6 +215,49 @@ func TestContext2Apply_createBeforeDestroyUpdate(t *testing.T) { } } +func TestContext2Apply_destroyComputed(t *testing.T) { + m := testModule(t, "apply-destroy-computed") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "foo", + Attributes: map[string]string{ + "output": "value", + }, + }, + }, + }, + }, + }, + } + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + Destroy: true, + }) + + if p, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } else { + t.Logf(p.String()) + } + + if _, err := ctx.Apply(); err != nil { + t.Fatalf("err: %s", err) + } +} + func TestContext2Apply_minimal(t *testing.T) { m := testModule(t, "apply-minimal") p := testProvider("aws") @@ -2097,6 +2140,105 @@ func TestContext2Apply_destroy(t *testing.T) { } } +func TestContext2Apply_destroyNestedModule(t *testing.T) { + m := testModule(t, "apply-destroy-nested-module") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: []string{"root", "child", "subchild"}, + Resources: map[string]*ResourceState{ + "aws_instance.bar": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + // First plan and apply a create operation + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + // Test that things were destroyed + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyDestroyNestedModuleStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + +func TestContext2Apply_destroyDeeplyNestedModule(t *testing.T) { + m := testModule(t, "apply-destroy-deeply-nested-module") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: []string{"root", "child", "subchild", "subsubchild"}, + Resources: map[string]*ResourceState{ + "aws_instance.bar": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + // First plan and apply a create operation + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + // Test that things were destroyed + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(` +module.child.subchild.subsubchild: + + `) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + func TestContext2Apply_destroyOutputs(t *testing.T) { m := testModule(t, "apply-destroy-outputs") h := new(HookRecordApplyOrder) diff --git a/terraform/eval_apply.go b/terraform/eval_apply.go index cebf68265..c22a6ca4e 100644 --- a/terraform/eval_apply.go +++ b/terraform/eval_apply.go @@ -94,7 +94,8 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) { // if we have one, otherwise we just output it. if err != nil { if n.Error != nil { - *n.Error = multierror.Append(*n.Error, err) + helpfulErr := fmt.Errorf("%s: %s", n.Info.Id, err.Error()) + *n.Error = multierror.Append(*n.Error, helpfulErr) } else { return nil, err } diff --git a/terraform/eval_refresh.go b/terraform/eval_refresh.go index 3d25ecc8b..fa2b8126c 100644 --- a/terraform/eval_refresh.go +++ b/terraform/eval_refresh.go @@ -1,6 +1,7 @@ package terraform import ( + "fmt" "log" ) @@ -35,7 +36,7 @@ func (n *EvalRefresh) Eval(ctx EvalContext) (interface{}, error) { // Refresh! state, err = provider.Refresh(n.Info, state) if err != nil { - return nil, err + return nil, fmt.Errorf("%s: %s", n.Info.Id, err.Error()) } // Call post-refresh hook diff --git a/terraform/graph_builder.go b/terraform/graph_builder.go index 0ec3f8b20..2190be15e 100644 --- a/terraform/graph_builder.go +++ b/terraform/graph_builder.go @@ -164,6 +164,9 @@ func (b *BuiltinGraphBuilder) Steps(path []string) []GraphTransformer { Then: &PruneDestroyTransformer{Diff: b.Diff, State: b.State}, }), + // Remove the noop nodes + &PruneNoopTransformer{Diff: b.Diff, State: b.State}, + // Insert nodes to close opened plugin connections &CloseProviderTransformer{}, &CloseProvisionerTransformer{}, diff --git a/terraform/graph_config_node_resource.go b/terraform/graph_config_node_resource.go index 2235f4925..dfc958714 100644 --- a/terraform/graph_config_node_resource.go +++ b/terraform/graph_config_node_resource.go @@ -249,6 +249,41 @@ func (n *GraphNodeConfigResource) DestroyNode(mode GraphNodeDestroyMode) GraphNo return result } +// GraphNodeNoopPrunable +func (n *GraphNodeConfigResource) Noop(opts *NoopOpts) bool { + // We don't have any noop optimizations for destroy nodes yet + if n.DestroyMode != DestroyNone { + return false + } + + // If there is no diff, then we aren't a noop since something needs to + // be done (such as a plan). We only check if we're a noop in a diff. + if opts.Diff == nil || opts.Diff.Empty() { + return false + } + + // If we have no module diff, we're certainly a noop. This is because + // it means there is a diff, and that the module we're in just isn't + // in it, meaning we're not doing anything. + if opts.ModDiff == nil || opts.ModDiff.Empty() { + return true + } + + // Grab the ID which is the prefix (in the case count > 0 at some point) + prefix := n.Resource.Id() + + // Go through the diff and if there are any with our name on it, keep us + found := false + for k, _ := range opts.ModDiff.Resources { + if strings.HasPrefix(k, prefix) { + found = true + break + } + } + + return !found +} + // Same as GraphNodeConfigResource, but for flattening type GraphNodeConfigResourceFlat struct { *GraphNodeConfigResource diff --git a/terraform/graph_config_node_variable.go b/terraform/graph_config_node_variable.go index 91f126c49..9b3d77dbc 100644 --- a/terraform/graph_config_node_variable.go +++ b/terraform/graph_config_node_variable.go @@ -75,6 +75,28 @@ func (n *GraphNodeConfigVariable) DestroyEdgeInclude(v dag.Vertex) bool { return false } +// GraphNodeNoopPrunable +func (n *GraphNodeConfigVariable) Noop(opts *NoopOpts) bool { + // If we have no diff, always keep this in the graph. We have to do + // this primarily for validation: we want to validate that variable + // interpolations are valid even if there are no resources that + // depend on them. + if opts.Diff == nil || opts.Diff.Empty() { + return false + } + + for _, v := range opts.Graph.UpEdges(opts.Vertex).List() { + // This is terrible, but I can't think of a better way to do this. + if dag.VertexName(v) == rootNodeName { + continue + } + + return false + } + + return true +} + // GraphNodeProxy impl. func (n *GraphNodeConfigVariable) Proxy() bool { return true diff --git a/terraform/interpolate.go b/terraform/interpolate.go index 83f6265e1..6d103cd80 100644 --- a/terraform/interpolate.go +++ b/terraform/interpolate.go @@ -370,7 +370,7 @@ MISSING: // be unknown. Instead, we return that the value is computed so // that the graph can continue to refresh other nodes. It doesn't // matter because the config isn't interpolated anyways. - if i.Operation == walkRefresh { + if i.Operation == walkRefresh || i.Operation == walkPlanDestroy { return config.UnknownVariableValue, nil } @@ -433,6 +433,11 @@ func (i *Interpolater) computeResourceMultiVariable( continue } + // If any value is unknown, the whole thing is unknown + if attr == config.UnknownVariableValue { + return config.UnknownVariableValue, nil + } + values = append(values, attr) } @@ -441,7 +446,7 @@ func (i *Interpolater) computeResourceMultiVariable( // be unknown. Instead, we return that the value is computed so // that the graph can continue to refresh other nodes. It doesn't // matter because the config isn't interpolated anyways. - if i.Operation == walkRefresh { + if i.Operation == walkRefresh || i.Operation == walkPlanDestroy { return config.UnknownVariableValue, nil } diff --git a/terraform/interpolate_test.go b/terraform/interpolate_test.go index 52896a54b..af06e429c 100644 --- a/terraform/interpolate_test.go +++ b/terraform/interpolate_test.go @@ -136,6 +136,80 @@ func TestInterpolater_pathRoot(t *testing.T) { }) } +func TestInterpolater_resourceVariable(t *testing.T) { + lock := new(sync.RWMutex) + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.web": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + }, + } + + i := &Interpolater{ + Module: testModule(t, "interpolate-resource-variable"), + State: state, + StateLock: lock, + } + + scope := &InterpolationScope{ + Path: rootModulePath, + } + + testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{ + Value: "bar", + Type: ast.TypeString, + }) +} + +func TestInterpolater_resourceVariableMulti(t *testing.T) { + lock := new(sync.RWMutex) + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.web": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "foo": config.UnknownVariableValue, + }, + }, + }, + }, + }, + }, + } + + i := &Interpolater{ + Module: testModule(t, "interpolate-resource-variable"), + State: state, + StateLock: lock, + } + + scope := &InterpolationScope{ + Path: rootModulePath, + } + + testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{ + Value: config.UnknownVariableValue, + Type: ast.TypeString, + }) +} + func testInterpolate( t *testing.T, i *Interpolater, scope *InterpolationScope, diff --git a/terraform/state.go b/terraform/state.go index d3b19c9de..2ba005e39 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -104,24 +104,56 @@ func (s *State) ModuleByPath(path []string) *ModuleState { // returning their full paths. These paths can be used with ModuleByPath // to return the actual state. func (s *State) ModuleOrphans(path []string, c *config.Config) [][]string { + // direct keeps track of what direct children we have both in our config + // and in our state. childrenKeys keeps track of what isn't an orphan. + direct := make(map[string]struct{}) childrenKeys := make(map[string]struct{}) if c != nil { for _, m := range c.Modules { childrenKeys[m.Name] = struct{}{} + direct[m.Name] = struct{}{} } } - // Go over the direct children and find any that aren't in our - // keys. + // Go over the direct children and find any that aren't in our keys. var orphans [][]string for _, m := range s.Children(path) { - if _, ok := childrenKeys[m.Path[len(m.Path)-1]]; ok { + key := m.Path[len(m.Path)-1] + + // Record that we found this key as a direct child. We use this + // later to find orphan nested modules. + direct[key] = struct{}{} + + // If we have a direct child still in our config, it is not an orphan + if _, ok := childrenKeys[key]; ok { continue } orphans = append(orphans, m.Path) } + // Find the orphans that are nested... + for _, m := range s.Modules { + // We only want modules that are at least grandchildren + if len(m.Path) < len(path)+2 { + continue + } + + // If it isn't part of our tree, continue + if !reflect.DeepEqual(path, m.Path[:len(path)]) { + continue + } + + // If we have the direct child, then just skip it. + key := m.Path[len(path)] + if _, ok := direct[key]; ok { + continue + } + + // Add this orphan + orphans = append(orphans, m.Path[:len(path)+1]) + } + return orphans } diff --git a/terraform/state_test.go b/terraform/state_test.go index 7f3dbb567..eeb974d0b 100644 --- a/terraform/state_test.go +++ b/terraform/state_test.go @@ -85,6 +85,28 @@ func TestStateModuleOrphans(t *testing.T) { } } +func TestStateModuleOrphans_nested(t *testing.T) { + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: RootModulePath, + }, + &ModuleState{ + Path: []string{RootModuleName, "foo", "bar"}, + }, + }, + } + + actual := state.ModuleOrphans(RootModulePath, nil) + expected := [][]string{ + []string{RootModuleName, "foo"}, + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +} + func TestStateModuleOrphans_nilConfig(t *testing.T) { state := &State{ Modules: []*ModuleState{ diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 59dad38d4..c84e9803c 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -483,6 +483,11 @@ const testTerraformApplyDestroyStr = ` ` +const testTerraformApplyDestroyNestedModuleStr = ` +module.child.subchild: + +` + const testTerraformApplyErrorStr = ` aws_instance.bar: ID = bar diff --git a/terraform/test-fixtures/apply-destroy-computed/child/main.tf b/terraform/test-fixtures/apply-destroy-computed/child/main.tf new file mode 100644 index 000000000..5cd1f02b6 --- /dev/null +++ b/terraform/test-fixtures/apply-destroy-computed/child/main.tf @@ -0,0 +1,5 @@ +variable "value" {} + +resource "aws_instance" "bar" { + value = "${var.value}" +} diff --git a/terraform/test-fixtures/apply-destroy-computed/main.tf b/terraform/test-fixtures/apply-destroy-computed/main.tf new file mode 100644 index 000000000..768c9680d --- /dev/null +++ b/terraform/test-fixtures/apply-destroy-computed/main.tf @@ -0,0 +1,6 @@ +resource "aws_instance" "foo" {} + +module "child" { + source = "./child" + value = "${aws_instance.foo.output}" +} diff --git a/terraform/test-fixtures/apply-destroy-deeply-nested-module/child/main.tf b/terraform/test-fixtures/apply-destroy-deeply-nested-module/child/main.tf new file mode 100644 index 000000000..3694951f5 --- /dev/null +++ b/terraform/test-fixtures/apply-destroy-deeply-nested-module/child/main.tf @@ -0,0 +1,3 @@ +module "subchild" { + source = "./subchild" +} diff --git a/terraform/test-fixtures/apply-destroy-deeply-nested-module/child/subchild/main.tf b/terraform/test-fixtures/apply-destroy-deeply-nested-module/child/subchild/main.tf new file mode 100644 index 000000000..d31b87e0c --- /dev/null +++ b/terraform/test-fixtures/apply-destroy-deeply-nested-module/child/subchild/main.tf @@ -0,0 +1,5 @@ +/* +module "subsubchild" { + source = "./subsubchild" +} +*/ diff --git a/terraform/test-fixtures/apply-destroy-deeply-nested-module/child/subchild/subsubchild/main.tf b/terraform/test-fixtures/apply-destroy-deeply-nested-module/child/subchild/subsubchild/main.tf new file mode 100644 index 000000000..6ff716a4d --- /dev/null +++ b/terraform/test-fixtures/apply-destroy-deeply-nested-module/child/subchild/subsubchild/main.tf @@ -0,0 +1 @@ +resource "aws_instance" "bar" {} diff --git a/terraform/test-fixtures/apply-destroy-deeply-nested-module/main.tf b/terraform/test-fixtures/apply-destroy-deeply-nested-module/main.tf new file mode 100644 index 000000000..1f95749fa --- /dev/null +++ b/terraform/test-fixtures/apply-destroy-deeply-nested-module/main.tf @@ -0,0 +1,3 @@ +module "child" { + source = "./child" +} diff --git a/terraform/test-fixtures/apply-destroy-nested-module/child/main.tf b/terraform/test-fixtures/apply-destroy-nested-module/child/main.tf new file mode 100644 index 000000000..852bce8b9 --- /dev/null +++ b/terraform/test-fixtures/apply-destroy-nested-module/child/main.tf @@ -0,0 +1,3 @@ +module "subchild" { + source = "./subchild" +} diff --git a/terraform/test-fixtures/apply-destroy-nested-module/child/subchild/main.tf b/terraform/test-fixtures/apply-destroy-nested-module/child/subchild/main.tf new file mode 100644 index 000000000..6ff716a4d --- /dev/null +++ b/terraform/test-fixtures/apply-destroy-nested-module/child/subchild/main.tf @@ -0,0 +1 @@ +resource "aws_instance" "bar" {} diff --git a/terraform/test-fixtures/apply-destroy-nested-module/main.tf b/terraform/test-fixtures/apply-destroy-nested-module/main.tf new file mode 100644 index 000000000..8a5a1b2e5 --- /dev/null +++ b/terraform/test-fixtures/apply-destroy-nested-module/main.tf @@ -0,0 +1,5 @@ +/* +module "child" { + source = "./child" +} +*/ diff --git a/terraform/test-fixtures/interpolate-resource-variable/main.tf b/terraform/test-fixtures/interpolate-resource-variable/main.tf new file mode 100644 index 000000000..64cbf6236 --- /dev/null +++ b/terraform/test-fixtures/interpolate-resource-variable/main.tf @@ -0,0 +1 @@ +resource "aws_instance" "web" {} diff --git a/terraform/transform_noop.go b/terraform/transform_noop.go new file mode 100644 index 000000000..e36b61937 --- /dev/null +++ b/terraform/transform_noop.go @@ -0,0 +1,104 @@ +package terraform + +import ( + "github.com/hashicorp/terraform/dag" +) + +// GraphNodeNoopPrunable can be implemented by nodes that can be +// pruned if they are noops. +type GraphNodeNoopPrunable interface { + Noop(*NoopOpts) bool +} + +// NoopOpts are the options available to determine if your node is a noop. +type NoopOpts struct { + Graph *Graph + Vertex dag.Vertex + Diff *Diff + State *State + ModDiff *ModuleDiff + ModState *ModuleState +} + +// PruneNoopTransformer is a graph transform that prunes nodes that +// consider themselves no-ops. This is done to both simplify the graph +// as well as to remove graph nodes that might otherwise cause problems +// during the graph run. Therefore, this transformer isn't completely +// an optimization step, and can instead be considered critical to +// Terraform operations. +// +// Example of the above case: variables for modules interpolate their values. +// Interpolation will fail on destruction (since attributes are being deleted), +// but variables shouldn't even eval if there is nothing that will consume +// the variable. Therefore, variables can note that they can be omitted +// safely in this case. +// +// The PruneNoopTransformer will prune nodes depth first, and will automatically +// create connect through the dependencies of pruned nodes. For example, +// if we have a graph A => B => C (A depends on B, etc.), and B decides to +// be removed, we'll still be left with A => C; the edge will be properly +// connected. +type PruneNoopTransformer struct { + Diff *Diff + State *State +} + +func (t *PruneNoopTransformer) Transform(g *Graph) error { + // Find the leaves. + leaves := make([]dag.Vertex, 0, 10) + for _, v := range g.Vertices() { + if g.DownEdges(v).Len() == 0 { + leaves = append(leaves, v) + } + } + + // Do a depth first walk from the leaves and remove things. + return g.ReverseDepthFirstWalk(leaves, func(v dag.Vertex, depth int) error { + // We need a prunable + pn, ok := v.(GraphNodeNoopPrunable) + if !ok { + return nil + } + + // Start building the noop opts + path := g.Path + if pn, ok := v.(GraphNodeSubPath); ok { + path = pn.Path() + } + + var modDiff *ModuleDiff + var modState *ModuleState + if t.Diff != nil { + modDiff = t.Diff.ModuleByPath(path) + } + if t.State != nil { + modState = t.State.ModuleByPath(path) + } + + // Determine if its a noop. If it isn't, just return + noop := pn.Noop(&NoopOpts{ + Graph: g, + Vertex: v, + Diff: t.Diff, + State: t.State, + ModDiff: modDiff, + ModState: modState, + }) + if !noop { + return nil + } + + // It is a noop! We first preserve edges. + up := g.UpEdges(v).List() + for _, downV := range g.DownEdges(v).List() { + for _, upV := range up { + g.Connect(dag.BasicEdge(upV, downV)) + } + } + + // Then remove it + g.Remove(v) + + return nil + }) +} diff --git a/terraform/transform_noop_test.go b/terraform/transform_noop_test.go new file mode 100644 index 000000000..65db95fda --- /dev/null +++ b/terraform/transform_noop_test.go @@ -0,0 +1,54 @@ +package terraform + +import ( + "strings" + "testing" + + "github.com/hashicorp/terraform/dag" +) + +func TestPruneNoopTransformer(t *testing.T) { + g := Graph{Path: RootModulePath} + + a := &testGraphNodeNoop{NameValue: "A"} + b := &testGraphNodeNoop{NameValue: "B", Value: true} + c := &testGraphNodeNoop{NameValue: "C"} + + g.Add(a) + g.Add(b) + g.Add(c) + g.Connect(dag.BasicEdge(a, b)) + g.Connect(dag.BasicEdge(b, c)) + + { + tf := &PruneNoopTransformer{} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testTransformPruneNoopStr) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + +const testTransformPruneNoopStr = ` +A + C +C +` + +type testGraphNodeNoop struct { + NameValue string + Value bool +} + +func (v *testGraphNodeNoop) Name() string { + return v.NameValue +} + +func (v *testGraphNodeNoop) Noop(*NoopOpts) bool { + return v.Value +} diff --git a/terraform/transform_orphan.go b/terraform/transform_orphan.go index 8e609f609..bb381c823 100644 --- a/terraform/transform_orphan.go +++ b/terraform/transform_orphan.go @@ -100,9 +100,14 @@ func (t *OrphanTransformer) Transform(g *Graph) error { moduleOrphans := t.State.ModuleOrphans(g.Path, config) moduleVertexes := make([]dag.Vertex, len(moduleOrphans)) for i, path := range moduleOrphans { + var deps []string + if s := t.State.ModuleByPath(path); s != nil { + deps = s.Dependencies + } + moduleVertexes[i] = g.Add(&graphNodeOrphanModule{ Path: path, - dependentOn: t.State.ModuleByPath(path).Dependencies, + dependentOn: deps, }) } @@ -356,3 +361,9 @@ func (n *graphNodeOrphanResourceFlat) CreateBeforeDestroy() bool { func (n *graphNodeOrphanResourceFlat) CreateNode() dag.Vertex { return n } + +func (n *graphNodeOrphanResourceFlat) ProvidedBy() []string { + return modulePrefixList( + n.graphNodeOrphanResource.ProvidedBy(), + modulePrefixStr(n.PathValue)) +} diff --git a/terraform/transform_root.go b/terraform/transform_root.go index 15de14754..7a422b826 100644 --- a/terraform/transform_root.go +++ b/terraform/transform_root.go @@ -2,6 +2,8 @@ package terraform import "github.com/hashicorp/terraform/dag" +const rootNodeName = "root" + // RootTransformer is a GraphTransformer that adds a root to the graph. type RootTransformer struct{} @@ -32,7 +34,7 @@ func (t *RootTransformer) Transform(g *Graph) error { type graphNodeRoot struct{} func (n graphNodeRoot) Name() string { - return "root" + return rootNodeName } func (n graphNodeRoot) Flatten(p []string) (dag.Vertex, error) { diff --git a/terraform/version.go b/terraform/version.go index 9cbc9adb1..78f3e5ff7 100644 --- a/terraform/version.go +++ b/terraform/version.go @@ -1,7 +1,7 @@ package terraform // The main version number that is being run at the moment. -const Version = "0.6.1" +const Version = "0.6.2" // A pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release diff --git a/website/source/docs/providers/aws/r/elb.html.markdown b/website/source/docs/providers/aws/r/elb.html.markdown index 9c63788d3..824a5507f 100644 --- a/website/source/docs/providers/aws/r/elb.html.markdown +++ b/website/source/docs/providers/aws/r/elb.html.markdown @@ -57,7 +57,7 @@ resource "aws_elb" "bar" { The following arguments are supported: -* `name` - (Required) The name of the ELB +* `name` - (Optional) The name of the ELB. By default generated by terraform. * `availability_zones` - (Required for an EC2-classic ELB) The AZ's to serve traffic in. * `security_groups` - (Optional) A list of security group IDs to assign to the ELB. * `subnets` - (Required for a VPC ELB) A list of subnet IDs to attach to the ELB. diff --git a/website/source/docs/providers/aws/r/network_interface.markdown b/website/source/docs/providers/aws/r/network_interface.markdown index 8144d8f0f..0384b184a 100644 --- a/website/source/docs/providers/aws/r/network_interface.markdown +++ b/website/source/docs/providers/aws/r/network_interface.markdown @@ -32,6 +32,7 @@ The following arguments are supported: * `private_ips` - (Optional) List of private IPs to assign to the ENI. * `security_groups` - (Optional) List of security group IDs to assign to the ENI. * `attachment` - (Required) Block to define the attachment of the ENI. Documented below. +* `source_dest_check` - (Optional) Whether to enable source destination checking for the ENI. Default true. * `tags` - (Optional) A mapping of tags to assign to the resource. The `attachment` block supports: @@ -47,5 +48,6 @@ The following attributes are exported: * `private_ips` - List of private IPs assigned to the ENI. * `security_groups` - List of security groups attached to the ENI. * `attachment` - Block defining the attachment of the ENI. +* `source_dest_check` - Whether source destination checking is enabled * `tags` - Tags assigned to the ENI. diff --git a/website/source/docs/providers/aws/r/s3_bucket.html.markdown b/website/source/docs/providers/aws/r/s3_bucket.html.markdown index 4f367d4f8..f30c153c5 100644 --- a/website/source/docs/providers/aws/r/s3_bucket.html.markdown +++ b/website/source/docs/providers/aws/r/s3_bucket.html.markdown @@ -67,3 +67,4 @@ The following attributes are exported: * `hosted_zone_id` - The [Route 53 Hosted Zone ID](http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_website_region_endpoints) for this bucket's region. * `region` - The AWS region this bucket resides in. * `website_endpoint` - The website endpoint, if the bucket is configured with a website. If not, this will be an empty string. +* `website_domain` - The domain of the website endpoint, if the bucket is configured with a website. If not, this will be an empty string. This is used to create Route 53 alias records. diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index cf1034231..a212bb697 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -1,296 +1,406 @@ <% wrap_layout :inner do %> - <% content_for :sidebar do %> - + <% end %> + + <%= yield %> <% end %> diff --git a/website/source/layouts/cloudstack.erb b/website/source/layouts/cloudstack.erb index 1cf60b787..84c52e2df 100644 --- a/website/source/layouts/cloudstack.erb +++ b/website/source/layouts/cloudstack.erb @@ -54,7 +54,7 @@ > - cloudstack_secondary_ipaddress + cloudstack_secondary_ipaddress >