diff --git a/CHANGELOG.md b/CHANGELOG.md index bcf9e9f55..50b5a41c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,16 @@ -## 0.3.1 (unreleased) +## 0.3.2 (unreleased) + + + +## 0.3.1 (October 21, 2014) + +IMPROVEMENTS: + + * providers/aws: Support tags for security groups. + * providers/google: Add "external\_address" to network attributes [GH-454] + * providers/google: External address is used as default connection host. [GH-454] + * providers/heroku: Support `locked` and `personal` booleans on organization + settings. [GH-406] BUG FIXES: @@ -9,10 +21,34 @@ BUG FIXES: computed so the value is still unknown. * core: If a resource fails to create and has provisioners, it is marked as tainted. [GH-434] + * core: Set types are validated to be sets. [GH-413] + * core: String types are validated properly. [GH-460] + * core: Fix crash case when destroying with tainted resources. [GH-412] + * core: Don't execute provisioners in some cases on destroy. + * core: Inherited provider configurations will be properly interpolated. [GH-418] + * core: Refresh works properly if there are outputs that depend on resources + that aren't yet created. [GH-483] * providers/aws: Refresh of launch configs and autoscale groups load the correct data and don't incorrectly recreate themselves. [GH-425] * providers/aws: Fix case where ELB would incorrectly plan to modify listeners (with the same data) in some cases. + * providers/aws: Retry destroying internet gateway for some amount of time + if there is a dependency violation since it is probably just eventual + consistency (public facing resources being destroyed). [GH-447] + * providers/aws: Retry deleting security groups for some amount of time + if there is a dependency violation since it is probably just eventual + consistency. [GH-436] + * providers/aws: Retry deleting subnet for some amount of time if there is a + dependency violation since probably asynchronous destroy events take + place still. [GH-449] + * providers/aws: Drain autoscale groups before deleting. [GH-435] + * providers/aws: Fix crash case if launch config is manually deleted. [GH-421] + * providers/aws: Disassociate EIP before destroying. + * providers/aws: ELB treats subnets as a set. + * providers/aws: Fix case where in a destroy/create tags weren't reapplied. [GH-464] + * providers/aws: Fix incorrect/erroneous apply cases around security group + rules. [GH-457] + * providers/consul: Fix regression where `key` param changed to `keys. [GH-475] ## 0.3.0 (October 14, 2014) diff --git a/builtin/providers/aws/resource_aws_autoscaling_group.go b/builtin/providers/aws/resource_aws_autoscaling_group.go index 1aebfd9a0..54c744989 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group.go @@ -3,8 +3,10 @@ package aws import ( "fmt" "log" + "time" "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/mitchellh/goamz/autoscaling" ) @@ -196,6 +198,22 @@ func resourceAwsAutoscalingGroupDelete(d *schema.ResourceData, meta interface{}) p := meta.(*ResourceProvider) autoscalingconn := p.autoscalingconn + // Read the autoscaling group first. If it doesn't exist, we're done. + // We need the group in order to check if there are instances attached. + // If so, we need to remove those first. + g, err := getAwsAutoscalingGroup(d, meta) + if err != nil { + return err + } + if g == nil { + return nil + } + if len(g.Instances) > 0 || g.DesiredCapacity > 0 { + if err := resourceAwsAutoscalingGroupDrain(d, meta); err != nil { + return err + } + } + log.Printf("[DEBUG] AutoScaling Group destroy: %v", d.Id()) deleteopts := autoscaling.DeleteAutoScalingGroup{Name: d.Id()} @@ -208,8 +226,7 @@ func resourceAwsAutoscalingGroupDelete(d *schema.ResourceData, meta interface{}) deleteopts.ForceDelete = true } - _, err := autoscalingconn.DeleteAutoScalingGroup(&deleteopts) - if err != nil { + if _, err := autoscalingconn.DeleteAutoScalingGroup(&deleteopts); err != nil { autoscalingerr, ok := err.(*autoscaling.Error) if ok && autoscalingerr.Code == "InvalidGroup.NotFound" { return nil @@ -222,35 +239,14 @@ func resourceAwsAutoscalingGroupDelete(d *schema.ResourceData, meta interface{}) } func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) error { - p := meta.(*ResourceProvider) - autoscalingconn := p.autoscalingconn - - describeOpts := autoscaling.DescribeAutoScalingGroups{ - Names: []string{d.Id()}, - } - - log.Printf("[DEBUG] AutoScaling Group describe configuration: %#v", describeOpts) - describeGroups, err := autoscalingconn.DescribeAutoScalingGroups(&describeOpts) + g, err := getAwsAutoscalingGroup(d, meta) if err != nil { - autoscalingerr, ok := err.(*autoscaling.Error) - if ok && autoscalingerr.Code == "InvalidGroup.NotFound" { - d.SetId("") - return nil - } - - return fmt.Errorf("Error retrieving AutoScaling groups: %s", err) + return err } - - // Verify AWS returned our sg - if len(describeGroups.AutoScalingGroups) != 1 || - describeGroups.AutoScalingGroups[0].Name != d.Id() { - if err != nil { - return fmt.Errorf("Unable to find AutoScaling group: %#v", describeGroups.AutoScalingGroups) - } + if g == nil { + return nil } - g := describeGroups.AutoScalingGroups[0] - d.Set("availability_zones", g.AvailabilityZones) d.Set("default_cooldown", g.DefaultCooldown) d.Set("desired_capacity", g.DesiredCapacity) @@ -265,3 +261,76 @@ func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) e return nil } + +func getAwsAutoscalingGroup( + d *schema.ResourceData, + meta interface{}) (*autoscaling.AutoScalingGroup, error) { + p := meta.(*ResourceProvider) + autoscalingconn := p.autoscalingconn + + describeOpts := autoscaling.DescribeAutoScalingGroups{ + Names: []string{d.Id()}, + } + + log.Printf("[DEBUG] AutoScaling Group describe configuration: %#v", describeOpts) + describeGroups, err := autoscalingconn.DescribeAutoScalingGroups(&describeOpts) + if err != nil { + autoscalingerr, ok := err.(*autoscaling.Error) + if ok && autoscalingerr.Code == "InvalidGroup.NotFound" { + d.SetId("") + return nil, nil + } + + return nil, fmt.Errorf("Error retrieving AutoScaling groups: %s", err) + } + + // Verify AWS returned our sg + if len(describeGroups.AutoScalingGroups) != 1 || + describeGroups.AutoScalingGroups[0].Name != d.Id() { + if err != nil { + return nil, fmt.Errorf( + "Unable to find AutoScaling group: %#v", + describeGroups.AutoScalingGroups) + } + } + + return &describeGroups.AutoScalingGroups[0], nil +} + +func resourceAwsAutoscalingGroupDrain(d *schema.ResourceData, meta interface{}) error { + p := meta.(*ResourceProvider) + autoscalingconn := p.autoscalingconn + + // First, set the capacity to zero so the group will drain + log.Printf("[DEBUG] Reducing autoscaling group capacity to zero") + opts := autoscaling.UpdateAutoScalingGroup{ + Name: d.Id(), + DesiredCapacity: 0, + MinSize: 0, + MaxSize: 0, + SetDesiredCapacity: true, + SetMinSize: true, + SetMaxSize: true, + } + if _, err := autoscalingconn.UpdateAutoScalingGroup(&opts); err != nil { + return fmt.Errorf("Error setting capacity to zero to drain: %s", err) + } + + // Next, wait for the autoscale group to drain + log.Printf("[DEBUG] Waiting for group to have zero instances") + return resource.Retry(10*time.Minute, func() error { + g, err := getAwsAutoscalingGroup(d, meta) + if err != nil { + return resource.RetryError{err} + } + if g == nil { + return nil + } + + if len(g.Instances) == 0 { + return nil + } + + return fmt.Errorf("group still has %d instances", len(g.Instances)) + }) +} diff --git a/builtin/providers/aws/resource_aws_eip.go b/builtin/providers/aws/resource_aws_eip.go index d7c9382b4..5a70ebacb 100644 --- a/builtin/providers/aws/resource_aws_eip.go +++ b/builtin/providers/aws/resource_aws_eip.go @@ -36,6 +36,11 @@ func resourceAwsEip() *schema.Resource { Computed: true, }, + "association_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "domain": &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -127,15 +132,55 @@ func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error { } func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{"pending"}, - Target: "destroyed", - Refresh: resourceEipDeleteRefreshFunc(d, meta), - Timeout: 3 * time.Minute, - MinTimeout: 1 * time.Second, + p := meta.(*ResourceProvider) + ec2conn := p.ec2conn + + if err := resourceAwsEipRead(d, meta); err != nil { + return err } - _, err := stateConf.WaitForState() - return err + if d.Id() == "" { + // This might happen from the read + return nil + } + + // If we are attached to an instance, detach first. + if d.Get("instance").(string) != "" { + log.Printf("[DEBUG] Disassociating EIP: %s", d.Id()) + var err error + switch resourceAwsEipDomain(d) { + case "vpc": + _, err = ec2conn.DisassociateAddress(d.Get("association_id").(string)) + case "standard": + _, err = ec2conn.DisassociateAddressClassic(d.Get("public_ip").(string)) + } + if err != nil { + return err + } + } + + domain := resourceAwsEipDomain(d) + return resource.Retry(3*time.Minute, func() error { + var err error + switch domain { + case "vpc": + log.Printf( + "[DEBUG] EIP release (destroy) address allocation: %v", + d.Id()) + _, err = ec2conn.ReleaseAddress(d.Id()) + case "standard": + log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id()) + _, err = ec2conn.ReleasePublicAddress(d.Id()) + } + + if err == nil { + return nil + } + if _, ok := err.(*ec2.Error); !ok { + return resource.RetryError{err} + } + + return err + }) } func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error { @@ -178,6 +223,7 @@ func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error { address := describeAddresses.Addresses[0] + d.Set("association_id", address.AssociationId) d.Set("instance", address.InstanceId) d.Set("public_ip", address.PublicIp) d.Set("private_ip", address.PrivateIpAddress) @@ -196,38 +242,3 @@ func resourceAwsEipDomain(d *schema.ResourceData) string { return "standard" } - -func resourceEipDeleteRefreshFunc( - d *schema.ResourceData, - meta interface{}) resource.StateRefreshFunc { - p := meta.(*ResourceProvider) - ec2conn := p.ec2conn - domain := resourceAwsEipDomain(d) - - return func() (interface{}, string, error) { - var err error - if domain == "vpc" { - log.Printf( - "[DEBUG] EIP release (destroy) address allocation: %v", - d.Id()) - _, err = ec2conn.ReleaseAddress(d.Id()) - } else { - log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id()) - _, err = ec2conn.ReleasePublicAddress(d.Id()) - } - - if err == nil { - return d, "destroyed", nil - } - - ec2err, ok := err.(*ec2.Error) - if !ok { - return d, "error", err - } - if ec2err.Code != "InvalidIPAddress.InUse" { - return d, "error", err - } - - return d, "pending", nil - } -} diff --git a/builtin/providers/aws/resource_aws_elb.go b/builtin/providers/aws/resource_aws_elb.go index 9fffd321e..353a675d6 100644 --- a/builtin/providers/aws/resource_aws_elb.go +++ b/builtin/providers/aws/resource_aws_elb.go @@ -59,11 +59,14 @@ func resourceAwsElb() *schema.Resource { // TODO: could be not ForceNew "subnets": &schema.Schema{ - Type: schema.TypeList, + Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, ForceNew: true, Computed: true, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, }, // TODO: could be not ForceNew @@ -200,7 +203,7 @@ func resourceAwsElbCreate(d *schema.ResourceData, meta interface{}) error { } if v, ok := d.GetOk("subnets"); ok { - elbOpts.Subnets = expandStringList(v.([]interface{})) + elbOpts.Subnets = expandStringList(v.(*schema.Set).List()) } log.Printf("[DEBUG] ELB create configuration: %#v", elbOpts) diff --git a/builtin/providers/aws/resource_aws_instance_test.go b/builtin/providers/aws/resource_aws_instance_test.go index 48c21eb4e..ff7087732 100644 --- a/builtin/providers/aws/resource_aws_instance_test.go +++ b/builtin/providers/aws/resource_aws_instance_test.go @@ -64,8 +64,6 @@ func TestAccAWSInstance_normal(t *testing.T) { }) } - - func TestAccAWSInstance_sourceDestCheck(t *testing.T) { var v ec2.Instance diff --git a/builtin/providers/aws/resource_aws_internet_gateway.go b/builtin/providers/aws/resource_aws_internet_gateway.go index a13892c60..fc23ebb06 100644 --- a/builtin/providers/aws/resource_aws_internet_gateway.go +++ b/builtin/providers/aws/resource_aws_internet_gateway.go @@ -81,14 +81,26 @@ func resource_aws_internet_gateway_destroy( } log.Printf("[INFO] Deleting Internet Gateway: %s", s.ID) - if _, err := ec2conn.DeleteInternetGateway(s.ID); err != nil { - ec2err, ok := err.(*ec2.Error) - if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" { - return nil + return resource.Retry(5*time.Minute, func() error { + _, err := ec2conn.DeleteInternetGateway(s.ID) + if err != nil { + ec2err, ok := err.(*ec2.Error) + if !ok { + return err + } + + switch ec2err.Code { + case "InvalidInternetGatewayID.NotFound": + return nil + case "DependencyViolation": + return err // retry + default: + return resource.RetryError{err} + } } return fmt.Errorf("Error deleting internet gateway: %s", err) - } + }) // Wait for the internet gateway to actually delete log.Printf("[DEBUG] Waiting for internet gateway (%s) to delete", s.ID) diff --git a/builtin/providers/aws/resource_aws_launch_configuration.go b/builtin/providers/aws/resource_aws_launch_configuration.go index 584bad164..0cb80c395 100644 --- a/builtin/providers/aws/resource_aws_launch_configuration.go +++ b/builtin/providers/aws/resource_aws_launch_configuration.go @@ -144,15 +144,16 @@ func resourceAwsLaunchConfigurationRead(d *schema.ResourceData, meta interface{} if err != nil { return fmt.Errorf("Error retrieving launch configuration: %s", err) } + if len(describConfs.LaunchConfigurations) == 0 { + d.SetId("") + return nil + } // Verify AWS returned our launch configuration - if len(describConfs.LaunchConfigurations) != 1 || - describConfs.LaunchConfigurations[0].Name != d.Id() { - if err != nil { - return fmt.Errorf( - "Unable to find launch configuration: %#v", - describConfs.LaunchConfigurations) - } + if describConfs.LaunchConfigurations[0].Name != d.Id() { + return fmt.Errorf( + "Unable to find launch configuration: %#v", + describConfs.LaunchConfigurations) } lc := describConfs.LaunchConfigurations[0] diff --git a/builtin/providers/aws/resource_aws_security_group.go b/builtin/providers/aws/resource_aws_security_group.go index 88627a0b8..4be7ca484 100644 --- a/builtin/providers/aws/resource_aws_security_group.go +++ b/builtin/providers/aws/resource_aws_security_group.go @@ -66,14 +66,18 @@ func resourceAwsSecurityGroup() *schema.Resource { }, "security_groups": &schema.Schema{ - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, }, "self": &schema.Schema{ Type: schema.TypeBool, Optional: true, + Default: false, }, }, }, @@ -84,6 +88,8 @@ func resourceAwsSecurityGroup() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + "tags": tagsSchema(), }, } } @@ -110,7 +116,7 @@ func resourceAwsSecurityGroupIngressHash(v interface{}) int { } } if v, ok := m["security_groups"]; ok { - vs := v.([]interface{}) + vs := v.(*schema.Set).List() s := make([]string, len(vs)) for i, raw := range vs { s[i] = raw.(string) @@ -226,6 +232,12 @@ func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) er } } + if err := setTags(ec2conn, d); err != nil { + return err + } else { + d.SetPartial("tags") + } + return nil } @@ -235,15 +247,28 @@ func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) er log.Printf("[DEBUG] Security Group destroy: %v", d.Id()) - _, err := ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: d.Id()}) - if err != nil { - ec2err, ok := err.(*ec2.Error) - if ok && ec2err.Code == "InvalidGroup.NotFound" { - return nil - } - } + return resource.Retry(5*time.Minute, func() error { + _, err := ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: d.Id()}) + if err != nil { + ec2err, ok := err.(*ec2.Error) + if !ok { + return err + } - return err + switch ec2err.Code { + case "InvalidGroup.NotFound": + return nil + case "DependencyViolation": + // If it is a dependency violation, we want to retry + return err + default: + // Any other error, we want to quit the retry loop immediately + return resource.RetryError{err} + } + } + + return nil + }) } func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { @@ -262,15 +287,28 @@ func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) erro sg := sgRaw.(*ec2.SecurityGroupInfo) // Gather our ingress rules - ingressRules := make([]map[string]interface{}, len(sg.IPPerms)) - for i, perm := range sg.IPPerms { - n := make(map[string]interface{}) - n["from_port"] = perm.FromPort - n["protocol"] = perm.Protocol - n["to_port"] = perm.ToPort + ingressMap := make(map[string]map[string]interface{}) + for _, perm := range sg.IPPerms { + k := fmt.Sprintf("%s-%d-%d", perm.Protocol, perm.FromPort, perm.ToPort) + m, ok := ingressMap[k] + if !ok { + m = make(map[string]interface{}) + ingressMap[k] = m + } + + m["from_port"] = perm.FromPort + m["to_port"] = perm.ToPort + m["protocol"] = perm.Protocol if len(perm.SourceIPs) > 0 { - n["cidr_blocks"] = perm.SourceIPs + raw, ok := m["cidr_blocks"] + if !ok { + raw = make([]string, 0, len(perm.SourceIPs)) + } + list := raw.([]string) + + list = append(list, perm.SourceIPs...) + m["cidr_blocks"] = list } var groups []string @@ -280,14 +318,24 @@ func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) erro for i, id := range groups { if id == d.Id() { groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1] - n["self"] = true + m["self"] = true } } - if len(groups) > 0 { - n["security_groups"] = groups - } - ingressRules[i] = n + if len(groups) > 0 { + raw, ok := m["security_groups"] + if !ok { + raw = make([]string, 0, len(groups)) + } + list := raw.([]string) + + list = append(list, groups...) + m["security_groups"] = list + } + } + ingressRules := make([]map[string]interface{}, 0, len(ingressMap)) + for _, m := range ingressMap { + ingressRules = append(ingressRules, m) } d.Set("description", sg.Description) @@ -295,6 +343,7 @@ func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) erro d.Set("vpc_id", sg.VpcId) d.Set("owner_id", sg.OwnerId) d.Set("ingress", ingressRules) + d.Set("tags", tagsToMap(sg.Tags)) return nil } diff --git a/builtin/providers/aws/resource_aws_security_group_test.go b/builtin/providers/aws/resource_aws_security_group_test.go index 0774b71dd..5ff4d4c49 100644 --- a/builtin/providers/aws/resource_aws_security_group_test.go +++ b/builtin/providers/aws/resource_aws_security_group_test.go @@ -276,6 +276,34 @@ func testAccCheckAWSSecurityGroupAttributes(group *ec2.SecurityGroupInfo) resour } } +func TestAccAWSSecurityGroup_tags(t *testing.T) { + var group ec2.SecurityGroupInfo + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSSecurityGroupConfigTags, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group.foo", &group), + testAccCheckTags(&group.Tags, "foo", "bar"), + ), + }, + + resource.TestStep{ + Config: testAccAWSSecurityGroupConfigTagsUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group.foo", &group), + testAccCheckTags(&group.Tags, "foo", ""), + testAccCheckTags(&group.Tags, "bar", "baz"), + ), + }, + }, + }) +} + func testAccCheckAWSSecurityGroupAttributesChanged(group *ec2.SecurityGroupInfo) resource.TestCheckFunc { return func(s *terraform.State) error { p := []ec2.IPPerm{ @@ -432,3 +460,19 @@ resource "aws_security_group" "web" { } } ` + +const testAccAWSSecurityGroupConfigTags = ` +resource "aws_security_group" "foo" { + tags { + foo = "bar" + } +} +` + +const testAccAWSSecurityGroupConfigTagsUpdate = ` +resource "aws_security_group" "foo" { + tags { + bar = "baz" + } +} +` diff --git a/builtin/providers/aws/resource_aws_subnet.go b/builtin/providers/aws/resource_aws_subnet.go index 35b6fba3c..249d8bf2c 100644 --- a/builtin/providers/aws/resource_aws_subnet.go +++ b/builtin/providers/aws/resource_aws_subnet.go @@ -88,18 +88,26 @@ func resource_aws_subnet_destroy( ec2conn := p.ec2conn log.Printf("[INFO] Deleting Subnet: %s", s.ID) - f := func() error { + return resource.Retry(5*time.Minute, func() error { _, err := ec2conn.DeleteSubnet(s.ID) - return err - } - if err := resource.Retry(10*time.Second, f); err != nil { - ec2err, ok := err.(*ec2.Error) - if ok && ec2err.Code == "InvalidSubnetID.NotFound" { - return nil + if err != nil { + ec2err, ok := err.(*ec2.Error) + if !ok { + return err + } + + switch ec2err.Code { + case "InvalidSubnetID.NotFound": + return nil + case "DependencyViolation": + return err // retry + default: + return resource.RetryError{err} + } } return fmt.Errorf("Error deleting subnet: %s", err) - } + }) // Wait for the Subnet to actually delete log.Printf("[DEBUG] Waiting for subnet (%s) to delete", s.ID) diff --git a/builtin/providers/aws/structure.go b/builtin/providers/aws/structure.go index c349991cb..3af176c2a 100644 --- a/builtin/providers/aws/structure.go +++ b/builtin/providers/aws/structure.go @@ -3,6 +3,7 @@ package aws import ( "strings" + "github.com/hashicorp/terraform/helper/schema" "github.com/mitchellh/goamz/ec2" "github.com/mitchellh/goamz/elb" ) @@ -48,7 +49,7 @@ func expandIPPerms(id string, configured []interface{}) []ec2.IPPerm { var groups []string if raw, ok := m["security_groups"]; ok { - list := raw.([]interface{}) + list := raw.(*schema.Set).List() for _, v := range list { groups = append(groups, v.(string)) } diff --git a/builtin/providers/aws/structure_test.go b/builtin/providers/aws/structure_test.go index 7621535fb..e230cdf91 100644 --- a/builtin/providers/aws/structure_test.go +++ b/builtin/providers/aws/structure_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/hashicorp/terraform/flatmap" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" "github.com/mitchellh/goamz/ec2" "github.com/mitchellh/goamz/elb" ) @@ -33,16 +35,20 @@ func testConf() map[string]string { } func Test_expandIPPerms(t *testing.T) { + hash := func(v interface{}) int { + return hashcode.String(v.(string)) + } + expanded := []interface{}{ map[string]interface{}{ "protocol": "icmp", "from_port": 1, "to_port": -1, "cidr_blocks": []interface{}{"0.0.0.0/0"}, - "security_groups": []interface{}{ + "security_groups": schema.NewSet(hash, []interface{}{ "sg-11111", "foo/sg-22222", - }, + }), }, map[string]interface{}{ "protocol": "icmp", @@ -60,13 +66,13 @@ func Test_expandIPPerms(t *testing.T) { ToPort: -1, SourceIPs: []string{"0.0.0.0/0"}, SourceGroups: []ec2.UserSecurityGroup{ - ec2.UserSecurityGroup{ - Id: "sg-11111", - }, ec2.UserSecurityGroup{ OwnerId: "foo", Id: "sg-22222", }, + ec2.UserSecurityGroup{ + Id: "sg-11111", + }, }, }, ec2.IPPerm{ diff --git a/builtin/providers/consul/resource_consul_keys.go b/builtin/providers/consul/resource_consul_keys.go index 4aef3df5e..45647b8ff 100644 --- a/builtin/providers/consul/resource_consul_keys.go +++ b/builtin/providers/consul/resource_consul_keys.go @@ -31,7 +31,7 @@ func resourceConsulKeys() *schema.Resource { Optional: true, }, - "keys": &schema.Schema{ + "key": &schema.Schema{ Type: schema.TypeSet, Optional: true, Elem: &schema.Resource{ @@ -60,6 +60,7 @@ func resourceConsulKeys() *schema.Resource { "delete": &schema.Schema{ Type: schema.TypeBool, Optional: true, + Default: false, }, }, }, @@ -113,19 +114,15 @@ func resourceConsulKeysCreate(d *schema.ResourceData, meta interface{}) error { vars := make(map[string]string) // Extract the keys - keys := d.Get("keys").(*schema.Set).List() + keys := d.Get("key").(*schema.Set).List() for _, raw := range keys { key, path, sub, err := parse_key(raw) if err != nil { return err } - if valueRaw, ok := sub["value"]; ok { - value, ok := valueRaw.(string) - if !ok { - return fmt.Errorf("Failed to get value for key '%s'", key) - } - + value := sub["value"].(string) + if value != "" { log.Printf("[DEBUG] Setting key '%s' to '%v' in %s", path, value, dc) pair := consulapi.KVPair{Key: path, Value: []byte(value)} if _, err := kv.Put(&pair, &wOpts); err != nil { @@ -142,14 +139,13 @@ func resourceConsulKeysCreate(d *schema.ResourceData, meta interface{}) error { } value := attribute_value(sub, key, pair) vars[key] = value - sub["value"] = value } } // Update the resource d.SetId("consul") d.Set("datacenter", dc) - d.Set("keys", keys) + d.Set("key", keys) d.Set("var", vars) return nil } @@ -178,7 +174,7 @@ func resourceConsulKeysRead(d *schema.ResourceData, meta interface{}) error { vars := make(map[string]string) // Extract the keys - keys := d.Get("keys").(*schema.Set).List() + keys := d.Get("key").(*schema.Set).List() for _, raw := range keys { key, path, sub, err := parse_key(raw) if err != nil { @@ -197,7 +193,7 @@ func resourceConsulKeysRead(d *schema.ResourceData, meta interface{}) error { } // Update the resource - d.Set("keys", keys) + d.Set("key", keys) d.Set("var", vars) return nil } @@ -223,7 +219,7 @@ func resourceConsulKeysDelete(d *schema.ResourceData, meta interface{}) error { wOpts := consulapi.WriteOptions{Datacenter: dc, Token: token} // Extract the keys - keys := d.Get("keys").(*schema.Set).List() + keys := d.Get("key").(*schema.Set).List() for _, raw := range keys { _, path, sub, err := parse_key(raw) if err != nil { diff --git a/builtin/providers/consul/resource_consul_keys_test.go b/builtin/providers/consul/resource_consul_keys_test.go index 2f52be83b..c0f132486 100644 --- a/builtin/providers/consul/resource_consul_keys_test.go +++ b/builtin/providers/consul/resource_consul_keys_test.go @@ -30,7 +30,7 @@ func TestAccConsulKeys(t *testing.T) { func testAccCheckConsulKeysDestroy(s *terraform.State) error { kv := testAccProvider.Meta().(*consulapi.Client).KV() - opts := &consulapi.QueryOptions{Datacenter: "nyc1"} + opts := &consulapi.QueryOptions{Datacenter: "nyc3"} pair, _, err := kv.Get("test/set", opts) if err != nil { return err @@ -44,7 +44,7 @@ func testAccCheckConsulKeysDestroy(s *terraform.State) error { func testAccCheckConsulKeysExists() resource.TestCheckFunc { return func(s *terraform.State) error { kv := testAccProvider.Meta().(*consulapi.Client).KV() - opts := &consulapi.QueryOptions{Datacenter: "nyc1"} + opts := &consulapi.QueryOptions{Datacenter: "nyc3"} pair, _, err := kv.Get("test/set", opts) if err != nil { return err @@ -78,7 +78,7 @@ func testAccCheckConsulKeysValue(n, attr, val string) resource.TestCheckFunc { const testAccConsulKeysConfig = ` resource "consul_keys" "app" { - datacenter = "nyc1" + datacenter = "nyc3" key { name = "time" path = "global/time" diff --git a/builtin/providers/consul/resource_provider_test.go b/builtin/providers/consul/resource_provider_test.go index 5e2746f98..ad2b29898 100644 --- a/builtin/providers/consul/resource_provider_test.go +++ b/builtin/providers/consul/resource_provider_test.go @@ -3,6 +3,7 @@ package consul import ( "testing" + "github.com/armon/consul-api" "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" @@ -16,6 +17,13 @@ func init() { testAccProviders = map[string]terraform.ResourceProvider{ "consul": testAccProvider, } + + // Use the demo address for the acceptance tests + testAccProvider.ConfigureFunc = func(d *schema.ResourceData) (interface{}, error) { + conf := consulapi.DefaultConfig() + conf.Address = "demo.consul.io:80" + return consulapi.NewClient(conf) + } } func TestResourceProvider(t *testing.T) { @@ -33,7 +41,7 @@ func TestResourceProvider_Configure(t *testing.T) { raw := map[string]interface{}{ "address": "demo.consul.io:80", - "datacenter": "nyc1", + "datacenter": "nyc3", } rawConfig, err := config.NewRawConfig(raw) diff --git a/builtin/providers/google/resource_compute_instance.go b/builtin/providers/google/resource_compute_instance.go index 696d8ca91..f6b0fde7a 100644 --- a/builtin/providers/google/resource_compute_instance.go +++ b/builtin/providers/google/resource_compute_instance.go @@ -100,6 +100,11 @@ func resourceComputeInstance() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + "external_address": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, }, }, }, @@ -335,12 +340,27 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error d.Set("can_ip_forward", instance.CanIpForward) // Set the networks + externalIP := "" for i, iface := range instance.NetworkInterfaces { prefix := fmt.Sprintf("network.%d", i) d.Set(prefix+".name", iface.Name) + + // Use the first external IP found for the default connection info. + natIP := resourceInstanceNatIP(iface) + if externalIP == "" && natIP != "" { + externalIP = natIP + } + d.Set(prefix+".external_address", natIP) + d.Set(prefix+".internal_address", iface.NetworkIP) } + // Initialize the connection info + d.SetConnInfo(map[string]string{ + "type": "ssh", + "host": externalIP, + }) + // Set the metadata fingerprint if there is one. if instance.Metadata != nil { d.Set("metadata_fingerprint", instance.Metadata.Fingerprint) @@ -506,3 +526,16 @@ func resourceInstanceTags(d *schema.ResourceData) *compute.Tags { return tags } + +// resourceInstanceNatIP acquires the first NatIP with a "ONE_TO_ONE_NAT" type +// in the compute.NetworkInterface's AccessConfigs. +func resourceInstanceNatIP(iface *compute.NetworkInterface) (natIP string) { + for _, config := range iface.AccessConfigs { + if config.Type == "ONE_TO_ONE_NAT" { + natIP = config.NatIP + break + } + } + + return natIP +} diff --git a/builtin/providers/heroku/resource_heroku_app.go b/builtin/providers/heroku/resource_heroku_app.go index 41a42d494..aaa31436c 100644 --- a/builtin/providers/heroku/resource_heroku_app.go +++ b/builtin/providers/heroku/resource_heroku_app.go @@ -95,17 +95,40 @@ func resourceHerokuApp() *schema.Resource { }, "organization": &schema.Schema{ - Type: schema.TypeString, Description: "Name of Organization to create application in. Leave blank for personal apps.", + Type: schema.TypeList, Optional: true, ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "locked": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + + "personal": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + }, + }, }, }, } } func switchHerokuAppCreate(d *schema.ResourceData, meta interface{}) error { - if _, ok := d.GetOk("organization"); ok { + orgCount := d.Get("organization.#").(int) + if orgCount > 1 { + return fmt.Errorf("Error Creating Heroku App: Only 1 Heroku Organization is permitted") + } + + if _, ok := d.GetOk("organization.0.name"); ok { return resourceHerokuOrgAppCreate(d, meta) } else { return resourceHerokuAppCreate(d, meta) @@ -157,12 +180,26 @@ func resourceHerokuOrgAppCreate(d *schema.ResourceData, meta interface{}) error client := meta.(*heroku.Service) // Build up our creation options opts := heroku.OrganizationAppCreateOpts{} - if v, ok := d.GetOk("organization"); ok { + + if v := d.Get("organization.0.name"); v != nil { vs := v.(string) - log.Printf("[DEBUG] App name: %s", vs) + log.Printf("[DEBUG] Organization name: %s", vs) opts.Organization = &vs } - if v, ok := d.GetOk("name"); ok { + + if v := d.Get("organization.0.personal"); v != nil { + vs := v.(bool) + log.Printf("[DEBUG] Organization Personal: %s", vs) + opts.Personal = &vs + } + + if v := d.Get("organization.0.locked"); v != nil { + vs := v.(bool) + log.Printf("[DEBUG] Organization locked: %s", vs) + opts.Locked = &vs + } + + if v := d.Get("name"); v != nil { vs := v.(string) log.Printf("[DEBUG] App name: %s", vs) opts.Name = &vs diff --git a/helper/resource/wait.go b/helper/resource/wait.go index 5fcd59c26..326555053 100644 --- a/helper/resource/wait.go +++ b/helper/resource/wait.go @@ -18,14 +18,29 @@ func Retry(timeout time.Duration, f RetryFunc) error { MinTimeout: 500 * time.Millisecond, Refresh: func() (interface{}, string, error) { err = f() - if err != nil { - return 42, "error", nil + if err == nil { + return 42, "success", nil } - return 42, "success", nil + if rerr, ok := err.(RetryError); ok { + err = rerr.Err + return nil, "quit", err + } + + return 42, "error", nil }, } c.WaitForState() return err } + +// RetryError, if returned, will quit the retry immediately with the +// Err. +type RetryError struct { + Err error +} + +func (e RetryError) Error() string { + return e.Err.Error() +} diff --git a/helper/resource/wait_test.go b/helper/resource/wait_test.go index 34ca6ce18..f79d27656 100644 --- a/helper/resource/wait_test.go +++ b/helper/resource/wait_test.go @@ -37,3 +37,26 @@ func TestRetry_timeout(t *testing.T) { t.Fatal("should error") } } + +func TestRetry_error(t *testing.T) { + t.Parallel() + + expected := fmt.Errorf("nope") + f := func() error { + return RetryError{expected} + } + + errCh := make(chan error) + go func() { + errCh <- Retry(1*time.Second, f) + }() + + select { + case err := <-errCh: + if err != expected { + t.Fatalf("bad: %#v", err) + } + case <-time.After(5 * time.Second): + t.Fatal("timeout") + } +} diff --git a/helper/schema/resource.go b/helper/schema/resource.go index 16c49f230..7f5736bc6 100644 --- a/helper/schema/resource.go +++ b/helper/schema/resource.go @@ -91,6 +91,12 @@ func (r *Resource) Apply( if !d.RequiresNew() { return nil, nil } + + // Reset the data to be stateless since we just destroyed + data, err = schemaMap(r.Schema).Data(nil, d) + if err != nil { + return nil, err + } } err = nil diff --git a/helper/schema/resource_data.go b/helper/schema/resource_data.go index deb331a05..23be6f290 100644 --- a/helper/schema/resource_data.go +++ b/helper/schema/resource_data.go @@ -43,10 +43,11 @@ type getSource byte const ( getSourceState getSource = 1 << iota getSourceConfig - getSourceDiff getSourceSet - getSourceExact - getSourceMax = getSourceSet + getSourceExact // Only get from the _exact_ level + getSourceDiff // Apply the diff on top our level + getSourceLevelMask getSource = getSourceState | getSourceConfig | getSourceSet + getSourceMax getSource = getSourceSet ) // getResult is the internal structure that is generated when a Get @@ -82,7 +83,7 @@ func (d *ResourceData) Get(key string) interface{} { // set and the new value is. This is common, for example, for boolean // fields which have a zero value of false. func (d *ResourceData) GetChange(key string) (interface{}, interface{}) { - o, n := d.getChange(key, getSourceConfig, getSourceDiff) + o, n := d.getChange(key, getSourceConfig, getSourceConfig|getSourceDiff) return o.Value, n.Value } @@ -93,7 +94,7 @@ func (d *ResourceData) GetChange(key string) (interface{}, interface{}) { // The first result will not necessarilly be nil if the value doesn't exist. // The second result should be checked to determine this information. func (d *ResourceData) GetOk(key string) (interface{}, bool) { - r := d.getRaw(key, getSourceSet) + r := d.getRaw(key, getSourceSet|getSourceDiff) return r.Value, r.Exists } @@ -289,12 +290,21 @@ func (d *ResourceData) getSet( // entire set must come from set, diff, state, etc. So we go backwards // and once we get a result, we take it. Or, we never get a result. var raw getResult - for listSource := source; listSource > 0; listSource >>= 1 { - if source&getSourceExact != 0 && listSource != source { + sourceLevel := source & getSourceLevelMask + sourceFlags := source & ^getSourceLevelMask + for listSource := sourceLevel; listSource > 0; listSource >>= 1 { + // If we're already asking for an exact source and it doesn't + // match, then leave since the original source was the match. + if sourceFlags&getSourceExact != 0 && listSource != sourceLevel { break } - raw = d.getList(k, nil, schema, listSource|getSourceExact) + // The source we get from is the level we're on, plus the flags + // we had, plus the exact flag. + getSource := listSource + getSource |= sourceFlags + getSource |= getSourceExact + raw = d.getList(k, nil, schema, getSource) if raw.Exists { break } @@ -384,11 +394,13 @@ func (d *ResourceData) getMap( resultSet := false prefix := k + "." - exact := source&getSourceExact != 0 - source &^= getSourceExact + flags := source & ^getSourceLevelMask + level := source & getSourceLevelMask + exact := flags&getSourceExact != 0 + diff := flags&getSourceDiff != 0 - if !exact || source == getSourceState { - if d.state != nil && source >= getSourceState { + if !exact || level == getSourceState { + if d.state != nil && level >= getSourceState { for k, _ := range d.state.Attributes { if !strings.HasPrefix(k, prefix) { continue @@ -401,7 +413,7 @@ func (d *ResourceData) getMap( } } - if d.config != nil && source == getSourceConfig { + if d.config != nil && level == getSourceConfig { // For config, we always set the result to exactly what was requested if mraw, ok := d.config.Get(k); ok { result = make(map[string]interface{}) @@ -433,42 +445,47 @@ func (d *ResourceData) getMap( } } - if !exact || source == getSourceDiff { - if d.diff != nil && source >= getSourceDiff { - for k, v := range d.diff.Attributes { - if !strings.HasPrefix(k, prefix) { - continue - } - resultSet = true + if d.diff != nil && diff { + for k, v := range d.diff.Attributes { + if !strings.HasPrefix(k, prefix) { + continue + } + resultSet = true - single := k[len(prefix):] + single := k[len(prefix):] - if v.NewRemoved { - delete(result, single) - } else { - result[single] = d.getPrimitive(k, nil, elemSchema, source).Value - } + if v.NewRemoved { + delete(result, single) + } else { + result[single] = d.getPrimitive(k, nil, elemSchema, source).Value } } } - if !exact || source == getSourceSet { - if d.setMap != nil && source >= getSourceSet { + if !exact || level == getSourceSet { + if d.setMap != nil && level >= getSourceSet { cleared := false - for k, _ := range d.setMap { - if !strings.HasPrefix(k, prefix) { - continue - } + if v, ok := d.setMap[k]; ok && v == "" { + // We've cleared the map + result = make(map[string]interface{}) resultSet = true + } else { + for k, _ := range d.setMap { + if !strings.HasPrefix(k, prefix) { + continue + } + resultSet = true - if !cleared { - // We clear the results if they are in the set map - result = make(map[string]interface{}) - cleared = true + if !cleared { + // We clear the results if they are in the set map + result = make(map[string]interface{}) + cleared = true + } + + single := k[len(prefix):] + result[single] = d.getPrimitive( + k, nil, elemSchema, source).Value } - - single := k[len(prefix):] - result[single] = d.getPrimitive(k, nil, elemSchema, source).Value } } } @@ -577,8 +594,10 @@ func (d *ResourceData) getPrimitive( var result string var resultProcessed interface{} var resultComputed, resultSet bool - exact := source&getSourceExact != 0 - source &^= getSourceExact + flags := source & ^getSourceLevelMask + source = source & getSourceLevelMask + exact := flags&getSourceExact != 0 + diff := flags&getSourceDiff != 0 if !exact || source == getSourceState { if d.state != nil && source >= getSourceState { @@ -604,28 +623,26 @@ func (d *ResourceData) getPrimitive( resultComputed = d.config.IsComputed(k) } - if !exact || source == getSourceDiff { - if d.diff != nil && source >= getSourceDiff { - attrD, ok := d.diff.Attributes[k] - if ok { - if !attrD.NewComputed { - result = attrD.New - if attrD.NewExtra != nil { - // If NewExtra != nil, then we have processed data as the New, - // so we store that but decode the unprocessed data into result - resultProcessed = result + if d.diff != nil && diff { + attrD, ok := d.diff.Attributes[k] + if ok { + if !attrD.NewComputed { + result = attrD.New + if attrD.NewExtra != nil { + // If NewExtra != nil, then we have processed data as the New, + // so we store that but decode the unprocessed data into result + resultProcessed = result - err := mapstructure.WeakDecode(attrD.NewExtra, &result) - if err != nil { - panic(err) - } + err := mapstructure.WeakDecode(attrD.NewExtra, &result) + if err != nil { + panic(err) } - - resultSet = true - } else { - result = "" - resultSet = false } + + resultSet = true + } else { + result = "" + resultSet = false } } } @@ -773,14 +790,6 @@ func (d *ResourceData) setMapValue( return fmt.Errorf("%s: full map must be set, no a single element", k) } - // Delete any prior map set - /* - v := d.getMap(k, nil, schema, getSourceSet) - for subKey, _ := range v.(map[string]interface{}) { - delete(d.setMap, fmt.Sprintf("%s.%s", k, subKey)) - } - */ - v := reflect.ValueOf(value) if v.Kind() != reflect.Map { return fmt.Errorf("%s: must be a map", k) @@ -794,6 +803,13 @@ func (d *ResourceData) setMapValue( vs[mk.String()] = mv.Interface() } + if len(vs) == 0 { + // The empty string here means the map is removed. + d.setMap[k] = "" + return nil + } + + delete(d.setMap, k) for subKey, v := range vs { err := d.set(fmt.Sprintf("%s.%s", k, subKey), nil, elemSchema, v) if err != nil { @@ -1101,14 +1117,14 @@ func (d *ResourceData) stateSingle( func (d *ResourceData) stateSource(prefix string) getSource { // If we're not doing a partial apply, then get the set level if !d.partial { - return getSourceSet + return getSourceSet | getSourceDiff } // Otherwise, only return getSourceSet if its in the partial map. // Otherwise we use state level only. for k, _ := range d.partialMap { if strings.HasPrefix(prefix, k) { - return getSourceSet + return getSourceSet | getSourceDiff } } diff --git a/helper/schema/resource_data_test.go b/helper/schema/resource_data_test.go index 01f3616b3..751555964 100644 --- a/helper/schema/resource_data_test.go +++ b/helper/schema/resource_data_test.go @@ -527,6 +527,58 @@ func TestResourceDataGet(t *testing.T) { Value: []interface{}{80}, }, + + { + Schema: map[string]*Schema{ + "data": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "index": &Schema{ + Type: TypeInt, + Required: true, + }, + + "value": &Schema{ + Type: TypeString, + Required: true, + }, + }, + }, + Set: func(a interface{}) int { + m := a.(map[string]interface{}) + return m["index"].(int) + }, + }, + }, + + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "data.#": "1", + "data.0.index": "10", + "data.0.value": "50", + }, + }, + + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "data.0.value": &terraform.ResourceAttrDiff{ + Old: "50", + New: "80", + }, + }, + }, + + Key: "data", + + Value: []interface{}{ + map[string]interface{}{ + "index": 10, + "value": "80", + }, + }, + }, } for i, tc := range cases { @@ -860,6 +912,31 @@ func TestResourceDataHasChange(t *testing.T) { Change: false, }, + + { + Schema: map[string]*Schema{ + "tags": &Schema{ + Type: TypeMap, + Optional: true, + Computed: true, + }, + }, + + State: nil, + + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "tags.Name": &terraform.ResourceAttrDiff{ + Old: "foo", + New: "foo", + }, + }, + }, + + Key: "tags", + + Change: true, + }, } for i, tc := range cases { @@ -2141,6 +2218,63 @@ func TestResourceDataState(t *testing.T) { }, }, }, + + // Maps + { + Schema: map[string]*Schema{ + "tags": &Schema{ + Type: TypeMap, + Optional: true, + Computed: true, + }, + }, + + State: nil, + + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "tags.Name": &terraform.ResourceAttrDiff{ + Old: "", + New: "foo", + }, + }, + }, + + Result: &terraform.InstanceState{ + Attributes: map[string]string{ + "tags.Name": "foo", + }, + }, + }, + + { + Schema: map[string]*Schema{ + "tags": &Schema{ + Type: TypeMap, + Optional: true, + Computed: true, + }, + }, + + State: nil, + + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "tags.Name": &terraform.ResourceAttrDiff{ + Old: "", + New: "foo", + }, + }, + }, + + Set: map[string]interface{}{ + "tags": map[string]string{}, + }, + + Result: &terraform.InstanceState{ + Attributes: map[string]string{}, + }, + }, } for i, tc := range cases { diff --git a/helper/schema/resource_test.go b/helper/schema/resource_test.go index 32ad34454..845bd264d 100644 --- a/helper/schema/resource_test.go +++ b/helper/schema/resource_test.go @@ -95,6 +95,77 @@ func TestResourceApply_destroy(t *testing.T) { } } +func TestResourceApply_destroyCreate(t *testing.T) { + r := &Resource{ + Schema: map[string]*Schema{ + "foo": &Schema{ + Type: TypeInt, + Optional: true, + }, + + "tags": &Schema{ + Type: TypeMap, + Optional: true, + Computed: true, + }, + }, + } + + change := false + r.Create = func(d *ResourceData, m interface{}) error { + change = d.HasChange("tags") + d.SetId("foo") + return nil + } + r.Delete = func(d *ResourceData, m interface{}) error { + return nil + } + + var s *terraform.InstanceState = &terraform.InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "foo": "bar", + "tags.Name": "foo", + }, + } + + d := &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "foo": &terraform.ResourceAttrDiff{ + New: "42", + RequiresNew: true, + }, + "tags.Name": &terraform.ResourceAttrDiff{ + Old: "foo", + New: "foo", + RequiresNew: true, + }, + }, + } + + actual, err := r.Apply(s, d, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !change { + t.Fatal("should have change") + } + + expected := &terraform.InstanceState{ + ID: "foo", + Attributes: map[string]string{ + "id": "foo", + "foo": "42", + "tags.Name": "foo", + }, + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +} + func TestResourceApply_destroyPartial(t *testing.T) { r := &Resource{ Schema: map[string]*Schema{ diff --git a/helper/schema/schema.go b/helper/schema/schema.go index d074e9512..7cd0fe711 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -205,7 +205,7 @@ func (m schemaMap) Diff( } for k, schema := range m { - err := m.diff(k, schema, result, d) + err := m.diff(k, schema, result, d, false) if err != nil { return nil, err } @@ -225,7 +225,7 @@ func (m schemaMap) Diff( // Perform the diff again for k, schema := range m { - err := m.diff(k, schema, result2, d) + err := m.diff(k, schema, result2, d, false) if err != nil { return nil, err } @@ -346,7 +346,7 @@ func (m schemaMap) Input( "%s: %s", k, err) } - c.Raw[k] = value + c.Config[k] = value } return c, nil @@ -427,7 +427,8 @@ func (m schemaMap) diff( k string, schema *Schema, diff *terraform.InstanceDiff, - d *ResourceData) error { + d *ResourceData, + all bool) error { var err error switch schema.Type { case TypeBool: @@ -435,13 +436,13 @@ func (m schemaMap) diff( case TypeInt: fallthrough case TypeString: - err = m.diffString(k, schema, diff, d) + err = m.diffString(k, schema, diff, d, all) case TypeList: - err = m.diffList(k, schema, diff, d) + err = m.diffList(k, schema, diff, d, all) case TypeMap: - err = m.diffMap(k, schema, diff, d) + err = m.diffMap(k, schema, diff, d, all) case TypeSet: - err = m.diffSet(k, schema, diff, d) + err = m.diffSet(k, schema, diff, d, all) default: err = fmt.Errorf("%s: unknown type %#v", k, schema.Type) } @@ -453,7 +454,8 @@ func (m schemaMap) diffList( k string, schema *Schema, diff *terraform.InstanceDiff, - d *ResourceData) error { + d *ResourceData, + all bool) error { o, n, _, computedList := d.diffChange(k) nSet := n != nil @@ -481,7 +483,7 @@ func (m schemaMap) diffList( // If the new value was set, and the two are equal, then we're done. // We have to do this check here because sets might be NOT // reflect.DeepEqual so we need to wait until we get the []interface{} - if nSet && reflect.DeepEqual(os, vs) { + if !all && nSet && reflect.DeepEqual(os, vs) { return nil } @@ -502,7 +504,7 @@ func (m schemaMap) diffList( // If the counts are not the same, then record that diff changed := oldLen != newLen computed := oldLen == 0 && newLen == 0 && schema.Computed - if changed || computed { + if changed || computed || all { countSchema := &Schema{ Type: TypeInt, Computed: schema.Computed, @@ -539,7 +541,7 @@ func (m schemaMap) diffList( // just diff each. for i := 0; i < maxLen; i++ { subK := fmt.Sprintf("%s.%d", k, i) - err := m.diff(subK, &t2, diff, d) + err := m.diff(subK, &t2, diff, d, all) if err != nil { return err } @@ -549,7 +551,7 @@ func (m schemaMap) diffList( for i := 0; i < maxLen; i++ { for k2, schema := range t.Schema { subK := fmt.Sprintf("%s.%d.%s", k, i, k2) - err := m.diff(subK, schema, diff, d) + err := m.diff(subK, schema, diff, d, all) if err != nil { return err } @@ -566,8 +568,8 @@ func (m schemaMap) diffMap( k string, schema *Schema, diff *terraform.InstanceDiff, - d *ResourceData) error { - //elemSchema := &Schema{Type: TypeString} + d *ResourceData, + all bool) error { prefix := k + "." // First get all the values from the state @@ -580,12 +582,17 @@ func (m schemaMap) diffMap( return fmt.Errorf("%s: %s", k, err) } + // If the new map is nil and we're computed, then ignore it. + if n == nil && schema.Computed { + return nil + } + // Now we compare, preferring values from the config map for k, v := range configMap { old := stateMap[k] delete(stateMap, k) - if old == v { + if old == v && !all { continue } @@ -608,15 +615,35 @@ func (m schemaMap) diffSet( k string, schema *Schema, diff *terraform.InstanceDiff, - d *ResourceData) error { - return m.diffList(k, schema, diff, d) + d *ResourceData, + all bool) error { + if !all { + // This is a bit strange, but we expect the entire set to be in the diff, + // so we first diff the set normally but with a new diff. Then, if + // there IS any change, we just set the change to the entire list. + tempD := new(terraform.InstanceDiff) + tempD.Attributes = make(map[string]*terraform.ResourceAttrDiff) + if err := m.diffList(k, schema, tempD, d, false); err != nil { + return err + } + + // If we had no changes, then we're done + if tempD.Empty() { + return nil + } + } + + // We have changes, so re-run the diff, but set a flag to force + // getting all diffs, even if there is no change. + return m.diffList(k, schema, diff, d, true) } func (m schemaMap) diffString( k string, schema *Schema, diff *terraform.InstanceDiff, - d *ResourceData) error { + d *ResourceData, + all bool) error { var originalN interface{} var os, ns string o, n, _, _ := d.diffChange(k) @@ -641,7 +668,7 @@ func (m schemaMap) diffString( return fmt.Errorf("%s: %s", k, err) } - if os == ns { + if os == ns && !all { // They're the same value. If there old value is not blank or we // have an ID, then return right away since we're already setup. if os != "" || d.Id() != "" { @@ -767,6 +794,44 @@ func (m schemaMap) validateList( return ws, es } +func (m schemaMap) validateMap( + k string, + raw interface{}, + schema *Schema, + c *terraform.ResourceConfig) ([]string, []error) { + // We use reflection to verify the slice because you can't + // case to []interface{} unless the slice is exactly that type. + rawV := reflect.ValueOf(raw) + switch rawV.Kind() { + case reflect.Map: + case reflect.Slice: + default: + return nil, []error{fmt.Errorf( + "%s: should be a map", k)} + } + + // If it is not a slice, it is valid + if rawV.Kind() != reflect.Slice { + return nil, nil + } + + // It is a slice, verify that all the elements are maps + raws := make([]interface{}, rawV.Len()) + for i, _ := range raws { + raws[i] = rawV.Index(i).Interface() + } + + for _, raw := range raws { + v := reflect.ValueOf(raw) + if v.Kind() != reflect.Map { + return nil, []error{fmt.Errorf( + "%s: should be a map", k)} + } + } + + return nil, nil +} + func (m schemaMap) validateObject( k string, schema map[string]*Schema, @@ -819,14 +884,32 @@ func (m schemaMap) validatePrimitive( } switch schema.Type { + case TypeSet: + fallthrough case TypeList: return m.validateList(k, raw, schema, c) + case TypeMap: + return m.validateMap(k, raw, schema, c) + case TypeBool: + // Verify that we can parse this as the correct type + var n bool + if err := mapstructure.WeakDecode(raw, &n); err != nil { + return nil, []error{err} + } case TypeInt: // Verify that we can parse this as an int var n int if err := mapstructure.WeakDecode(raw, &n); err != nil { return nil, []error{err} } + case TypeString: + // Verify that we can parse this as a string + var n string + if err := mapstructure.WeakDecode(raw, &n); err != nil { + return nil, []error{err} + } + default: + panic(fmt.Sprintf("Unknown validation type: %s", schema.Type)) } return nil, nil diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index 66830869c..5351f61d7 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -98,6 +98,38 @@ func TestSchemaMap_Diff(t *testing.T) { Err: false, }, + // Computed, but set in config + { + Schema: map[string]*Schema{ + "availability_zone": &Schema{ + Type: TypeString, + Optional: true, + Computed: true, + }, + }, + + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "availability_zone": "foo", + }, + }, + + Config: map[string]interface{}{ + "availability_zone": "bar", + }, + + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "availability_zone": &terraform.ResourceAttrDiff{ + Old: "foo", + New: "bar", + }, + }, + }, + + Err: false, + }, + // Default { Schema: map[string]*Schema{ @@ -342,6 +374,31 @@ func TestSchemaMap_Diff(t *testing.T) { Err: false, }, + /* + * Bool + */ + { + Schema: map[string]*Schema{ + "delete": &Schema{ + Type: TypeBool, + Optional: true, + Default: false, + }, + }, + + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "delete": "false", + }, + }, + + Config: nil, + + Diff: nil, + + Err: false, + }, + /* * List decode */ @@ -808,6 +865,14 @@ func TestSchemaMap_Diff(t *testing.T) { Old: "2", New: "3", }, + "ports.0": &terraform.ResourceAttrDiff{ + Old: "1", + New: "1", + }, + "ports.1": &terraform.ResourceAttrDiff{ + Old: "2", + New: "2", + }, "ports.2": &terraform.ResourceAttrDiff{ Old: "", New: "5", @@ -1166,6 +1231,67 @@ func TestSchemaMap_Diff(t *testing.T) { Err: false, }, + { + Schema: map[string]*Schema{ + "vars": &Schema{ + Type: TypeMap, + Optional: true, + Computed: true, + }, + }, + + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "vars.foo": "bar", + }, + }, + + Config: map[string]interface{}{ + "vars": []interface{}{ + map[string]interface{}{ + "bar": "baz", + }, + }, + }, + + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "vars.foo": &terraform.ResourceAttrDiff{ + Old: "bar", + New: "", + NewRemoved: true, + }, + "vars.bar": &terraform.ResourceAttrDiff{ + Old: "", + New: "baz", + }, + }, + }, + + Err: false, + }, + + { + Schema: map[string]*Schema{ + "vars": &Schema{ + Type: TypeMap, + Computed: true, + }, + }, + + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "vars.foo": "bar", + }, + }, + + Config: nil, + + Diff: nil, + + Err: false, + }, + { Schema: map[string]*Schema{ "config_vars": &Schema{ @@ -1413,9 +1539,7 @@ func TestSchemaMap_Input(t *testing.T) { "availability_zone": "foo", }, - Result: map[string]interface{}{ - "availability_zone": "bar", - }, + Result: map[string]interface{}{}, Err: false, }, @@ -1494,14 +1618,16 @@ func TestSchemaMap_Input(t *testing.T) { input := new(terraform.MockUIInput) input.InputReturnMap = tc.Input - actual, err := schemaMap(tc.Schema).Input( - input, terraform.NewResourceConfig(c)) + rc := terraform.NewResourceConfig(c) + rc.Config = make(map[string]interface{}) + + actual, err := schemaMap(tc.Schema).Input(input, rc) if (err != nil) != tc.Err { t.Fatalf("#%d err: %s", i, err) } - if !reflect.DeepEqual(tc.Result, actual.Raw) { - t.Fatalf("#%d: bad:\n\n%#v", i, actual.Raw) + if !reflect.DeepEqual(tc.Result, actual.Config) { + t.Fatalf("#%d: bad:\n\n%#v", i, actual.Config) } } } @@ -1788,6 +1914,25 @@ func TestSchemaMap_Validate(t *testing.T) { Err: true, }, + { + Schema: map[string]*Schema{ + "user_data": &Schema{ + Type: TypeString, + Optional: true, + }, + }, + + Config: map[string]interface{}{ + "user_data": []interface{}{ + map[string]interface{}{ + "foo": "bar", + }, + }, + }, + + Err: true, + }, + // Bad type, interpolated { Schema: map[string]*Schema{ @@ -1970,6 +2115,91 @@ func TestSchemaMap_Validate(t *testing.T) { Err: true, }, + + // Not a set + { + Schema: map[string]*Schema{ + "ports": &Schema{ + Type: TypeSet, + Required: true, + Elem: &Schema{Type: TypeInt}, + Set: func(a interface{}) int { + return a.(int) + }, + }, + }, + + Config: map[string]interface{}{ + "ports": "foo", + }, + + Err: true, + }, + + // Maps + { + Schema: map[string]*Schema{ + "user_data": &Schema{ + Type: TypeMap, + Optional: true, + }, + }, + + Config: map[string]interface{}{ + "user_data": "foo", + }, + + Err: true, + }, + + { + Schema: map[string]*Schema{ + "user_data": &Schema{ + Type: TypeMap, + Optional: true, + }, + }, + + Config: map[string]interface{}{ + "user_data": []interface{}{ + map[string]interface{}{ + "foo": "bar", + }, + }, + }, + }, + + { + Schema: map[string]*Schema{ + "user_data": &Schema{ + Type: TypeMap, + Optional: true, + }, + }, + + Config: map[string]interface{}{ + "user_data": map[string]interface{}{ + "foo": "bar", + }, + }, + }, + + { + Schema: map[string]*Schema{ + "user_data": &Schema{ + Type: TypeMap, + Optional: true, + }, + }, + + Config: map[string]interface{}{ + "user_data": []interface{}{ + "foo", + }, + }, + + Err: true, + }, } for i, tc := range cases { diff --git a/helper/schema/set.go b/helper/schema/set.go index 21e329f22..92966ea59 100644 --- a/helper/schema/set.go +++ b/helper/schema/set.go @@ -14,6 +14,17 @@ type Set struct { once sync.Once } +// NewSet is a convenience method for creating a new set with the given +// items. +func NewSet(f SchemaSetFunc, items []interface{}) *Set { + s := &Set{F: f} + for _, i := range items { + s.Add(i) + } + + return s +} + // Add adds an item to the set if it isn't already in the set. func (s *Set) Add(item interface{}) { s.add(item) diff --git a/main.go b/main.go index 46f143ba5..abd0e18e3 100644 --- a/main.go +++ b/main.go @@ -84,6 +84,9 @@ func realMain() int { func wrappedMain() int { log.SetOutput(os.Stderr) + log.Printf( + "[INFO] Terraform version: %s %s %s", + Version, VersionPrerelease, GitCommit) // Load the configuration config := BuiltinConfig diff --git a/terraform/context.go b/terraform/context.go index 3565fbe66..e2d8bbd33 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -531,6 +531,14 @@ func (c *walkContext) Walk() error { outputs := make(map[string]string) for _, o := range conf.Outputs { if err := c.computeVars(o.RawConfig, nil); err != nil { + // If we're refreshing, then we ignore output errors. This is + // properly not fully the correct behavior, but fixes a range + // of issues right now. As we expand test cases to find the + // correct behavior, this will likely be removed. + if c.Operation == walkRefresh { + continue + } + return err } vraw := o.RawConfig.Config()["value"] @@ -600,6 +608,7 @@ func (c *walkContext) inputWalkFn() depgraph.WalkFunc { raw = sharedProvider.Config.RawConfig } rc := NewResourceConfig(raw) + rc.Config = make(map[string]interface{}) // Wrap the input into a namespace input := &PrefixUIInput{ @@ -617,8 +626,8 @@ func (c *walkContext) inputWalkFn() depgraph.WalkFunc { return fmt.Errorf( "Error configuring %s: %s", k, err) } - if newc != nil { - configs[k] = newc.Raw + if newc != nil && len(newc.Config) > 0 { + configs[k] = newc.Config } } @@ -700,7 +709,7 @@ func (c *walkContext) applyWalkFn() depgraph.WalkFunc { // We create a new instance if there was no ID // previously or the diff requires re-creating the // underlying instance - createNew := is.ID == "" || diff.RequiresNew() + createNew := (is.ID == "" && !diff.Destroy) || diff.RequiresNew() // With the completed diff, apply! log.Printf("[DEBUG] %s: Executing Apply", r.Id) @@ -1034,18 +1043,20 @@ func (c *walkContext) validateWalkFn() depgraph.WalkFunc { // Interpolate the count and verify it is non-negative rc := NewResourceConfig(rn.Config.RawCount) rc.interpolate(c, rn.Resource) - count, err := rn.Config.Count() - if err == nil { - if count < 0 { - err = fmt.Errorf( - "%s error: count must be positive", rn.Resource.Id) + if !rc.IsComputed(rn.Config.RawCount.Key) { + count, err := rn.Config.Count() + if err == nil { + if count < 0 { + err = fmt.Errorf( + "%s error: count must be positive", rn.Resource.Id) + } + } + if err != nil { + l.Lock() + defer l.Unlock() + meta.Errs = append(meta.Errs, err) + return nil } - } - if err != nil { - l.Lock() - defer l.Unlock() - meta.Errs = append(meta.Errs, err) - return nil } return c.genericWalkResource(rn, walkFn) @@ -1201,9 +1212,17 @@ func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc { } for k, p := range sharedProvider.Providers { - // Merge the configurations to get what we use to configure with + // Interpolate our own configuration before merging + if sharedProvider.Config != nil { + rc := NewResourceConfig(sharedProvider.Config.RawConfig) + rc.interpolate(c, nil) + } + + // Merge the configurations to get what we use to configure + // with. We don't need to interpolate this because the + // lines above verify that all parents are interpolated + // properly. rc := sharedProvider.MergeConfig(false, cs[k]) - rc.interpolate(c, nil) log.Printf("[INFO] Configuring provider: %s", k) err := p.Configure(rc) @@ -1269,6 +1288,20 @@ func (c *walkContext) genericWalkResource( rc := NewResourceConfig(rn.Config.RawCount) rc.interpolate(c, rn.Resource) + // If we're validating, then we set the count to 1 if it is computed + if c.Operation == walkValidate { + if key := rn.Config.RawCount.Key; rc.IsComputed(key) { + // Preserve the old value so that we reset it properly + old := rn.Config.RawCount.Raw[key] + defer func() { + rn.Config.RawCount.Raw[key] = old + }() + + // Set th count to 1 for validation purposes + rn.Config.RawCount.Raw[key] = "1" + } + } + // Expand the node to the actual resources g, err := rn.Expand() if err != nil { @@ -1515,7 +1548,7 @@ func (c *walkContext) computeVars( continue } - if c.Operation == walkValidate { + if _, ok := vs[n]; !ok && c.Operation == walkValidate { vs[n] = config.UnknownVariableValue continue } @@ -1578,19 +1611,25 @@ func (c *walkContext) computeResourceVariable( // Get the relevant module module := c.Context.state.ModuleByPath(c.Path) - r, ok := module.Resources[id] - if !ok { - if v.Multi && v.Index == 0 { + var r *ResourceState + if module != nil { + var ok bool + r, ok = module.Resources[id] + if !ok && v.Multi && v.Index == 0 { r, ok = module.Resources[v.ResourceId()] } if !ok { - return "", fmt.Errorf( - "Resource '%s' not found for variable '%s'", - id, - v.FullKey()) + r = nil } } + if r == nil { + return "", fmt.Errorf( + "Resource '%s' not found for variable '%s'", + id, + v.FullKey()) + } + if r.Primary == nil { goto MISSING } diff --git a/terraform/context_test.go b/terraform/context_test.go index dd19c654e..5ad260763 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -1,6 +1,7 @@ package terraform import ( + "bytes" "fmt" "os" "reflect" @@ -127,6 +128,50 @@ func TestContextValidate_countNegative(t *testing.T) { } } +func TestContextValidate_countVariable(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "apply-count-variable") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + for _, err := range e { + t.Errorf("bad: %s", err) + } + t.FailNow() + } +} + +func TestContextValidate_countVariableNoDefault(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-count-variable") + c := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 1 { + for _, err := range e { + t.Errorf("bad: %s", err) + } + t.FailNow() + } +} + func TestContextValidate_moduleBadResource(t *testing.T) { m := testModule(t, "validate-module-bad-rc") p := testProvider("aws") @@ -602,11 +647,11 @@ func TestContextInput_provider(t *testing.T) { var actual interface{} p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) { - c.Raw["foo"] = "bar" + c.Config["foo"] = "bar" return c, nil } p.ConfigureFn = func(c *ResourceConfig) error { - actual = c.Raw["foo"] + actual = c.Config["foo"] return nil } @@ -648,11 +693,11 @@ func TestContextInput_providerId(t *testing.T) { return nil, err } - c.Raw["foo"] = v + c.Config["foo"] = v return c, nil } p.ConfigureFn = func(c *ResourceConfig) error { - actual = c.Raw["foo"] + actual = c.Config["foo"] return nil } @@ -700,11 +745,11 @@ func TestContextInput_providerOnly(t *testing.T) { var actual interface{} p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) { - c.Raw["foo"] = "bar" + c.Config["foo"] = "bar" return c, nil } p.ConfigureFn = func(c *ResourceConfig) error { - actual = c.Raw["foo"] + actual = c.Config["foo"] return nil } @@ -732,6 +777,54 @@ func TestContextInput_providerOnly(t *testing.T) { } } +func TestContextInput_providerVars(t *testing.T) { + input := new(MockUIInput) + m := testModule(t, "input-provider-with-vars") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "foo": "bar", + }, + UIInput: input, + }) + + input.InputReturnMap = map[string]string{ + "var.foo": "bar", + } + + var actual interface{} + p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) { + c.Config["bar"] = "baz" + return c, nil + } + p.ConfigureFn = func(c *ResourceConfig) error { + actual, _ = c.Get("foo") + return nil + } + + if err := ctx.Input(InputModeStd); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Apply(); err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(actual, "bar") { + t.Fatalf("bad: %#v", actual) + } +} + func TestContextInput_varOnly(t *testing.T) { input := new(MockUIInput) m := testModule(t, "input-provider-vars") @@ -1203,6 +1296,35 @@ func TestContextApply_countTainted(t *testing.T) { t.Fatalf("bad: \n%s", actual) } } + +func TestContextApply_countVariable(t *testing.T) { + m := testModule(t, "apply-count-variable") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyCountVariableStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + func TestContextApply_module(t *testing.T) { m := testModule(t, "apply-module") p := testProvider("aws") @@ -1994,6 +2116,71 @@ func TestContextApply_destroyOrphan(t *testing.T) { } } +func TestContextApply_destroyTaintedProvisioner(t *testing.T) { + m := testModule(t, "apply-destroy-provisioner") + p := testProvider("aws") + pr := testProvisioner() + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + + called := false + pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error { + called = true + return nil + } + + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Type: "aws_instance", + Tainted: []*InstanceState{ + &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "id": "bar", + }, + }, + }, + }, + }, + }, + }, + } + + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + State: s, + }) + + if _, err := ctx.Plan(&PlanOpts{Destroy: true}); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + if called { + t.Fatal("provisioner should not be called") + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace("") + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + func TestContextApply_error(t *testing.T) { errored := false @@ -2765,6 +2952,54 @@ func TestContextPlan_moduleProviderDefaults(t *testing.T) { } } +func TestContextPlan_moduleProviderDefaultsVar(t *testing.T) { + var l sync.Mutex + var calls []string + + m := testModule(t, "plan-module-provider-defaults-var") + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": func() (ResourceProvider, error) { + l.Lock() + defer l.Unlock() + + p := testProvider("aws") + p.ConfigureFn = func(c *ResourceConfig) error { + var buf bytes.Buffer + if v, ok := c.Get("from"); ok { + buf.WriteString(v.(string) + "\n") + } + if v, ok := c.Get("to"); ok { + buf.WriteString(v.(string) + "\n") + } + + calls = append(calls, buf.String()) + return nil + } + p.DiffFn = testDiffFn + return p, nil + }, + }, + Variables: map[string]string{ + "foo": "root", + }, + }) + + _, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + expected := []string{ + "root\n", + "root\nchild\n", + } + if !reflect.DeepEqual(calls, expected) { + t.Fatalf("BAD: %#v", calls) + } +} + func TestContextPlan_moduleVar(t *testing.T) { m := testModule(t, "plan-module-var") p := testProvider("aws") @@ -3595,6 +3830,36 @@ func TestContextPlan_multiple_taint(t *testing.T) { } } +func TestContextPlan_provider(t *testing.T) { + m := testModule(t, "plan-provider") + p := testProvider("aws") + p.DiffFn = testDiffFn + + var value interface{} + p.ConfigureFn = func(c *ResourceConfig) error { + value, _ = c.Get("foo") + return nil + } + + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "foo": "bar", + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + if value != "bar" { + t.Fatalf("bad: %#v", value) + } +} + func TestContextPlan_varMultiCountOne(t *testing.T) { m := testModule(t, "plan-var-multi-count-one") p := testProvider("aws") @@ -3845,6 +4110,22 @@ func TestContextRefresh_modules(t *testing.T) { } } +func TestContextRefresh_moduleInputComputedOutput(t *testing.T) { + m := testModule(t, "refresh-module-input-computed-output") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Refresh(); err != nil { + t.Fatalf("err: %s", err) + } +} + // GH-70 func TestContextRefresh_noState(t *testing.T) { p := testProvider("aws") @@ -3866,6 +4147,46 @@ func TestContextRefresh_noState(t *testing.T) { } } +func TestContextRefresh_outputPartial(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-output-partial") + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + }) + + p.RefreshFn = nil + p.RefreshReturn = nil + + s, err := ctx.Refresh() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(s.String()) + expected := strings.TrimSpace(testContextRefreshOutputPartialStr) + if actual != expected { + t.Fatalf("bad:\n\n%s\n\n%s", actual, expected) + } +} + func TestContextRefresh_state(t *testing.T) { p := testProvider("aws") m := testModule(t, "refresh-basic") @@ -4176,6 +4497,10 @@ module.child: ID = new ` +const testContextRefreshOutputPartialStr = ` + +` + const testContextRefreshTaintedStr = ` aws_instance.web: (1 tainted) ID = diff --git a/terraform/graph.go b/terraform/graph.go index e8741e5d6..0e561c882 100644 --- a/terraform/graph.go +++ b/terraform/graph.go @@ -1594,14 +1594,15 @@ func graphRemoveInvalidDeps(g *depgraph.Graph) { func (p *graphSharedProvider) MergeConfig( raw bool, override map[string]interface{}) *ResourceConfig { var rawMap map[string]interface{} - if override != nil { - rawMap = override - } else if p.Config != nil { - rawMap = p.Config.RawConfig.Raw + if p.Config != nil { + rawMap = p.Config.RawConfig.Config() } if rawMap == nil { rawMap = make(map[string]interface{}) } + for k, v := range override { + rawMap[k] = v + } // Merge in all the parent configurations if p.Parent != nil { @@ -1667,11 +1668,6 @@ func (n *GraphNodeResource) Expand() (*depgraph.Graph, error) { return n.finalizeGraph(g, false) } - if n.State != nil { - // Add the tainted resources - graphAddTainted(g, n.State) - } - return n.finalizeGraph(g, true) } diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 7447010b7..543862950 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -228,6 +228,17 @@ const testTerraformApplyCountTaintedStr = ` ` +const testTerraformApplyCountVariableStr = ` +aws_instance.foo.0: + ID = foo + foo = foo + type = aws_instance +aws_instance.foo.1: + ID = foo + foo = foo + type = aws_instance +` + const testTerraformApplyMinimalStr = ` aws_instance.bar: ID = foo diff --git a/terraform/test-fixtures/apply-count-variable/main.tf b/terraform/test-fixtures/apply-count-variable/main.tf new file mode 100644 index 000000000..6f322f218 --- /dev/null +++ b/terraform/test-fixtures/apply-count-variable/main.tf @@ -0,0 +1,8 @@ +variable "foo" { + default = "2" +} + +resource "aws_instance" "foo" { + foo = "foo" + count = "${var.foo}" +} diff --git a/terraform/test-fixtures/apply-destroy-provisioner/main.tf b/terraform/test-fixtures/apply-destroy-provisioner/main.tf new file mode 100644 index 000000000..398e2deec --- /dev/null +++ b/terraform/test-fixtures/apply-destroy-provisioner/main.tf @@ -0,0 +1,5 @@ +resource "aws_instance" "foo" { + id = "foo" + + provisioner "shell" {} +} diff --git a/terraform/test-fixtures/input-provider-with-vars/main.tf b/terraform/test-fixtures/input-provider-with-vars/main.tf new file mode 100644 index 000000000..d8f931115 --- /dev/null +++ b/terraform/test-fixtures/input-provider-with-vars/main.tf @@ -0,0 +1,7 @@ +variable "foo" {} + +provider "aws" { + foo = "${var.foo}" +} + +resource "aws_instance" "foo" {} diff --git a/terraform/test-fixtures/plan-module-provider-defaults-var/child/main.tf b/terraform/test-fixtures/plan-module-provider-defaults-var/child/main.tf new file mode 100644 index 000000000..5ce4f55fe --- /dev/null +++ b/terraform/test-fixtures/plan-module-provider-defaults-var/child/main.tf @@ -0,0 +1,8 @@ +provider "aws" { + from = "child" + to = "child" +} + +resource "aws_instance" "foo" { + from = "child" +} diff --git a/terraform/test-fixtures/plan-module-provider-defaults-var/main.tf b/terraform/test-fixtures/plan-module-provider-defaults-var/main.tf new file mode 100644 index 000000000..83b241154 --- /dev/null +++ b/terraform/test-fixtures/plan-module-provider-defaults-var/main.tf @@ -0,0 +1,7 @@ +module "child" { + source = "./child" +} + +provider "aws" { + from = "${var.foo}" +} diff --git a/terraform/test-fixtures/plan-provider/main.tf b/terraform/test-fixtures/plan-provider/main.tf new file mode 100644 index 000000000..8010f70ae --- /dev/null +++ b/terraform/test-fixtures/plan-provider/main.tf @@ -0,0 +1,7 @@ +variable "foo" {} + +provider "aws" { + foo = "${var.foo}" +} + +resource "aws_instance" "bar" {} diff --git a/terraform/test-fixtures/refresh-module-input-computed-output/child/main.tf b/terraform/test-fixtures/refresh-module-input-computed-output/child/main.tf new file mode 100644 index 000000000..fff03a60f --- /dev/null +++ b/terraform/test-fixtures/refresh-module-input-computed-output/child/main.tf @@ -0,0 +1,9 @@ +variable "input" {} + +resource "aws_instance" "foo" { + foo = "${var.input}" +} + +output "foo" { + value = "${aws_instance.foo.foo}" +} diff --git a/terraform/test-fixtures/refresh-module-input-computed-output/main.tf b/terraform/test-fixtures/refresh-module-input-computed-output/main.tf new file mode 100644 index 000000000..3a0576434 --- /dev/null +++ b/terraform/test-fixtures/refresh-module-input-computed-output/main.tf @@ -0,0 +1,8 @@ +module "child" { + input = "${aws_instance.bar.foo}" + source = "./child" +} + +resource "aws_instance" "bar" { + compute = "foo" +} diff --git a/terraform/test-fixtures/refresh-output-partial/main.tf b/terraform/test-fixtures/refresh-output-partial/main.tf new file mode 100644 index 000000000..36ce289a3 --- /dev/null +++ b/terraform/test-fixtures/refresh-output-partial/main.tf @@ -0,0 +1,7 @@ +resource "aws_instance" "foo" {} + +resource "aws_instance" "web" {} + +output "foo" { + value = "${aws_instance.web.foo}" +} diff --git a/terraform/test-fixtures/validate-count-variable/main.tf b/terraform/test-fixtures/validate-count-variable/main.tf new file mode 100644 index 000000000..9c892ac2e --- /dev/null +++ b/terraform/test-fixtures/validate-count-variable/main.tf @@ -0,0 +1,6 @@ +variable "foo" {} + +resource "aws_instance" "foo" { + foo = "foo" + count = "${var.foo}" +} diff --git a/version.go b/version.go index 82b69806b..28f33d2bc 100644 --- a/version.go +++ b/version.go @@ -4,7 +4,7 @@ package main var GitCommit string // The main version number that is being run at the moment. -const Version = "0.3.1" +const Version = "0.3.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/Gemfile.lock b/website/Gemfile.lock index 5a6e8e40d..4f883cde3 100644 --- a/website/Gemfile.lock +++ b/website/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: git://github.com/hashicorp/middleman-hashicorp.git - revision: d884dbcc1be77ba2042b471b1423639c33296109 + revision: 66e68bbb66eef195542bfb334bf0942df6da2c6e specs: middleman-hashicorp (0.1.0) bootstrap-sass (~> 3.2) @@ -29,7 +29,7 @@ GEM builder (3.2.2) celluloid (0.16.0) timers (~> 4.0.0) - chunky_png (1.3.1) + chunky_png (1.3.2) coffee-script (2.3.0) coffee-script-source execjs @@ -53,7 +53,7 @@ GEM http_parser.rb (~> 0.6.0) erubis (2.7.0) eventmachine (1.0.3) - execjs (2.2.1) + execjs (2.2.2) ffi (1.9.6) haml (4.0.5) tilt @@ -113,11 +113,11 @@ GEM rouge (~> 1.0) minitest (5.4.2) multi_json (1.10.1) - padrino-helpers (0.12.3) + padrino-helpers (0.12.4) i18n (~> 0.6, >= 0.6.7) - padrino-support (= 0.12.3) + padrino-support (= 0.12.4) tilt (~> 1.4.1) - padrino-support (0.12.3) + padrino-support (0.12.4) activesupport (>= 3.1) rack (1.5.2) rack-contrib (1.1.0) @@ -132,7 +132,7 @@ GEM redcarpet (3.2.0) ref (1.0.5) rouge (1.7.2) - sass (3.4.5) + sass (3.4.6) sprockets (2.12.2) hike (~> 1.2) multi_json (~> 1.0) diff --git a/website/config.rb b/website/config.rb index 86fcc518f..4bcd629db 100644 --- a/website/config.rb +++ b/website/config.rb @@ -2,6 +2,8 @@ # Configure Middleman #------------------------------------------------------------------------- +set :base_url, "https://www.terraform.io/" + activate :hashicorp do |h| h.version = ENV['TERRAFORM_VERSION'] h.bintray_repo = 'mitchellh/terraform' diff --git a/website/source/404.html.erb b/website/source/404.html.erb index fa51e4b8c..e7dcdc5a2 100644 --- a/website/source/404.html.erb +++ b/website/source/404.html.erb @@ -1 +1,5 @@ +--- +noindex: true +--- +

Page Not Found

diff --git a/website/source/assets/fonts/.!50177!league_gothic-webfont.eot b/website/source/assets/fonts/.!50177!league_gothic-webfont.eot deleted file mode 100755 index e69de29bb..000000000 diff --git a/website/source/assets/fonts/league_gothic-webfont.eot b/website/source/assets/fonts/league_gothic-webfont.eot deleted file mode 100755 index 598dcbc06..000000000 Binary files a/website/source/assets/fonts/league_gothic-webfont.eot and /dev/null differ diff --git a/website/source/assets/fonts/league_gothic-webfont.svg b/website/source/assets/fonts/league_gothic-webfont.svg deleted file mode 100644 index 201cfe1fe..000000000 --- a/website/source/assets/fonts/league_gothic-webfont.svg +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/website/source/assets/fonts/league_gothic-webfont.ttf b/website/source/assets/fonts/league_gothic-webfont.ttf deleted file mode 100644 index 29f896a79..000000000 Binary files a/website/source/assets/fonts/league_gothic-webfont.ttf and /dev/null differ diff --git a/website/source/assets/fonts/league_gothic-webfont.woff b/website/source/assets/fonts/league_gothic-webfont.woff deleted file mode 100644 index 71117fb7f..000000000 Binary files a/website/source/assets/fonts/league_gothic-webfont.woff and /dev/null differ diff --git a/website/source/assets/javascripts/lang-go.js b/website/source/assets/javascripts/lang-go.js deleted file mode 100644 index f329e29b3..000000000 --- a/website/source/assets/javascripts/lang-go.js +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (C) 2010 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - - -/** - * @fileoverview - * Registers a language handler for the Go language.. - *

- * Based on the lexical grammar at - * http://golang.org/doc/go_spec.html#Lexical_elements - *

- * Go uses a minimal style for highlighting so the below does not distinguish - * strings, keywords, literals, etc. by design. - * From a discussion with the Go designers: - *

- * On Thursday, July 22, 2010, Mike Samuel <...> wrote:
- * > On Thu, Jul 22, 2010, Rob 'Commander' Pike <...> wrote:
- * >> Personally, I would vote for the subdued style godoc presents at http://golang.org
- * >>
- * >> Not as fancy as some like, but a case can be made it's the official style.
- * >> If people want more colors, I wouldn't fight too hard, in the interest of
- * >> encouragement through familiarity, but even then I would ask to shy away
- * >> from technicolor starbursts.
- * >
- * > Like http://golang.org/pkg/go/scanner/ where comments are blue and all
- * > other content is black?  I can do that.
- * 
- * - * @author mikesamuel@gmail.com - */ - -PR['registerLangHandler']( - PR['createSimpleLexer']( - [ - // Whitespace is made up of spaces, tabs and newline characters. - [PR['PR_PLAIN'], /^[\t\n\r \xA0]+/, null, '\t\n\r \xA0'], - // Not escaped as a string. See note on minimalism above. - [PR['PR_PLAIN'], /^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])+(?:\'|$)|`[^`]*(?:`|$))/, null, '"\''] - ], - [ - // Block comments are delimited by /* and */. - // Single-line comments begin with // and extend to the end of a line. - [PR['PR_COMMENT'], /^(?:\/\/[^\r\n]*|\/\*[\s\S]*?\*\/)/], - [PR['PR_PLAIN'], /^(?:[^\/\"\'`]|\/(?![\/\*]))+/i] - ]), - ['go']); diff --git a/website/source/assets/javascripts/prettify.js b/website/source/assets/javascripts/prettify.js deleted file mode 100644 index 7b990496d..000000000 --- a/website/source/assets/javascripts/prettify.js +++ /dev/null @@ -1,30 +0,0 @@ -!function(){var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; -(function(){function S(a){function d(e){var b=e.charCodeAt(0);if(b!==92)return b;var a=e.charAt(1);return(b=r[a])?b:"0"<=a&&a<="7"?parseInt(e.substring(1),8):a==="u"||a==="x"?parseInt(e.substring(2),16):e.charCodeAt(1)}function g(e){if(e<32)return(e<16?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);return e==="\\"||e==="-"||e==="]"||e==="^"?"\\"+e:e}function b(e){var b=e.substring(1,e.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),e=[],a= -b[0]==="^",c=["["];a&&c.push("^");for(var a=a?1:0,f=b.length;a122||(l<65||h>90||e.push([Math.max(65,h)|32,Math.min(l,90)|32]),l<97||h>122||e.push([Math.max(97,h)&-33,Math.min(l,122)&-33]))}}e.sort(function(e,a){return e[0]-a[0]||a[1]-e[1]});b=[];f=[];for(a=0;ah[0]&&(h[1]+1>h[0]&&c.push("-"),c.push(g(h[1])));c.push("]");return c.join("")}function s(e){for(var a=e.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),c=a.length,d=[],f=0,h=0;f=2&&e==="["?a[f]=b(l):e!=="\\"&&(a[f]=l.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return a.join("")}for(var x=0,m=!1,j=!1,k=0,c=a.length;k=5&&"lang-"===w.substring(0,5))&&!(t&&typeof t[1]==="string"))f=!1,w="src";f||(r[z]=w)}h=c;c+=z.length;if(f){f=t[1];var l=z.indexOf(f),B=l+f.length;t[2]&&(B=z.length-t[2].length,l=B-f.length);w=w.substring(5);H(j+h,z.substring(0,l),g,k);H(j+h+l,f,I(w,f),k);H(j+h+B,z.substring(B),g,k)}else k.push(j+h,w)}a.g=k}var b={},s;(function(){for(var g=a.concat(d),j=[],k={},c=0,i=g.length;c=0;)b[n.charAt(e)]=r;r=r[1];n=""+r;k.hasOwnProperty(n)||(j.push(r),k[n]=q)}j.push(/[\S\s]/);s=S(j)})();var x=d.length;return g}function v(a){var d=[],g=[];a.tripleQuotedStrings?d.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?d.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, -q,"'\"`"]):d.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&g.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var b=a.hashComments;b&&(a.cStyleComments?(b>1?d.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):d.push(["com",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),g.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,q])):d.push(["com", -/^#[^\n\r]*/,q,"#"]));a.cStyleComments&&(g.push(["com",/^\/\/[^\n\r]*/,q]),g.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));if(b=a.regexLiterals){var s=(b=b>1?"":"\n\r")?".":"[\\S\\s]";g.push(["lang-regex",RegExp("^(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+("/(?=[^/*"+b+"])(?:[^/\\x5B\\x5C"+b+"]|\\x5C"+s+"|\\x5B(?:[^\\x5C\\x5D"+b+"]|\\x5C"+ -s+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&g.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&g.push(["kwd",RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),q]);d.push(["pln",/^\s+/,q," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");g.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/, -q],["pun",RegExp(b),q]);return C(d,g)}function J(a,d,g){function b(a){var c=a.nodeType;if(c==1&&!x.test(a.className))if("br"===a.nodeName)s(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&g){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(j.createTextNode(d),a.nextSibling),s(a),c||a.parentNode.removeChild(a)}}function s(a){function b(a,c){var d= -c?a.cloneNode(!1):a,e=a.parentNode;if(e){var e=b(e,1),g=a.nextSibling;e.appendChild(d);for(var i=g;i;i=g)g=i.nextSibling,e.appendChild(i)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var x=/(?:^|\s)nocode(?:\s|$)/,m=/\r\n?|\n/,j=a.ownerDocument,k=j.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var c=[k],i=0;i=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*=l&&(b+=2);g>=B&&(r+=2)}}finally{if(f)f.style.display=h}}catch(u){D.console&&console.log(u&&u.stack||u)}}var D=window,y=["break,continue,do,else,for,if,return,while"],E=[[y,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], -"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],M=[E,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],N=[E,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"], -O=[N,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],E=[E,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],P=[y,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], -Q=[y,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],W=[y,"as,assert,const,copy,drop,enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"],y=[y,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],R=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/, -V=/\S/,X=v({keywords:[M,O,E,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",P,Q,y],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),F={};p(X,["default-code"]);p(C([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-", -/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);p(C([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/], -["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);p(C([],[["atv",/^[\S\s]+/]]),["uq.val"]);p(v({keywords:M,hashComments:!0,cStyleComments:!0,types:R}),["c","cc","cpp","cxx","cyc","m"]);p(v({keywords:"null,true,false"}),["json"]);p(v({keywords:O,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:R}), -["cs"]);p(v({keywords:N,cStyleComments:!0}),["java"]);p(v({keywords:y,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);p(v({keywords:P,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);p(v({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]);p(v({keywords:Q, -hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);p(v({keywords:E,cStyleComments:!0,regexLiterals:!0}),["javascript","js"]);p(v({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);p(v({keywords:W,cStyleComments:!0,multilineStrings:!0}),["rc","rs","rust"]); -p(C([],[["str",/^[\S\s]+/]]),["regex"]);var Y=D.PR={createSimpleLexer:C,registerLangHandler:p,sourceDecorator:v,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:D.prettyPrintOne=function(a,d,g){var b=document.createElement("div");b.innerHTML="
"+a+"
";b=b.firstChild;g&&J(b,g,!0);K({h:d,j:g,c:b,i:1}); -return b.innerHTML},prettyPrint:D.prettyPrint=function(a,d){function g(){for(var b=D.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;iCommunity diff --git a/website/source/docs/commands/apply.html.markdown b/website/source/docs/commands/apply.html.markdown index bfd563d75..96591a476 100644 --- a/website/source/docs/commands/apply.html.markdown +++ b/website/source/docs/commands/apply.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Command: apply" sidebar_current: "docs-commands-apply" +description: |- + The `terraform apply` command is used to apply the changes required to reach the desired state of the configuration, or the pre-determined set of actions generated by a `terraform plan` execution plan. --- # Command: apply diff --git a/website/source/docs/commands/destroy.html.markdown b/website/source/docs/commands/destroy.html.markdown index 434163b70..311fe174e 100644 --- a/website/source/docs/commands/destroy.html.markdown +++ b/website/source/docs/commands/destroy.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Command: destroy" sidebar_current: "docs-commands-destroy" +description: |- + The `terraform destroy` command is used to destroy the Terraform-managed infrastructure. --- # Command: destroy diff --git a/website/source/docs/commands/get.html.markdown b/website/source/docs/commands/get.html.markdown index 80f91b08e..0abaae14e 100644 --- a/website/source/docs/commands/get.html.markdown +++ b/website/source/docs/commands/get.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Command: get" sidebar_current: "docs-commands-get" +description: |- + The `terraform get` command is used to download and update modules. --- # Command: get diff --git a/website/source/docs/commands/graph.html.markdown b/website/source/docs/commands/graph.html.markdown index c2f38dfa0..d01c6d769 100644 --- a/website/source/docs/commands/graph.html.markdown +++ b/website/source/docs/commands/graph.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Command: graph" sidebar_current: "docs-commands-graph" +description: |- + The `terraform graph` command is used to generate a visual representation of either a configuration or execution plan. The output is in the DOT format, which can be used by GraphViz to generate charts. --- # Command: graph diff --git a/website/source/docs/commands/index.html.markdown b/website/source/docs/commands/index.html.markdown index eeacfa5d9..f7e8a7126 100644 --- a/website/source/docs/commands/index.html.markdown +++ b/website/source/docs/commands/index.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Commands" sidebar_current: "docs-commands" +description: |- + Terraform is controlled via a very easy to use command-line interface (CLI). Terraform is only a single command-line application: terraform. This application then takes a subcommand such as "apply" or "plan". The complete list of subcommands is in the navigation to the left. --- # Terraform Commands (CLI) diff --git a/website/source/docs/commands/init.html.markdown b/website/source/docs/commands/init.html.markdown index a157a047a..bed95eac0 100644 --- a/website/source/docs/commands/init.html.markdown +++ b/website/source/docs/commands/init.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Command: init" sidebar_current: "docs-commands-init" +description: |- + The `terraform init` command is used to initialize a Terraform configuration using another module as a skeleton. --- # Command: init diff --git a/website/source/docs/commands/output.html.markdown b/website/source/docs/commands/output.html.markdown index 8ac98aa69..0ac423937 100644 --- a/website/source/docs/commands/output.html.markdown +++ b/website/source/docs/commands/output.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Command: output" sidebar_current: "docs-commands-output" +description: |- + The `terraform output` command is used to extract the value of an output variable from the state file. --- # Command: output diff --git a/website/source/docs/commands/plan.html.markdown b/website/source/docs/commands/plan.html.markdown index 64fd6073e..1d8f1a778 100644 --- a/website/source/docs/commands/plan.html.markdown +++ b/website/source/docs/commands/plan.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Command: plan" sidebar_current: "docs-commands-plan" +description: |- + The `terraform plan` command is used to create an execution plan. Terraform performs a refresh, unless explicitly disabled, and then determines what actions are necessary to achieve the desired state specified in the configuration files. The plan can be saved using `-out`, and then provided to `terraform apply` to ensure only the pre-planned actions are executed. --- # Command: plan diff --git a/website/source/docs/commands/refresh.html.markdown b/website/source/docs/commands/refresh.html.markdown index cbf10b333..cc797ca38 100644 --- a/website/source/docs/commands/refresh.html.markdown +++ b/website/source/docs/commands/refresh.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Command: refresh" sidebar_current: "docs-commands-refresh" +description: |- + The `terraform refresh` command is used to reconcile the state Terraform knows about (via its state file) with the real-world infrastructure. This can be used to detect any drift from the last-known state, and to update the state file. --- # Command: refresh diff --git a/website/source/docs/commands/show.html.markdown b/website/source/docs/commands/show.html.markdown index 9888884cb..8593b75c0 100644 --- a/website/source/docs/commands/show.html.markdown +++ b/website/source/docs/commands/show.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Command: show" sidebar_current: "docs-commands-show" +description: |- + The `terraform show` command is used to provide human-readable output from a state or plan file. This can be used to inspect a plan to ensure that the planned operations are expected, or to inspect the current state as terraform sees it. --- # Command: show diff --git a/website/source/docs/configuration/index.html.md b/website/source/docs/configuration/index.html.md index 422bf007b..2b3e7f396 100644 --- a/website/source/docs/configuration/index.html.md +++ b/website/source/docs/configuration/index.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Configuration" sidebar_current: "docs-config" +description: |- + Terraform uses text files to describe infrastructure and to set variables. These text files are called Terraform _configurations_ and end in `.tf`. This section talks about the format of these files as well as how they're loaded. --- # Configuration diff --git a/website/source/docs/configuration/interpolation.html.md b/website/source/docs/configuration/interpolation.html.md index dcec02448..c0fe3372f 100644 --- a/website/source/docs/configuration/interpolation.html.md +++ b/website/source/docs/configuration/interpolation.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Interpolation Syntax" sidebar_current: "docs-config-interpolation" +description: |- + Embedded within strings in Terraform, whether you're using the Terraform syntax or JSON syntax, you can interpolate other values into strings. These interpolations are wrapped in `${}`, such as `${var.foo}`. --- # Interpolation Syntax diff --git a/website/source/docs/configuration/load.html.md b/website/source/docs/configuration/load.html.md index e1b760894..3ccbcf7b6 100644 --- a/website/source/docs/configuration/load.html.md +++ b/website/source/docs/configuration/load.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Load Order and Semantics" sidebar_current: "docs-config-load" +description: |- + When invoking any command that loads the Terraform configuration, Terraform loads all configuration files within the directory specified in alphabetical order. --- # Load Order and Semantics diff --git a/website/source/docs/configuration/modules.html.md b/website/source/docs/configuration/modules.html.md index 07f48ca3c..0cc47461a 100644 --- a/website/source/docs/configuration/modules.html.md +++ b/website/source/docs/configuration/modules.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Configuring Modules" sidebar_current: "docs-config-modules" +description: |- + Modules are used in Terraform to modularize and encapsulate groups of resources in your infrastructure. For more information on modules, see the dedicated modules section. --- # Module Configuration diff --git a/website/source/docs/configuration/outputs.html.md b/website/source/docs/configuration/outputs.html.md index cc5bae9a5..3e8ac5c7b 100644 --- a/website/source/docs/configuration/outputs.html.md +++ b/website/source/docs/configuration/outputs.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Configuring Outputs" sidebar_current: "docs-config-outputs" +description: |- + Outputs define values that will be highlighted to the user when Terraform applies, and can be queried easily using the output command. Output usage is covered in more detail in the getting started guide. This page covers configuration syntax for outputs. --- # Output Configuration diff --git a/website/source/docs/configuration/override.html.md b/website/source/docs/configuration/override.html.md index e0b42c781..a667adcd5 100644 --- a/website/source/docs/configuration/override.html.md +++ b/website/source/docs/configuration/override.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Overrides" sidebar_current: "docs-config-override" +description: |- + Terraform loads all configuration files within a directory and appends them together. Terraform also has a concept of overrides, a way to create files that are loaded last and merged into your configuration, rather than appended. --- # Overrides diff --git a/website/source/docs/configuration/providers.html.md b/website/source/docs/configuration/providers.html.md index 1ed33b795..7d95ba9fa 100644 --- a/website/source/docs/configuration/providers.html.md +++ b/website/source/docs/configuration/providers.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Configuring Providers" sidebar_current: "docs-config-providers" +description: |- + Providers are responsible in Terraform for managing the lifecycle of a resource: create, read, update, delete. --- # Provider Configuration diff --git a/website/source/docs/configuration/resources.html.md b/website/source/docs/configuration/resources.html.md index 03e3eedef..dde8fb3ef 100644 --- a/website/source/docs/configuration/resources.html.md +++ b/website/source/docs/configuration/resources.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Configuring Resources" sidebar_current: "docs-config-resources" +description: |- + The most important thing you'll configure with Terraform are resources. Resources are a component of your infrastructure. It might be some low level component such as a physical server, virtual machine, or container. Or it can be a higher level component such as an email provider, DNS record, or database provider. --- # Resource Configuration diff --git a/website/source/docs/configuration/syntax.html.md b/website/source/docs/configuration/syntax.html.md index 0be82b45b..ad8c8c910 100644 --- a/website/source/docs/configuration/syntax.html.md +++ b/website/source/docs/configuration/syntax.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Configuration Syntax" sidebar_current: "docs-config-syntax" +description: |- + The syntax of Terraform configurations is custom. It is meant to strike a balance between human readable and editable as well as being machine-friendly. For machine-friendliness, Terraform can also read JSON configurations. For general Terraform configurations, however, we recommend using the Terraform syntax. --- # Configuration Syntax diff --git a/website/source/docs/configuration/variables.html.md b/website/source/docs/configuration/variables.html.md index b86654094..4e8b834e8 100644 --- a/website/source/docs/configuration/variables.html.md +++ b/website/source/docs/configuration/variables.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Configuring Variables" sidebar_current: "docs-config-variables" +description: |- + Variables define the parameterization of Terraform configurations. Variables can be overridden via the CLI. Variable usage is covered in more detail in the getting started guide. This page covers configuration syntax for variables. --- # Variable Configuration diff --git a/website/source/docs/index.html.markdown b/website/source/docs/index.html.markdown index 6323d404d..5329003dc 100644 --- a/website/source/docs/index.html.markdown +++ b/website/source/docs/index.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Documentation" sidebar_current: "docs-home" +description: |- + Welcome to the Terraform documentation! This documentation is more of a reference guide for all available features and options of Terraform. If you're just getting started with Terraform, please start with the introduction and getting started guide instead. --- # Terraform Documentation diff --git a/website/source/docs/internals/graph.html.md b/website/source/docs/internals/graph.html.md index 55f995332..4b1a56de7 100644 --- a/website/source/docs/internals/graph.html.md +++ b/website/source/docs/internals/graph.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Resource Graph" sidebar_current: "docs-internals-graph" +description: |- + Terraform builds a dependency graph from the Terraform configurations, and walks this graph to generate plans, refresh state, and more. This page documents the details of what are contained in this graph, what types of nodes there are, and how the edges of the graph are determined. --- # Resource Graph @@ -13,13 +15,11 @@ generate plans, refresh state, and more. This page documents the details of what are contained in this graph, what types of nodes there are, and how the edges of the graph are determined. -
-Advanced Topic! This page covers technical details +~> **Advanced Topic!** This page covers technical details of Terraform. You don't need to understand these details to effectively use Terraform. The details are documented here for those who wish to learn about them without having to go spelunking through the source code. -
## Graph Nodes diff --git a/website/source/docs/internals/index.html.md b/website/source/docs/internals/index.html.md index bde40ef03..135fb027d 100644 --- a/website/source/docs/internals/index.html.md +++ b/website/source/docs/internals/index.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Internals" sidebar_current: "docs-internals" +description: |- + This section covers the internals of Terraform and explains how plans are generated, the lifecycle of a provider, etc. The goal of this section is to remove any notion of "magic" from Terraform. We want you to be able to trust and understand what Terraform is doing to function. --- # Terraform Internals @@ -12,8 +14,6 @@ of this section is to remove any notion of "magic" from Terraform. We want you to be able to trust and understand what Terraform is doing to function. -
-Note: Knowledge of Terraform internals is not +-> **Note:** Knowledge of Terraform internals is not required to use Terraform. If you aren't interested in the internals of Terraform, you may safely skip this section. -
diff --git a/website/source/docs/internals/lifecycle.html.md b/website/source/docs/internals/lifecycle.html.md index d39b6000b..beaa28f77 100644 --- a/website/source/docs/internals/lifecycle.html.md +++ b/website/source/docs/internals/lifecycle.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Resource Lifecycle" sidebar_current: "docs-internals-lifecycle" +description: |- + Resources have a strict lifecycle, and can be thought of as basic state machines. Understanding this lifecycle can help better understand how Terraform generates an execution plan, how it safely executes that plan, and what the resource provider is doing throughout all of this. --- # Resource Lifecycle @@ -11,13 +13,11 @@ state machines. Understanding this lifecycle can help better understand how Terraform generates an execution plan, how it safely executes that plan, and what the resource provider is doing throughout all of this. -
-Advanced Topic! This page covers technical details +~> **Advanced Topic!** This page covers technical details of Terraform. You don't need to understand these details to effectively use Terraform. The details are documented here for those who wish to learn about them without having to go spelunking through the source code. -
## Lifecycle diff --git a/website/source/docs/modules/create.html.markdown b/website/source/docs/modules/create.html.markdown index bfe12395f..da69891f7 100644 --- a/website/source/docs/modules/create.html.markdown +++ b/website/source/docs/modules/create.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Creating Modules" sidebar_current: "docs-modules-create" +description: |- + Creating modules in Terraform is easy. You may want to do this to better organize your code, to make a reusable component, or just to learn more about Terraform. For any reason, if you already know the basics of Terraform, creating a module is a piece of cake. --- # Creating Modules diff --git a/website/source/docs/modules/index.html.markdown b/website/source/docs/modules/index.html.markdown index 30031ccd3..5a4bd933b 100644 --- a/website/source/docs/modules/index.html.markdown +++ b/website/source/docs/modules/index.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Modules" sidebar_current: "docs-modules" +description: |- + Modules in terraform are self-contained packages of Terraform configurations that are managed as a group. Modules are used to create reusable components in Terraform as well as for basic code organization. --- # Modules diff --git a/website/source/docs/modules/sources.html.markdown b/website/source/docs/modules/sources.html.markdown index 9ea86b699..8b4fce01f 100644 --- a/website/source/docs/modules/sources.html.markdown +++ b/website/source/docs/modules/sources.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Module Sources" sidebar_current: "docs-modules-sources" +description: |- + As documented in usage, the only required parameter when using a module is the `source` paramter which tells Terraform where the module can be found and what constraints to put on the module if any (such as branches for git, versions, etc.). --- # Module Sources diff --git a/website/source/docs/modules/usage.html.markdown.erb b/website/source/docs/modules/usage.html.markdown similarity index 94% rename from website/source/docs/modules/usage.html.markdown.erb rename to website/source/docs/modules/usage.html.markdown index 1ec890928..fb379cc83 100644 --- a/website/source/docs/modules/usage.html.markdown.erb +++ b/website/source/docs/modules/usage.html.markdown @@ -2,6 +2,7 @@ layout: "docs" page_title: "Using Modules" sidebar_current: "docs-modules-usage" +description: Using modules in Terraform is very similar to defining resources. --- # Module Usage @@ -91,13 +92,13 @@ For example, with a configuration similar to what we've built above, here is what the graph output looks like by default:
-<%= image_tag "docs/module_graph.png" %> +![Terraform Module Graph](images/docs/module_graph.png)
But if we set `-module-depth=-1`, the graph will look like this:
-<%= image_tag "docs/module_graph_expand.png" %> +![Terraform Expanded Module Graph](images/docs/module_graph_expand.png)
Other commands work similarly with modules. Note that the `-module-depth` diff --git a/website/source/docs/plugins/basics.html.md b/website/source/docs/plugins/basics.html.md index e7b80ebd2..94aa25d77 100644 --- a/website/source/docs/plugins/basics.html.md +++ b/website/source/docs/plugins/basics.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Plugin Basics" sidebar_current: "docs-plugins-basics" +description: |- + This page documents the basics of how the plugin system in Terraform works, and how to setup a basic development environment for plugin development if you're writing a Terraform plugin. --- # Plugin Basics @@ -10,12 +12,10 @@ This page documents the basics of how the plugin system in Terraform works, and how to setup a basic development environment for plugin development if you're writing a Terraform plugin. -
-Advanced topic! Plugin development is a highly advanced +~> **Advanced topic!** Plugin development is a highly advanced topic in Terraform, and is not required knowledge for day-to-day usage. If you don't plan on writing any plugins, we recommend not reading this section of the documentation. -
## How it Works @@ -68,12 +68,10 @@ Developing a plugin is simple. The only knowledge necessary to write a plugin is basic command-line skills and basic knowledge of the [Go programming language](http://golang.org). -
-Note: A common pitfall is not properly setting up a +-> **Note:** A common pitfall is not properly setting up a $GOPATH. This can lead to strange errors. You can read more about -this here to familiarize +this [here](https://golang.org/doc/code.html) to familiarize yourself. -
Create a new Go project somewhere in your `$GOPATH`. If you're a GitHub user, we recommend creating the project in the directory diff --git a/website/source/docs/plugins/index.html.md b/website/source/docs/plugins/index.html.md index cbc3bc4c0..7970e704c 100644 --- a/website/source/docs/plugins/index.html.md +++ b/website/source/docs/plugins/index.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Plugins" sidebar_current: "docs-plugins" +description: |- + Terraform is built on a plugin-based architecture. All providers and provisioners that are used in Terraform configurations are plugins, even the core types such as AWS and Heroku. Users of Terraform are able to write new plugins in order to support new functionality in Terraform. --- # Plugins @@ -16,9 +18,7 @@ to write plugins for Terraform. It does not hold your hand through the process, however, and expects a relatively high level of understanding of Go, provider semantics, Unix, etc. -
-Advanced topic! Plugin development is a highly advanced +~> **Advanced topic!** Plugin development is a highly advanced topic in Terraform, and is not required knowledge for day-to-day usage. If you don't plan on writing any plugins, we recommend not reading this section of the documentation. -
diff --git a/website/source/docs/plugins/provider.html.md b/website/source/docs/plugins/provider.html.md index 79932cc65..76c4bd003 100644 --- a/website/source/docs/plugins/provider.html.md +++ b/website/source/docs/plugins/provider.html.md @@ -2,6 +2,8 @@ layout: "docs" page_title: "Provider Plugins" sidebar_current: "docs-plugins-provider" +description: |- + A provider in Terraform is responsible for the lifecycle of a resource: create, read, update, delete. An example of a provider is AWS, which can manage resources of type `aws_instance`, `aws_eip`, `aws_elb`, etc. --- # Provider Plugins @@ -20,12 +22,10 @@ The primary reasons to care about provider plugins are: * You want to write a completely new provider for custom, internal systems such as a private inventory management system. -
-Advanced topic! Plugin development is a highly advanced +~> **Advanced topic!** Plugin development is a highly advanced topic in Terraform, and is not required knowledge for day-to-day usage. If you don't plan on writing any plugins, we recommend not reading this section of the documentation. -
If you're interested in provider development, then read on. The remainder of this page will assume you're familiar with @@ -182,7 +182,7 @@ The parameter to provider configuration as well as all the CRUD operations on a resource is a [schema.ResourceData](http://godoc.org/github.com/hashicorp/terraform/helper/schema#ResourceData). This structure is used to query configurations as well as to set information -about the resource such as it's ID, connection information, and computed +about the resource such as its ID, connection information, and computed attributes. The API documentation covers ResourceData well, as well as the core providers diff --git a/website/source/docs/providers/aws/index.html.markdown b/website/source/docs/providers/aws/index.html.markdown index 8105d8b83..ec4a52881 100644 --- a/website/source/docs/providers/aws/index.html.markdown +++ b/website/source/docs/providers/aws/index.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "Provider: AWS" sidebar_current: "docs-aws-index" +description: |- + The Amazon Web Services (AWS) provider is used to interact with the many resources supported by AWS. The provider needs to be configured with the proper credentials before it can be used. --- # AWS Provider diff --git a/website/source/docs/providers/aws/r/autoscale.html.markdown b/website/source/docs/providers/aws/r/autoscale.html.markdown index ce05e48e9..a10dec4ce 100644 --- a/website/source/docs/providers/aws/r/autoscale.html.markdown +++ b/website/source/docs/providers/aws/r/autoscale.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_autoscaling_group" sidebar_current: "docs-aws-resource-autoscale" +description: |- + Provides an AutoScaling Group resource. --- # aws\_autoscaling\_group @@ -40,7 +42,7 @@ The following arguments are supported: for all instances in the pool to terminate. * `load_balancers` (Optional) A list of load balancer names to add to the autoscaling group names. -* `vpc_zone_identifier` (Optional) A list of vpc IDs to launch resources in. +* `vpc_zone_identifier` (Optional) A list of subnet IDs to launch resources in. ## Attributes Reference diff --git a/website/source/docs/providers/aws/r/db_instance.html.markdown b/website/source/docs/providers/aws/r/db_instance.html.markdown index 7696f7c09..d0e96c952 100644 --- a/website/source/docs/providers/aws/r/db_instance.html.markdown +++ b/website/source/docs/providers/aws/r/db_instance.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_db_instance" sidebar_current: "docs-aws-resource-db-instance" +description: |- + Provides an RDS instance resource. --- # aws\_db\_instance @@ -21,7 +23,7 @@ resource "aws_db_instance" "default" { username = "foo" password = "bar" security_group_names = ["${aws_db_security_group.bar.name}"] - subnet_group_name = "my_database_subnet_group" + db_subnet_group_name = "my_database_subnet_group" } ``` @@ -50,7 +52,7 @@ The following arguments are supported: * `vpc_security_group_ids` - (Optional) List of VPC security groups to associate. * `skip_final_snapshot` - (Optional) Enables skipping the final snapshot on deletion. * `security_group_names` - (Optional) List of DB Security Groups to associate. -* `subnet_group_name` - (Optional) Name of DB subnet group +* `db_subnet_group_name` - (Optional) Name of DB subnet group ## Attributes Reference diff --git a/website/source/docs/providers/aws/r/db_security_group.html.markdown b/website/source/docs/providers/aws/r/db_security_group.html.markdown index dc9c30035..85041f522 100644 --- a/website/source/docs/providers/aws/r/db_security_group.html.markdown +++ b/website/source/docs/providers/aws/r/db_security_group.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_db_security_group" sidebar_current: "docs-aws-resource-db-security-group" +description: |- + Provides an RDS security group resource. --- # aws\_db\_security\_group diff --git a/website/source/docs/providers/aws/r/db_subnet_group.html.markdown b/website/source/docs/providers/aws/r/db_subnet_group.html.markdown index 48163c869..f9050ad45 100644 --- a/website/source/docs/providers/aws/r/db_subnet_group.html.markdown +++ b/website/source/docs/providers/aws/r/db_subnet_group.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_db_subnet_group" sidebar_current: "docs-aws-resource-db-subnet-group" +description: |- + Provides an RDS DB subnet group resource. --- # aws\_db\_subnet\_group diff --git a/website/source/docs/providers/aws/r/eip.html.markdown b/website/source/docs/providers/aws/r/eip.html.markdown index 458d1e78a..d422f8110 100644 --- a/website/source/docs/providers/aws/r/eip.html.markdown +++ b/website/source/docs/providers/aws/r/eip.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_eip" sidebar_current: "docs-aws-resource-eip" +description: |- + Provides an Elastic IP resource. --- # aws\_eip diff --git a/website/source/docs/providers/aws/r/elb.html.markdown b/website/source/docs/providers/aws/r/elb.html.markdown index 3420c7726..4d29178bb 100644 --- a/website/source/docs/providers/aws/r/elb.html.markdown +++ b/website/source/docs/providers/aws/r/elb.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_elb" sidebar_current: "docs-aws-resource-elb" +description: |- + Provides an Elastic Load Balancer resource. --- # aws\_elb @@ -28,7 +30,7 @@ resource "aws_elb" "bar" { instance_protocol = "http" lb_port = 443 lb_protocol = "https" - ssl_certificate_id = "arn:aws:iam::123456789012:server-certificate/certName" + ssl_certificate_id = "arn:aws:iam::123456789012:server-certificate/certName" } health_check { diff --git a/website/source/docs/providers/aws/r/instance.html.markdown b/website/source/docs/providers/aws/r/instance.html.markdown index 48ab4bd81..d7eeef904 100644 --- a/website/source/docs/providers/aws/r/instance.html.markdown +++ b/website/source/docs/providers/aws/r/instance.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_instance" sidebar_current: "docs-aws-resource-instance" +description: |- + Provides an EC2 instance resource. This allows instances to be created, updated, and deleted. Instances also support provisioning. --- # aws\_instance diff --git a/website/source/docs/providers/aws/r/internet_gateway.html.markdown b/website/source/docs/providers/aws/r/internet_gateway.html.markdown index e83ec6871..2207febe0 100644 --- a/website/source/docs/providers/aws/r/internet_gateway.html.markdown +++ b/website/source/docs/providers/aws/r/internet_gateway.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_internet_gateway" sidebar_current: "docs-aws-resource-internet-gateway" +description: |- + Provides a resource to create a VPC Internet Gateway. --- # aws\_internet\_gateway diff --git a/website/source/docs/providers/aws/r/launch_config.html.markdown b/website/source/docs/providers/aws/r/launch_config.html.markdown index c681747e8..58ff91975 100644 --- a/website/source/docs/providers/aws/r/launch_config.html.markdown +++ b/website/source/docs/providers/aws/r/launch_config.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_launch_configuration" sidebar_current: "docs-aws-resource-launch-config" +description: |- + Provides a resource to create a new launch configuration, used for autoscaling groups. --- # aws\_launch\_configuration diff --git a/website/source/docs/providers/aws/r/route53_record.html.markdown b/website/source/docs/providers/aws/r/route53_record.html.markdown index 4a519cf82..d0522e597 100644 --- a/website/source/docs/providers/aws/r/route53_record.html.markdown +++ b/website/source/docs/providers/aws/r/route53_record.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_route53_record" sidebar_current: "docs-aws-resource-route53-record" +description: |- + Provides a Route53 record resource. --- # aws\_route53\_record diff --git a/website/source/docs/providers/aws/r/route53_zone.html.markdown b/website/source/docs/providers/aws/r/route53_zone.html.markdown index 6be339aaf..b8b6f32d6 100644 --- a/website/source/docs/providers/aws/r/route53_zone.html.markdown +++ b/website/source/docs/providers/aws/r/route53_zone.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_route53_zone" sidebar_current: "docs-aws-resource-route53-zone" +description: |- + Provides a Route53 Hosted Zone resource. --- # aws\_route53\_zone diff --git a/website/source/docs/providers/aws/r/route_table.html.markdown b/website/source/docs/providers/aws/r/route_table.html.markdown index eaf45cb6d..37a9d89ac 100644 --- a/website/source/docs/providers/aws/r/route_table.html.markdown +++ b/website/source/docs/providers/aws/r/route_table.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_route_table" sidebar_current: "docs-aws-resource-route-table|" +description: |- + Provides a resource to create a VPC routing table. --- # aws\_route\_table diff --git a/website/source/docs/providers/aws/r/route_table_assoc.html.markdown b/website/source/docs/providers/aws/r/route_table_assoc.html.markdown index 2c924b7d2..712a1dd9c 100644 --- a/website/source/docs/providers/aws/r/route_table_assoc.html.markdown +++ b/website/source/docs/providers/aws/r/route_table_assoc.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_route_table_association" sidebar_current: "docs-aws-resource-route-table-assoc" +description: |- + Provides a resource to create an association between a subnet and routing table. --- # aws\_route\_table\_association 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 dcce52738..a2a4c7360 100644 --- a/website/source/docs/providers/aws/r/s3_bucket.html.markdown +++ b/website/source/docs/providers/aws/r/s3_bucket.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_s3_bucket" sidebar_current: "docs-aws-resource-s3-bucket" +description: |- + Provides a S3 bucket resource. --- # aws\_s3\_bucket diff --git a/website/source/docs/providers/aws/r/security_group.html.markdown b/website/source/docs/providers/aws/r/security_group.html.markdown index 89323a996..765fdab9e 100644 --- a/website/source/docs/providers/aws/r/security_group.html.markdown +++ b/website/source/docs/providers/aws/r/security_group.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_security_group" sidebar_current: "docs-aws-resource-security-group" +description: |- + Provides an security group resource. --- # aws\_security\_group @@ -10,17 +12,39 @@ Provides an security group resource. ## Example Usage +Basic usage + ``` resource "aws_security_group" "allow_all" { - name = "allow_all" + name = "allow_all" description = "Allow all inbound traffic" - ingress { - from_port = 0 - to_port = 65535 - protocol = "tcp" - cidr_blocks = ["0.0.0.0/0"] - } + ingress { + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } +} +``` + +Basic usage with tags: + +``` +resource "aws_security_group" "allow_all" { + name = "allow_all" + description = "Allow all inbound traffic" + + ingress { + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + tags { + Name = "allow_all" + } } ``` @@ -44,6 +68,7 @@ The `ingress` block supports: * `self` - (Optional) If true, the security group itself will be added as a source to this ingress rule. * `to_port` - (Required) The end range port. +* `tags` - (Optional) A mapping of tags to assign to the resource. ## Attributes Reference @@ -55,4 +80,3 @@ The following attributes are exported: * `name` - The name of the security group * `description` - The description of the security group * `ingress` - The ingress rules. See above for more. - diff --git a/website/source/docs/providers/aws/r/subnet.html.markdown b/website/source/docs/providers/aws/r/subnet.html.markdown index 73d6e5628..7ce52ba99 100644 --- a/website/source/docs/providers/aws/r/subnet.html.markdown +++ b/website/source/docs/providers/aws/r/subnet.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_subnet" sidebar_current: "docs-aws-resource-subnet" +description: |- + Provides an VPC subnet resource. --- # aws\_subnet diff --git a/website/source/docs/providers/aws/r/vpc.html.markdown b/website/source/docs/providers/aws/r/vpc.html.markdown index be9337713..489648d80 100644 --- a/website/source/docs/providers/aws/r/vpc.html.markdown +++ b/website/source/docs/providers/aws/r/vpc.html.markdown @@ -2,6 +2,8 @@ layout: "aws" page_title: "AWS: aws_vpc" sidebar_current: "docs-aws-resource-vpc" +description: |- + Provides an VPC resource. --- # aws\_vpc diff --git a/website/source/docs/providers/cloudflare/index.html.markdown b/website/source/docs/providers/cloudflare/index.html.markdown index a8c0cd353..a806d1254 100644 --- a/website/source/docs/providers/cloudflare/index.html.markdown +++ b/website/source/docs/providers/cloudflare/index.html.markdown @@ -2,6 +2,8 @@ layout: "cloudflare" page_title: "Provider: Cloudflare" sidebar_current: "docs-cloudflare-index" +description: |- + The CloudFlare provider is used to interact with the DNS resources supported by CloudFlare. The provider needs to be configured with the proper credentials before it can be used. --- # CloudFlare Provider diff --git a/website/source/docs/providers/cloudflare/r/record.html.markdown b/website/source/docs/providers/cloudflare/r/record.html.markdown index 55a09eb6a..55e401e3f 100644 --- a/website/source/docs/providers/cloudflare/r/record.html.markdown +++ b/website/source/docs/providers/cloudflare/r/record.html.markdown @@ -2,6 +2,8 @@ layout: "cloudflare" page_title: "CloudFlare: cloudflare_record" sidebar_current: "docs-cloudflare-resource-record" +description: |- + Provides a Cloudflare record resource. --- # cloudflare\_record diff --git a/website/source/docs/providers/consul/index.html.markdown b/website/source/docs/providers/consul/index.html.markdown index 291d4367d..d7ceec10b 100644 --- a/website/source/docs/providers/consul/index.html.markdown +++ b/website/source/docs/providers/consul/index.html.markdown @@ -2,6 +2,8 @@ layout: "consul" page_title: "Provider: Consul" sidebar_current: "docs-consul-index" +description: |- + Consul is a tool for service discovery, configuration and orchestration. The Consul provider exposes resources used to interact with a Consul cluster. Configuration of the provider is optional, as it provides defaults for all arguments. --- # Consul Provider diff --git a/website/source/docs/providers/consul/r/keys.html.markdown b/website/source/docs/providers/consul/r/keys.html.markdown index 4919af109..f0f854a3e 100644 --- a/website/source/docs/providers/consul/r/keys.html.markdown +++ b/website/source/docs/providers/consul/r/keys.html.markdown @@ -2,6 +2,8 @@ layout: "consul" page_title: "Consul: consul_keys" sidebar_current: "docs-consul-resource-keys" +description: |- + Provides access to Key/Value data in Consul. This can be used to both read keys from Consul, but also to set the value of keys in Consul. This is a powerful way dynamically set values in templates, and to expose infrastructure details to clients. --- # consul\_keys diff --git a/website/source/docs/providers/dnsimple/index.html.markdown b/website/source/docs/providers/dnsimple/index.html.markdown index ac84f177d..ad98c3218 100644 --- a/website/source/docs/providers/dnsimple/index.html.markdown +++ b/website/source/docs/providers/dnsimple/index.html.markdown @@ -2,6 +2,8 @@ layout: "dnsimple" page_title: "Provider: DNSimple" sidebar_current: "docs-dnsimple-index" +description: |- + The DNSimple provider is used to interact with the resources supported by DNSimple. The provider needs to be configured with the proper credentials before it can be used. --- # DNSimple Provider diff --git a/website/source/docs/providers/dnsimple/r/record.html.markdown b/website/source/docs/providers/dnsimple/r/record.html.markdown index bf6afae09..24b6d199a 100644 --- a/website/source/docs/providers/dnsimple/r/record.html.markdown +++ b/website/source/docs/providers/dnsimple/r/record.html.markdown @@ -2,6 +2,8 @@ layout: "dnsimple" page_title: "DNSimple: dnsimple_record" sidebar_current: "docs-dnsimple-resource-record" +description: |- + Provides a DNSimple record resource. --- # dnsimple\_record diff --git a/website/source/docs/providers/do/index.html.markdown b/website/source/docs/providers/do/index.html.markdown index c074d6ac2..453a3cb37 100644 --- a/website/source/docs/providers/do/index.html.markdown +++ b/website/source/docs/providers/do/index.html.markdown @@ -2,6 +2,8 @@ layout: "digitalocean" page_title: "Provider: DigitalOcean" sidebar_current: "docs-do-index" +description: |- + The DigitalOcean (DO) provider is used to interact with the resources supported by DigitalOcean. The provider needs to be configured with the proper credentials before it can be used. --- # DigitalOcean Provider diff --git a/website/source/docs/providers/do/r/domain.html.markdown b/website/source/docs/providers/do/r/domain.html.markdown index 8a3de1bd9..5580b63d2 100644 --- a/website/source/docs/providers/do/r/domain.html.markdown +++ b/website/source/docs/providers/do/r/domain.html.markdown @@ -2,6 +2,8 @@ layout: "digitalocean" page_title: "DigitalOcean: digitalocean_domain" sidebar_current: "docs-do-resource-domain" +description: |- + Provides a DigitalOcean domain resource. --- # digitalocean\_domain diff --git a/website/source/docs/providers/do/r/droplet.html.markdown b/website/source/docs/providers/do/r/droplet.html.markdown index ebe4931ce..bfb8ed550 100644 --- a/website/source/docs/providers/do/r/droplet.html.markdown +++ b/website/source/docs/providers/do/r/droplet.html.markdown @@ -2,6 +2,8 @@ layout: "digitalocean" page_title: "DigitalOcean: digitalocean_droplet" sidebar_current: "docs-do-resource-droplet" +description: |- + Provides a DigitalOcean droplet resource. This can be used to create, modify, and delete droplets. Droplets also support provisioning. --- # digitalocean\_droplet diff --git a/website/source/docs/providers/do/r/record.html.markdown b/website/source/docs/providers/do/r/record.html.markdown index bfd6ee7c7..938fb5a59 100644 --- a/website/source/docs/providers/do/r/record.html.markdown +++ b/website/source/docs/providers/do/r/record.html.markdown @@ -2,6 +2,8 @@ layout: "digitalocean" page_title: "DigitalOcean: digitalocean_record" sidebar_current: "docs-do-resource-record" +description: |- + Provides a DigitalOcean domain resource. --- # digitalocean\_record diff --git a/website/source/docs/providers/google/index.html.markdown b/website/source/docs/providers/google/index.html.markdown index b9a67ae61..6eafafbcd 100644 --- a/website/source/docs/providers/google/index.html.markdown +++ b/website/source/docs/providers/google/index.html.markdown @@ -2,6 +2,8 @@ layout: "google" page_title: "Provider: Google Cloud" sidebar_current: "docs-google-index" +description: |- + The Google Cloud provider is used to interact with Google Cloud services. The provider needs to be configured with the proper credentials before it can be used. --- # Google Cloud Provider diff --git a/website/source/docs/providers/google/r/compute_address.html.markdown b/website/source/docs/providers/google/r/compute_address.html.markdown index 08c98b0d3..5365fa2b6 100644 --- a/website/source/docs/providers/google/r/compute_address.html.markdown +++ b/website/source/docs/providers/google/r/compute_address.html.markdown @@ -2,6 +2,8 @@ layout: "google" page_title: "Google: google_compute_address" sidebar_current: "docs-google-resource-address" +description: |- + Creates a static IP address resource for Google Compute Engine. --- # google\_compute\_address diff --git a/website/source/docs/providers/google/r/compute_disk.html.markdown b/website/source/docs/providers/google/r/compute_disk.html.markdown index f52120058..e2fae7380 100644 --- a/website/source/docs/providers/google/r/compute_disk.html.markdown +++ b/website/source/docs/providers/google/r/compute_disk.html.markdown @@ -2,6 +2,8 @@ layout: "google" page_title: "Google: google_compute_disk" sidebar_current: "docs-google-resource-disk" +description: |- + Creates a new persistent disk within GCE, based on another disk. --- # google\_compute\_disk diff --git a/website/source/docs/providers/google/r/compute_firewall.html.markdown b/website/source/docs/providers/google/r/compute_firewall.html.markdown index a2d69e515..638a4bfd2 100644 --- a/website/source/docs/providers/google/r/compute_firewall.html.markdown +++ b/website/source/docs/providers/google/r/compute_firewall.html.markdown @@ -2,6 +2,8 @@ layout: "google" page_title: "Google: google_compute_firewall" sidebar_current: "docs-google-resource-firewall" +description: |- + Manages a firewall resource within GCE. --- # google\_compute\_firewall diff --git a/website/source/docs/providers/google/r/compute_instance.html.markdown b/website/source/docs/providers/google/r/compute_instance.html.markdown index 66172fc51..a30860947 100644 --- a/website/source/docs/providers/google/r/compute_instance.html.markdown +++ b/website/source/docs/providers/google/r/compute_instance.html.markdown @@ -2,6 +2,8 @@ layout: "google" page_title: "Google: google_compute_instance" sidebar_current: "docs-google-resource-instance" +description: |- + Manages a VM instance resource within GCE. --- # google\_compute\_instance diff --git a/website/source/docs/providers/google/r/compute_network.html.markdown b/website/source/docs/providers/google/r/compute_network.html.markdown index 52ae1c7bc..a6d94e402 100644 --- a/website/source/docs/providers/google/r/compute_network.html.markdown +++ b/website/source/docs/providers/google/r/compute_network.html.markdown @@ -2,6 +2,8 @@ layout: "google" page_title: "Google: google_compute_network" sidebar_current: "docs-google-resource-network" +description: |- + Manages a network within GCE. --- # google\_compute\_network diff --git a/website/source/docs/providers/google/r/compute_route.html.markdown b/website/source/docs/providers/google/r/compute_route.html.markdown index b3ebe3313..3651ded95 100644 --- a/website/source/docs/providers/google/r/compute_route.html.markdown +++ b/website/source/docs/providers/google/r/compute_route.html.markdown @@ -2,6 +2,8 @@ layout: "google" page_title: "Google: google_compute_route" sidebar_current: "docs-google-resource-route" +description: |- + Manages a network route within GCE. --- # google\_compute\_route diff --git a/website/source/docs/providers/heroku/index.html.markdown b/website/source/docs/providers/heroku/index.html.markdown index ba59c79c0..b04fd001c 100644 --- a/website/source/docs/providers/heroku/index.html.markdown +++ b/website/source/docs/providers/heroku/index.html.markdown @@ -2,6 +2,8 @@ layout: "heroku" page_title: "Provider: Heroku" sidebar_current: "docs-heroku-index" +description: |- + The Heroku provider is used to interact with the resources supported by Heroku. The provider needs to be configured with the proper credentials before it can be used. --- # Heroku Provider diff --git a/website/source/docs/providers/heroku/r/addon.html.markdown b/website/source/docs/providers/heroku/r/addon.html.markdown index eb7ced786..6bb0a9bd5 100644 --- a/website/source/docs/providers/heroku/r/addon.html.markdown +++ b/website/source/docs/providers/heroku/r/addon.html.markdown @@ -2,6 +2,8 @@ layout: "heroku" page_title: "Heroku: heroku_addon" sidebar_current: "docs-heroku-resource-addon" +description: |- + Provides a Heroku Add-On resource. These can be attach services to a Heroku app. --- # heroku\_addon diff --git a/website/source/docs/providers/heroku/r/app.html.markdown b/website/source/docs/providers/heroku/r/app.html.markdown index ee34649c1..aba45924d 100644 --- a/website/source/docs/providers/heroku/r/app.html.markdown +++ b/website/source/docs/providers/heroku/r/app.html.markdown @@ -2,6 +2,8 @@ layout: "heroku" page_title: "Heroku: heroku_app" sidebar_current: "docs-heroku-resource-app" +description: |- + Provides a Heroku App resource. This can be used to create and manage applications on Heroku. --- # heroku\_app @@ -36,10 +38,15 @@ The following arguments are supported: variables, but rather variables you want present. That is, other configuration variables set externally won't be removed by Terraform if they aren't present in this list. -* `organization` - (Optional) The organization name to create the application - under. You must have the correct permissions for this organization on Heroku to - create new applications inside an organization. +* `organization` - (Optional) A block that can be specified once to define + organization settings for this app. The fields for this block are + documented below. +The `organization` block supports: + +* `name` (string) - The name of the organization. +* `locked` (boolean) +* `personal` (boolean) ## Attributes Reference diff --git a/website/source/docs/providers/heroku/r/domain.html.markdown b/website/source/docs/providers/heroku/r/domain.html.markdown index b02b4b591..9d7b03cf4 100644 --- a/website/source/docs/providers/heroku/r/domain.html.markdown +++ b/website/source/docs/providers/heroku/r/domain.html.markdown @@ -2,6 +2,8 @@ layout: "heroku" page_title: "Heroku: heroku_domain" sidebar_current: "docs-heroku-resource-domain" +description: |- + Provides a Heroku App resource. This can be used to create and manage applications on Heroku. --- # heroku\_domain diff --git a/website/source/docs/providers/heroku/r/drain.html.markdown b/website/source/docs/providers/heroku/r/drain.html.markdown index 2fdc8c63a..819181c3e 100644 --- a/website/source/docs/providers/heroku/r/drain.html.markdown +++ b/website/source/docs/providers/heroku/r/drain.html.markdown @@ -2,6 +2,8 @@ layout: "heroku" page_title: "Heroku: heroku_drain" sidebar_current: "docs-heroku-resource-drain" +description: |- + Provides a Heroku Drain resource. This can be used to create and manage Log Drains on Heroku. --- # heroku\_drain diff --git a/website/source/docs/providers/index.html.markdown b/website/source/docs/providers/index.html.markdown index fb739259d..457c6ceaf 100644 --- a/website/source/docs/providers/index.html.markdown +++ b/website/source/docs/providers/index.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Providers" sidebar_current: "docs-providers" +description: |- + Terraform is used to create, manage, and manipulate infrastructure resources. Examples of resources include physical machines, VMs, network switches, containers, etc. Almost any infrastructure noun can be represented as a resource in Terraform. --- # Providers diff --git a/website/source/docs/providers/mailgun/index.html.markdown b/website/source/docs/providers/mailgun/index.html.markdown index 13c89923e..20a307cb6 100644 --- a/website/source/docs/providers/mailgun/index.html.markdown +++ b/website/source/docs/providers/mailgun/index.html.markdown @@ -2,6 +2,8 @@ layout: "mailgun" page_title: "Provider: Mailgun" sidebar_current: "docs-mailgun-index" +description: |- + The Mailgun provider is used to interact with the resources supported by Mailgun. The provider needs to be configured with the proper credentials before it can be used. --- # Provider diff --git a/website/source/docs/providers/mailgun/r/domain.html.markdown b/website/source/docs/providers/mailgun/r/domain.html.markdown index 82fae53da..3efe63426 100644 --- a/website/source/docs/providers/mailgun/r/domain.html.markdown +++ b/website/source/docs/providers/mailgun/r/domain.html.markdown @@ -2,6 +2,8 @@ layout: "mailgun" page_title: "Mailgun: mailgun_domain" sidebar_current: "docs-mailgun-resource-domain" +description: |- + Provides a Mailgun App resource. This can be used to create and manage applications on Mailgun. --- # mailgun\_domain diff --git a/website/source/docs/provisioners/connection.html.markdown b/website/source/docs/provisioners/connection.html.markdown index 61fb1f5e5..af55fb2e4 100644 --- a/website/source/docs/provisioners/connection.html.markdown +++ b/website/source/docs/provisioners/connection.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Provisioner Connections" sidebar_current: "docs-provisioners-connection" +description: |- + Many provisioners require access to the remote resource. For example, a provisioner may need to use ssh to connect to the resource. --- # Provisioner Connections diff --git a/website/source/docs/provisioners/file.html.markdown b/website/source/docs/provisioners/file.html.markdown index 11188e84b..692ce8e57 100644 --- a/website/source/docs/provisioners/file.html.markdown +++ b/website/source/docs/provisioners/file.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Provisioner: file" sidebar_current: "docs-provisioners-file" +description: |- + The `file` provisioner is used to copy files or directories from the machine executing Terraform to the newly created resource. The `file` provisioner only supports `ssh` type connections. --- # File Provisioner diff --git a/website/source/docs/provisioners/index.html.markdown b/website/source/docs/provisioners/index.html.markdown index 8fc766a3c..07d37feea 100644 --- a/website/source/docs/provisioners/index.html.markdown +++ b/website/source/docs/provisioners/index.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Provisioners" sidebar_current: "docs-provisioners" +description: |- + When a resource is initially created, provisioners can be executed to initialize that resource. This can be used to add resources to an inventory management system, run a configuration management tool, bootstrap the resource into a cluster, etc. --- # Provisioners diff --git a/website/source/docs/provisioners/local-exec.html.markdown b/website/source/docs/provisioners/local-exec.html.markdown index cba34c526..1a41194b2 100644 --- a/website/source/docs/provisioners/local-exec.html.markdown +++ b/website/source/docs/provisioners/local-exec.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Provisioner: local-exec" sidebar_current: "docs-provisioners-local" +description: |- + The `local-exec` provisioner invokes a local executable after a resource is created. This invokes a process on the machine running Terraform, not on the resource. See the `remote-exec` provisioner to run commands on the resource. --- # local-exec Provisioner diff --git a/website/source/docs/provisioners/remote-exec.html.markdown b/website/source/docs/provisioners/remote-exec.html.markdown index e431db5ab..0062fd254 100644 --- a/website/source/docs/provisioners/remote-exec.html.markdown +++ b/website/source/docs/provisioners/remote-exec.html.markdown @@ -2,6 +2,8 @@ layout: "docs" page_title: "Provisioner: remote-exec" sidebar_current: "docs-provisioners-remote" +description: |- + The `remote-exec` provisioner invokes a script on a remote resource after it is created. This can be used to run a configuration management tool, bootstrap into a cluster, etc. To invoke a local process, see the `local-exec` provisioner instead. The `remote-exec` provisioner only supports `ssh` type connections. --- # remote-exec Provisioner diff --git a/website/source/downloads.html.erb b/website/source/downloads.html.erb index 25fbe3906..834e18af3 100644 --- a/website/source/downloads.html.erb +++ b/website/source/downloads.html.erb @@ -2,6 +2,8 @@ layout: "downloads" page_title: "Download Terraform" sidebar_current: "downloads-terraform" +description: |- + Download Terraform ---

Download Terraform

@@ -39,7 +41,7 @@ sidebar_current: "downloads-terraform" diff --git a/website/source/intro/examples/aws.html.markdown b/website/source/intro/examples/aws.html.markdown index 04b9088ad..b7bf529aa 100644 --- a/website/source/intro/examples/aws.html.markdown +++ b/website/source/intro/examples/aws.html.markdown @@ -2,6 +2,8 @@ layout: "intro" page_title: "Basic Two-Tier AWS Architecture" sidebar_current: "examples-aws" +description: |- + This provides a template for running a simple two-tier architecture on Amazon Web services. The premise is that you have stateless app servers running behind an ELB serving traffic. --- # Basic Two-Tier AWS Architecture diff --git a/website/source/intro/examples/consul.html.markdown b/website/source/intro/examples/consul.html.markdown index 77066c73d..0f00f1b25 100644 --- a/website/source/intro/examples/consul.html.markdown +++ b/website/source/intro/examples/consul.html.markdown @@ -2,6 +2,8 @@ layout: "intro" page_title: "Consul Example" sidebar_current: "examples-consul" +description: |- + Consul is a tool for service discovery, configuration and orchestration. The Key/Value store it provides is often used to store application configuration and information about the infrastructure necessary to process requests. --- # Consul Example diff --git a/website/source/intro/examples/count.markdown b/website/source/intro/examples/count.markdown index 29ec62fef..ea7ed9884 100644 --- a/website/source/intro/examples/count.markdown +++ b/website/source/intro/examples/count.markdown @@ -2,11 +2,13 @@ layout: "intro" page_title: "Count" sidebar_current: "examples-count" +description: |- + The count parameter on resources can simplify configurations and let you scale resources by simply incrementing a number. --- # Count Example -[**Example Contents**](https://github.com/hashicorp/terraform/tree/master/examples/count) +[**Example Contents**](https://github.com/hashicorp/terraform/tree/master/examples/aws-count) The count parameter on resources can simplify configurations and let you scale resources by simply incrementing a number. diff --git a/website/source/intro/examples/cross-provider.markdown b/website/source/intro/examples/cross-provider.markdown index ad7435693..92cd44df3 100644 --- a/website/source/intro/examples/cross-provider.markdown +++ b/website/source/intro/examples/cross-provider.markdown @@ -2,6 +2,8 @@ layout: "intro" page_title: "Cross Provider" sidebar_current: "examples-cross-provider" +description: |- + This is a simple example of the cross-provider capabilities of Terraform. --- # Cross Provider Example diff --git a/website/source/intro/examples/index.html.markdown b/website/source/intro/examples/index.html.markdown index 708c86f13..421ac121e 100644 --- a/website/source/intro/examples/index.html.markdown +++ b/website/source/intro/examples/index.html.markdown @@ -2,6 +2,8 @@ layout: "intro" page_title: "Example Configurations" sidebar_current: "examples" +description: |- + These examples are designed to help you understand some of the ways Terraform can be used. --- # Example Configurations @@ -14,14 +16,10 @@ ask for input of things such as variables and API keys. If you want to conitnue using the example, you should save those parameters in a "terraform.tfvars" file or in a `provider` config bock. -
-
-Note: The examples use real providers that launch -real resources. That means they can cost money to -experiment with. To avoid unexpected charges, be sure to understand the price -of resources before launching them, and verify any unneeded resources -are cleaned up afterwards.
-
+~> **Warning!** The examples use real providers that launch _real_ resources. +That means they can cost money to experiment with. To avoid unexpected charges, +be sure to understand the price of resources before launching them, and verify +any unneeded resources are cleaned up afterwards. Experimenting in this way can help you learn how the Terraform lifecycle works, as well as how to repeatedly create and destroy infrastructure. diff --git a/website/source/intro/getting-started/build.html.md b/website/source/intro/getting-started/build.html.md index e2f41f839..4557403a0 100644 --- a/website/source/intro/getting-started/build.html.md +++ b/website/source/intro/getting-started/build.html.md @@ -2,6 +2,8 @@ layout: "intro" page_title: "Build Infrastructure" sidebar_current: "gettingstarted-build" +description: |- + With Terraform installed, let's dive right into it and start creating some infrastructure. --- # Build Infrastructure @@ -27,16 +29,10 @@ If you already have an AWS account, you may be charged some amount of money, but it shouldn't be more than a few dollars at most. -
-

-Note: If you're not using an account that qualifies -under the AWS -free-tier, -you may be charged to run these examples. The most you should -be charged should only be a few dollars, but we're not responsible -for any charges that may incur. -

-
+~> **Warning!** If you're not using an account that qualifies under the AWS +[free-tier](http://aws.amazon.com/free/), you may be charged to run these +examples. The most you should be charged should only be a few dollars, but +we're not responsible for any charges that may incur. ## Configuration diff --git a/website/source/intro/getting-started/change.html.md b/website/source/intro/getting-started/change.html.md index 9df323e05..4850bc808 100644 --- a/website/source/intro/getting-started/change.html.md +++ b/website/source/intro/getting-started/change.html.md @@ -2,6 +2,8 @@ layout: "intro" page_title: "Change Infrastructure" sidebar_current: "gettingstarted-change" +description: |- + In the previous page, you created your first infrastructure with Terraform: a single EC2 instance. In this page, we're going to modify that resource, and see how Terraform handles change. --- # Change Infrastructure diff --git a/website/source/intro/getting-started/dependencies.html.md b/website/source/intro/getting-started/dependencies.html.md index 62535fd1f..550546d29 100644 --- a/website/source/intro/getting-started/dependencies.html.md +++ b/website/source/intro/getting-started/dependencies.html.md @@ -2,6 +2,8 @@ layout: "intro" page_title: "Resource Dependencies" sidebar_current: "gettingstarted-deps" +description: |- + In this page, we're going to introduce resource dependencies, where we'll not only see a configuration with multiple resources for the first time, but also scenarios where resource parameters use information from other resources. --- # Resource Dependencies diff --git a/website/source/intro/getting-started/destroy.html.md b/website/source/intro/getting-started/destroy.html.md index 9c0d21ad3..c08976561 100644 --- a/website/source/intro/getting-started/destroy.html.md +++ b/website/source/intro/getting-started/destroy.html.md @@ -2,6 +2,8 @@ layout: "intro" page_title: "Destroy Infrastructure" sidebar_current: "gettingstarted-destroy" +description: |- + We've now seen how to build and change infrastructure. Before we move on to creating multiple resources and showing resource dependencies, we're going to go over how to completely destroy the Terraform-managed infrastructure. --- # Destroy Infrastructure diff --git a/website/source/intro/getting-started/install.html.markdown b/website/source/intro/getting-started/install.html.markdown index e5ff20512..dc4729bc9 100644 --- a/website/source/intro/getting-started/install.html.markdown +++ b/website/source/intro/getting-started/install.html.markdown @@ -2,6 +2,8 @@ layout: "intro" page_title: "Installing Terraform" sidebar_current: "gettingstarted-install" +description: |- + Terraform must first be installed on your machine. Terraform is distributed as a binary package for all supported platforms and architecture. This page will not cover how to compile Terraform from source. --- # Install Terraform diff --git a/website/source/intro/getting-started/modules.html.md b/website/source/intro/getting-started/modules.html.md index b9d63abce..d7008dd12 100644 --- a/website/source/intro/getting-started/modules.html.md +++ b/website/source/intro/getting-started/modules.html.md @@ -2,6 +2,8 @@ layout: "intro" page_title: "Modules" sidebar_current: "gettingstarted-modules" +description: |- + Up to this point, we've been configuring Terraform by editing Terraform configurations directly. As our infrastructure grows, this practice has a few key problems: a lack of organization, a lack of reusability, and difficulties in management for teams. --- # Modules @@ -19,16 +21,9 @@ This section of the getting started will cover the basics of using modules. Writing modules is covered in more detail in the [modules documentation](/docs/modules/index.html). -
-

-Warning: The examples on this page are -not eligible for the -AWS -free-tier. Do not execute -the examples on this page unless you're willing to spend a small -amount of money. -

-
+~> **Warning!** The examples on this page are _**not** eligible_ for the AWS +[free-tier](http://aws.amazon.com/free/). Do not execute the examples on this +page unless you're willing to spend a small amount of money. ## Using Modules diff --git a/website/source/intro/getting-started/next-steps.html.markdown b/website/source/intro/getting-started/next-steps.html.markdown index ae4a92e5f..bc0653944 100644 --- a/website/source/intro/getting-started/next-steps.html.markdown +++ b/website/source/intro/getting-started/next-steps.html.markdown @@ -2,6 +2,8 @@ layout: "intro" page_title: "Next Steps" sidebar_current: "gettingstarted-nextsteps" +description: |- + That concludes the getting started guide for Terraform. Hopefully you're now able to not only see what Terraform is useful for, but you're also able to put this knowledge to use to improve building your own infrastructure. --- # Next Steps diff --git a/website/source/intro/getting-started/outputs.html.md b/website/source/intro/getting-started/outputs.html.md index 9eb319dd7..2d537097e 100644 --- a/website/source/intro/getting-started/outputs.html.md +++ b/website/source/intro/getting-started/outputs.html.md @@ -2,6 +2,8 @@ layout: "intro" page_title: "Output Variables" sidebar_current: "gettingstarted-outputs" +description: |- + In the previous section, we introduced input variables as a way to parameterize Terraform configurations. In this page, we introduce output variables as a way to organize data to be easily queried and shown back to the Terraform user. --- # Output Variables diff --git a/website/source/intro/getting-started/provision.html.md b/website/source/intro/getting-started/provision.html.md index 0aeb497f8..69aa053ff 100644 --- a/website/source/intro/getting-started/provision.html.md +++ b/website/source/intro/getting-started/provision.html.md @@ -2,6 +2,8 @@ layout: "intro" page_title: "Provision" sidebar_current: "gettingstarted-provision" +description: |- + You're now able to create and modify infrastructure. This page introduces how to use provisioners to run basic shell scripts on instances when they're created. --- # Provision diff --git a/website/source/intro/getting-started/variables.html.md b/website/source/intro/getting-started/variables.html.md index d9bb5f6a1..e0c385509 100644 --- a/website/source/intro/getting-started/variables.html.md +++ b/website/source/intro/getting-started/variables.html.md @@ -2,6 +2,8 @@ layout: "intro" page_title: "Input Variables" sidebar_current: "gettingstarted-variables" +description: |- + You now have enough Terraform knowledge to create useful configurations, but we're still hardcoding access keys, AMIs, etc. To become truly shareable and commitable to version control, we need to parameterize the configurations. This page introduces input variables as a way to do this. --- # Input Variables diff --git a/website/source/intro/index.html.markdown b/website/source/intro/index.html.markdown index f339ac20b..258303313 100644 --- a/website/source/intro/index.html.markdown +++ b/website/source/intro/index.html.markdown @@ -2,6 +2,8 @@ layout: "intro" page_title: "Introduction" sidebar_current: "what" +description: |- + Welcome to the intro guide to Terraform! This guide is the best place to start with Terraform. We cover what Terraform is, what problems it can solve, how it compares to existing software, and contains a quick start for using Terraform. --- # Introduction to Terraform diff --git a/website/source/intro/use-cases.html.markdown b/website/source/intro/use-cases.html.markdown index d34edda16..1b2db5a07 100644 --- a/website/source/intro/use-cases.html.markdown +++ b/website/source/intro/use-cases.html.markdown @@ -2,6 +2,8 @@ layout: "intro" page_title: "Use Cases" sidebar_current: "use-cases" +description: |- + Before understanding use cases, it's useful to know what Terraform is. This page lists some concrete use cases for Terraform, but the possible use cases are much broader than what we cover. Due to its extensible nature, providers and provisioners can be added to further extend Terraform's ability to manipulate resources. --- # Use Cases diff --git a/website/source/intro/vs/boto.html.markdown b/website/source/intro/vs/boto.html.markdown index 7fec3a8e6..f6a2912d6 100644 --- a/website/source/intro/vs/boto.html.markdown +++ b/website/source/intro/vs/boto.html.markdown @@ -2,6 +2,8 @@ layout: "intro" page_title: "Terraform vs. Boto, Fog, etc." sidebar_current: "vs-other-boto" +description: |- + Libraries like Boto, Fog, etc. are used to provide native access to cloud providers and services by using their APIs. Some libraries are focused on specific clouds, while others attempt to bridge them all and mask the semantic differences. Using a client library only provides low-level access to APIs, requiring application developers to create their own tooling to build and manage their infrastructure. --- # Terraform vs. Boto, Fog, etc. diff --git a/website/source/intro/vs/chef-puppet.html.markdown b/website/source/intro/vs/chef-puppet.html.markdown index 562d6c1a8..9e5a820fe 100644 --- a/website/source/intro/vs/chef-puppet.html.markdown +++ b/website/source/intro/vs/chef-puppet.html.markdown @@ -2,6 +2,8 @@ layout: "intro" page_title: "Terraform vs. Chef, Puppet, etc." sidebar_current: "vs-other-chef" +description: |- + Configuration management tools install and manage software on a machine that already exists. Terraform is not a configuration management tool, and it allows existing tooling to focus on their strengths: bootstrapping and initializing resources. --- # Terraform vs. Chef, Puppet, etc. diff --git a/website/source/intro/vs/cloudformation.html.markdown b/website/source/intro/vs/cloudformation.html.markdown index 098474891..37eda92a9 100644 --- a/website/source/intro/vs/cloudformation.html.markdown +++ b/website/source/intro/vs/cloudformation.html.markdown @@ -2,6 +2,8 @@ layout: "intro" page_title: "Terraform vs. CloudFormation, Heat, etc." sidebar_current: "vs-other-cloudformation" +description: |- + Tools like CloudFormation, Heat, etc. allow the details of an infrastructure to be codified into a configuration file. The configuration files allow the infrastructure to be elastically created, modified and destroyed. Terraform is inspired by the problems they solve. --- # Terraform vs. CloudFormation, Heat, etc. diff --git a/website/source/intro/vs/custom.html.markdown b/website/source/intro/vs/custom.html.markdown index 772019428..4962bdba0 100644 --- a/website/source/intro/vs/custom.html.markdown +++ b/website/source/intro/vs/custom.html.markdown @@ -2,6 +2,8 @@ layout: "intro" page_title: "Terraform vs. Custom Solutions" sidebar_current: "vs-other-custom" +description: |- + Most organizations start by manually managing infrastructure through simple scripts or web-based interfaces. As the infrastructure grows, any manual approach to management becomes both error-prone and tedious, and many organizations begin to home-roll tooling to help automate the mechanical processes involved. --- # Terraform vs. Custom Solutions diff --git a/website/source/intro/vs/index.html.markdown b/website/source/intro/vs/index.html.markdown index 400a10101..472fefcbc 100644 --- a/website/source/intro/vs/index.html.markdown +++ b/website/source/intro/vs/index.html.markdown @@ -2,6 +2,8 @@ layout: "intro" page_title: "Terraform vs. Other Software" sidebar_current: "vs-other" +description: |- + Terraform provides a flexible abstraction of resources and providers. This model allows for representing everything from physical hardware, virtual machines, and containers, to email and DNS providers. Because of this flexibility, Terraform can be used to solve many different problems. This means there are a number of existing tools that overlap with the capabilities of Terraform. We compare Terraform to a number of these tools, but it should be noted that Terraform is not mutually exclusive with other systems. It can be used to manage a single application, or the entire datacenter. --- # Terraform vs. Other Software diff --git a/website/source/layouts/_footer.erb b/website/source/layouts/_footer.erb index 3f8dee3b7..911db6417 100644 --- a/website/source/layouts/_footer.erb +++ b/website/source/layouts/_footer.erb @@ -22,6 +22,16 @@ + + <%= javascript_include_tag "application" %> diff --git a/website/source/layouts/_meta.erb b/website/source/layouts/_meta.erb index c05381bb6..1f21b8dae 100644 --- a/website/source/layouts/_meta.erb +++ b/website/source/layouts/_meta.erb @@ -1,35 +1,21 @@ - - - - + + + + - + -<%= current_page.data.page_title ? "#{current_page.data.page_title} - " : "" %>Terraform + <%= [current_page.data.page_title, "Terraform by HashiCorp"].compact.join(" - ") %> - -<%= stylesheet_link_tag "application" %> + <%= stylesheet_link_tag "application" %> - - + - - + <%= yield_content :head %> + -<%= yield_content :head %> - - - -" class="page-<%= current_page.data.page_title ? "#{current_page.data.page_title} layout-#{current_page.data.layout} page-sub" : "home layout-#{current_page.data.layout}" %>"> + " class="page-<%= current_page.data.page_title ? "#{current_page.data.page_title} layout-#{current_page.data.layout} page-sub" : "home layout-#{current_page.data.layout}" %>"> diff --git a/website/source/robots.txt b/website/source/robots.txt new file mode 100644 index 000000000..190c6ce04 --- /dev/null +++ b/website/source/robots.txt @@ -0,0 +1,8 @@ +--- +layout: false +noindex: true +--- + +User-agent: * +Disallow: /404 +Disallow: /500 diff --git a/website/source/sitemap.xml.builder b/website/source/sitemap.xml.builder new file mode 100644 index 000000000..a08b5b0e3 --- /dev/null +++ b/website/source/sitemap.xml.builder @@ -0,0 +1,19 @@ +--- +layout: false +--- + +xml.instruct! +xml.urlset 'xmlns' => "http://www.sitemaps.org/schemas/sitemap/0.9" do + sitemap + .resources + .select { |page| page.path =~ /\.html/ } + .select { |page| !page.data.noindex } + .each do |page| + xml.url do + xml.loc File.join(base_url, page.url) + xml.lastmod Date.today.to_time.iso8601 + xml.changefreq page.data.changefreq || "monthly" + xml.priority page.data.priority || "0.5" + end + end +end