diff --git a/CHANGELOG.md b/CHANGELOG.md index 543badfa526a..bc5bb85eb33b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,16 +7,18 @@ FEATURES: IMPROVEMENTS: + * **New config function: `formatlist`** - Format lists in a similar way to `format`. + Useful for creating URLs from a list of IPs. [GH-1829] * provider/aws: `aws_s3_bucket` exports `hosted_zone_id` and `region` [GH-1865] * provider/aws: `aws_route53_record` exports `fqdn` [GH-1847] * provider/google: `google_compute_instance` `scratch` attribute added [GH-1920] - * **New config function: `formatlist`** - Format lists in a similar way to `format`. - Useful for creating URLs from a list of IPs. [GH-1829] BUG FIXES: * core: fix "resource not found" for interpolation issues with modules * core: fix unflattenable error for orphans [GH-1922] + * core: fix deadlock with create-before-destroy + modules [GH-1949] + * core: fix "no roots found" error with create-before-destroy [GH-1953] * command/push: local vars override remote ones [GH-1881] * provider/aws: Mark `aws_security_group` description as `ForceNew` [GH-1871] * provider/aws: `aws_db_instance` ARN value is correct [GH-1910] diff --git a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go index 97a7325c7758..2da5ee8bf348 100644 --- a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go +++ b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go @@ -53,6 +53,7 @@ func resourceBlockStorageVolumeV1() *schema.Resource { Type: schema.TypeMap, Optional: true, ForceNew: false, + Computed: true, }, "snapshot_id": &schema.Schema{ Type: schema.TypeString, diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 02dafe5ae8e4..44fca923f6fa 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -209,7 +209,6 @@ func resourceComputeInstanceV2() *schema.Resource { Schema: map[string]*schema.Schema{ "id": &schema.Schema{ Type: schema.TypeString, - Optional: true, Computed: true, }, "volume_id": &schema.Schema{ @@ -955,7 +954,6 @@ func resourceComputeVolumeAttachmentHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) buf.WriteString(fmt.Sprintf("%s-", m["volume_id"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["device"].(string))) return hashcode.String(buf.String()) } diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go index 540232da52c8..95b3b446f085 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go @@ -46,7 +46,7 @@ func TestAccComputeV2Instance_basic(t *testing.T) { }) } -func _TestAccComputeV2Instance_volumeAttach(t *testing.T) { +func TestAccComputeV2Instance_volumeAttach(t *testing.T) { var instance servers.Server var volume volumes.Volume @@ -229,6 +229,7 @@ var testAccComputeV2Instance_volumeAttach = fmt.Sprintf(` resource "openstack_compute_instance_v2" "foo" { region = "%s" name = "terraform-test" + security_groups = ["default"] volume { volume_id = "${openstack_blockstorage_volume_v1.myvol.id}" } diff --git a/command/push.go b/command/push.go index a4649a822279..80d72c4dc030 100644 --- a/command/push.go +++ b/command/push.go @@ -211,6 +211,13 @@ Options: -token= Access token to use to upload. If blank or unspecified, the ATLAS_TOKEN environmental variable will be used. + -var 'foo=bar' Set a variable in the Terraform configuration. This + flag can be set multiple times. + + -var-file=foo Set variables in the Terraform configuration from + a file. If "terraform.tfvars" is present, it will be + automatically loaded if this flag is not specified. + -vcs=true If true (default), push will upload only files comitted to your VCS, if detected. diff --git a/terraform/context_test.go b/terraform/context_test.go index 15bb62778471..31308e8d5277 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -40,6 +40,43 @@ func TestContext2Plan(t *testing.T) { } } +func TestContext2Plan_createBefore_maintainRoot(t *testing.T) { + m := testModule(t, "plan-cbd-maintain-root") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "in": "a,b,c", + }, + }) + + plan, err := ctx.Plan() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(` +DIFF: + +CREATE: aws_instance.bar.0 +CREATE: aws_instance.bar.1 +CREATE: aws_instance.foo.0 +CREATE: aws_instance.foo.1 + +STATE: + + + `) + if actual != expected { + t.Fatalf("expected:\n%s, got:\n%s", expected, actual) + } +} + func TestContext2Plan_emptyDiff(t *testing.T) { m := testModule(t, "plan-empty") p := testProvider("aws") @@ -139,6 +176,56 @@ func TestContext2Plan_moduleCycle(t *testing.T) { } } +func TestContext2Plan_moduleDeadlock(t *testing.T) { + m := testModule(t, "plan-module-deadlock") + p := testProvider("aws") + p.DiffFn = testDiffFn + timeout := make(chan bool, 1) + done := make(chan bool, 1) + go func() { + time.Sleep(3 * time.Second) + timeout <- true + }() + go func() { + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan() + done <- true + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(` +DIFF: + +module.child: + CREATE: aws_instance.foo.0 + CREATE: aws_instance.foo.1 + CREATE: aws_instance.foo.2 + +STATE: + + + `) + if actual != expected { + t.Fatalf("expected:\n%sgot:\n%s", expected, actual) + } + }() + + select { + case <-timeout: + t.Fatalf("timed out! probably deadlock") + case <-done: + // ok + } +} + func TestContext2Plan_moduleInput(t *testing.T) { m := testModule(t, "plan-module-input") p := testProvider("aws") diff --git a/terraform/graph_config_node_resource.go b/terraform/graph_config_node_resource.go index 30667cb43151..bf1803b00af9 100644 --- a/terraform/graph_config_node_resource.go +++ b/terraform/graph_config_node_resource.go @@ -311,6 +311,7 @@ func (n *GraphNodeConfigResourceFlat) DestroyNode(mode GraphNodeDestroyMode) Gra return &graphNodeResourceDestroyFlat{ graphNodeResourceDestroy: node, PathValue: n.PathValue, + FlatCreateNode: n, } } @@ -318,6 +319,9 @@ type graphNodeResourceDestroyFlat struct { *graphNodeResourceDestroy PathValue []string + + // Needs to be able to properly yield back a flattened create node to prevent + FlatCreateNode *GraphNodeConfigResourceFlat } func (n *graphNodeResourceDestroyFlat) Name() string { @@ -329,6 +333,10 @@ func (n *graphNodeResourceDestroyFlat) Path() []string { return n.PathValue } +func (n *graphNodeResourceDestroyFlat) CreateNode() dag.Vertex { + return n.FlatCreateNode +} + // graphNodeResourceDestroy represents the logical destruction of a // resource. This node doesn't mean it will be destroyed for sure, but // instead that if a destroy were to happen, it must happen at this point. diff --git a/terraform/graph_dot.go b/terraform/graph_dot.go index b2c064e63933..b9313fa24227 100644 --- a/terraform/graph_dot.go +++ b/terraform/graph_dot.go @@ -178,7 +178,7 @@ func graphDotFindOrigins(g *Graph) ([]dag.Vertex, error) { } if len(origin) == 0 { - return nil, fmt.Errorf("No DOT origin nodes found.\nGraph: %s", g) + return nil, fmt.Errorf("No DOT origin nodes found.\nGraph: %s", g.String()) } return origin, nil diff --git a/terraform/test-fixtures/plan-cbd-maintain-root/main.tf b/terraform/test-fixtures/plan-cbd-maintain-root/main.tf new file mode 100644 index 000000000000..9a826475ce92 --- /dev/null +++ b/terraform/test-fixtures/plan-cbd-maintain-root/main.tf @@ -0,0 +1,13 @@ +resource "aws_instance" "foo" { + count = "2" + lifecycle { create_before_destroy = true } +} + +resource "aws_instance" "bar" { + count = "2" + lifecycle { create_before_destroy = true } +} + +output "out" { + value = "${aws_instance.foo.0.id}" +} diff --git a/terraform/test-fixtures/plan-module-deadlock/child/main.tf b/terraform/test-fixtures/plan-module-deadlock/child/main.tf new file mode 100644 index 000000000000..ccee7bfb40bb --- /dev/null +++ b/terraform/test-fixtures/plan-module-deadlock/child/main.tf @@ -0,0 +1,4 @@ +resource "aws_instance" "foo" { + count = "${length("abc")}" + lifecycle { create_before_destroy = true } +} diff --git a/terraform/test-fixtures/plan-module-deadlock/main.tf b/terraform/test-fixtures/plan-module-deadlock/main.tf new file mode 100644 index 000000000000..1f95749fa7ea --- /dev/null +++ b/terraform/test-fixtures/plan-module-deadlock/main.tf @@ -0,0 +1,3 @@ +module "child" { + source = "./child" +} diff --git a/terraform/transform_destroy.go b/terraform/transform_destroy.go index 3fe5856642a9..9fb235107015 100644 --- a/terraform/transform_destroy.go +++ b/terraform/transform_destroy.go @@ -1,8 +1,6 @@ package terraform -import ( - "github.com/hashicorp/terraform/dag" -) +import "github.com/hashicorp/terraform/dag" type GraphNodeDestroyMode byte @@ -193,6 +191,14 @@ func (t *CreateBeforeDestroyTransformer) Transform(g *Graph) error { // This ensures that. for _, sourceRaw := range g.UpEdges(cn).List() { source := sourceRaw.(dag.Vertex) + + // If the graph has a "root" node (one added by a RootTransformer and not + // just a resource that happens to have no ancestors), we don't want to + // add any edges to it, because then it ceases to be a root. + if _, ok := source.(graphNodeRoot); ok { + continue + } + connect = append(connect, dag.BasicEdge(dn, source)) } diff --git a/terraform/transform_expand.go b/terraform/transform_expand.go index 85d660206389..440c522f397c 100644 --- a/terraform/transform_expand.go +++ b/terraform/transform_expand.go @@ -1,6 +1,7 @@ package terraform import ( + "fmt" "log" "github.com/hashicorp/terraform/dag" @@ -59,3 +60,26 @@ func (n *GraphNodeBasicSubgraph) Name() string { func (n *GraphNodeBasicSubgraph) Subgraph() *Graph { return n.Graph } + +func (n *GraphNodeBasicSubgraph) Flatten(p []string) (dag.Vertex, error) { + return &graphNodeBasicSubgraphFlat{ + GraphNodeBasicSubgraph: n, + PathValue: p, + }, nil +} + +// Same as GraphNodeBasicSubgraph, but for flattening +type graphNodeBasicSubgraphFlat struct { + *GraphNodeBasicSubgraph + + PathValue []string +} + +func (n *graphNodeBasicSubgraphFlat) Name() string { + return fmt.Sprintf( + "%s.%s", modulePrefixStr(n.PathValue), n.GraphNodeBasicSubgraph.Name()) +} + +func (n *graphNodeBasicSubgraphFlat) Path() []string { + return n.PathValue +} diff --git a/terraform/transform_provider.go b/terraform/transform_provider.go index b9ac53ccbfc3..4863a4c98712 100644 --- a/terraform/transform_provider.go +++ b/terraform/transform_provider.go @@ -173,6 +173,14 @@ func (n *graphNodeDisabledProvider) EvalTree() EvalNode { } } +// GraphNodeFlattenable impl. +func (n *graphNodeDisabledProvider) Flatten(p []string) (dag.Vertex, error) { + return &graphNodeDisabledProviderFlat{ + graphNodeDisabledProvider: n, + PathValue: p, + }, nil +} + func (n *graphNodeDisabledProvider) Name() string { return fmt.Sprintf("%s (disabled)", dag.VertexName(n.GraphNodeProvider)) } @@ -205,6 +213,51 @@ func (n *graphNodeDisabledProvider) ProviderConfig() *config.RawConfig { return n.GraphNodeProvider.ProviderConfig() } +// Same as graphNodeDisabledProvider, but for flattening +type graphNodeDisabledProviderFlat struct { + *graphNodeDisabledProvider + + PathValue []string +} + +func (n *graphNodeDisabledProviderFlat) Name() string { + return fmt.Sprintf( + "%s.%s", modulePrefixStr(n.PathValue), n.graphNodeDisabledProvider.Name()) +} + +func (n *graphNodeDisabledProviderFlat) Path() []string { + return n.PathValue +} + +func (n *graphNodeDisabledProviderFlat) ProviderName() string { + return fmt.Sprintf( + "%s.%s", modulePrefixStr(n.PathValue), + n.graphNodeDisabledProvider.ProviderName()) +} + +// GraphNodeDependable impl. +func (n *graphNodeDisabledProviderFlat) DependableName() []string { + return []string{n.Name()} +} + +func (n *graphNodeDisabledProviderFlat) DependentOn() []string { + var result []string + + // If we're in a module, then depend on our parent's provider + if len(n.PathValue) > 1 { + prefix := modulePrefixStr(n.PathValue[:len(n.PathValue)-1]) + if prefix != "" { + prefix += "." + } + + result = append(result, fmt.Sprintf( + "%s%s", + prefix, n.graphNodeDisabledProvider.Name())) + } + + return result +} + type graphNodeMissingProvider struct { ProviderNameValue string } diff --git a/website/source/docs/configuration/interpolation.html.md b/website/source/docs/configuration/interpolation.html.md index fa2e756803be..5c3ce791d5f6 100644 --- a/website/source/docs/configuration/interpolation.html.md +++ b/website/source/docs/configuration/interpolation.html.md @@ -19,6 +19,9 @@ variables, attributes of resources, call functions, etc. You can also perform simple math in interpolations, allowing you to write expressions such as `${count.index+1}`. +You can escape interpolation with double dollar signs: `$${foo}` +will be rendered as a literal `${foo}`. + ## Available Variables **To reference user variables**, use the `var.` prefix followed by the @@ -123,7 +126,7 @@ The supported built-in functions are: * `split(delim, string)` - Splits the string previously created by `join` back into a list. This is useful for pushing lists through module outputs since they currently only support string values. Depending on the - use, the string this is being performed within may need to be wrapped + use, the string this is being performed within may need to be wrapped in brackets to indicate that the output is actually a list, e.g. `a_resource_param = ["${split(",", var.CSV_STRING)}"]`. Example: `split(",", module.amod.server_ids)` diff --git a/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown b/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown index 600c04c7e304..ccc4429ddb9e 100644 --- a/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown +++ b/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown @@ -8,7 +8,7 @@ description: |- # aws\_elasticache\_cluster -Provides an ElastiCache Cluster resource. +Provides an ElastiCache Cluster resource. ## Example Usage @@ -26,37 +26,37 @@ resource "aws_elasticache_cluster" "bar" { The following arguments are supported: -* `cluster_id` – (Required) Group identifier. This parameter is stored as a +* `cluster_id` – (Required) Group identifier. This parameter is stored as a lowercase string * `engine` – (Required) Name of the cache engine to be used for this cache cluster. Valid values for this parameter are `memcached` or `redis` * `engine_version` – (Optional) Version number of the cache engine to be used. -See [Selecting a Cache Engine and Version](http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/SelectEngine.html) -in the AWS Documentation center for supported versions +See [Selecting a Cache Engine and Version](http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/SelectEngine.html) +in the AWS Documentation center for supported versions -* `node_type` – (Required) The compute and memory capacity of the nodes. See +* `node_type` – (Required) The compute and memory capacity of the nodes. See [Available Cache Node Types](http://aws.amazon.com/elasticache/details#Available_Cache_Node_Types) for supported node types -* `num_cache_nodes` – (Required) The initial number of cache nodes that the +* `num_cache_nodes` – (Required) The initial number of cache nodes that the cache cluster will have. For Redis, this value must be 1. For Memcache, this value must be between 1 and 20 -* `parameter_group_name` – (Optional) Name of the parameter group to associate +* `parameter_group_name` – (Required) Name of the parameter group to associate with this cache cluster -* `port` – (Optional) The port number on which each of the cache nodes will +* `port` – (Optional) The port number on which each of the cache nodes will accept connections. Default 11211. -* `subnet_group_name` – (Optional, VPC only) Name of the subnet group to be used +* `subnet_group_name` – (Optional, VPC only) Name of the subnet group to be used for the cache cluster. -* `security_group_names` – (Optional, EC2 Classic only) List of security group +* `security_group_names` – (Optional, EC2 Classic only) List of security group names to associate with this cache cluster -* `security_group_ids` – (Optional, VPC only) One or more VPC security groups associated +* `security_group_ids` – (Optional, VPC only) One or more VPC security groups associated with the cache cluster @@ -64,13 +64,13 @@ names to associate with this cache cluster The following attributes are exported: -* `cluster_id` -* `engine` +* `cluster_id` +* `engine` * `engine_version` * `node_type` * `num_cache_nodes` * `parameter_group_name` -* `port` +* `port` * `subnet_group_name` * `security_group_names` * `security_group_ids` diff --git a/website/source/docs/providers/aws/r/iam_group_policy.html.markdown b/website/source/docs/providers/aws/r/iam_group_policy.html.markdown index 862e12fec6f6..9916e8ca05d3 100644 --- a/website/source/docs/providers/aws/r/iam_group_policy.html.markdown +++ b/website/source/docs/providers/aws/r/iam_group_policy.html.markdown @@ -45,7 +45,7 @@ The following arguments are supported: * `policy` - (Required) The policy document. This is a JSON formatted string. The heredoc syntax or `file` funciton is helpful here. * `name` - (Required) Name of the policy. -* `user` - (Required) The IAM group to attach to the policy. +* `group` - (Required) The IAM group to attach to the policy. ## Attributes Reference 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 83757be39bcc..0e253e707ed5 100644 --- a/website/source/docs/providers/aws/r/security_group.html.markdown +++ b/website/source/docs/providers/aws/r/security_group.html.markdown @@ -112,7 +112,7 @@ be in place, you can use this `egress` block: from_port = 0 to_port = 0 protocol = "-1" - cidr_block = "0.0.0.0/0" + cidr_blocks = ["0.0.0.0/0"] } ## Attributes Reference