diff --git a/builtin/providers/aws/diff_suppress_funcs.go b/builtin/providers/aws/diff_suppress_funcs.go index 3a6d7076d7..5c96c6696b 100644 --- a/builtin/providers/aws/diff_suppress_funcs.go +++ b/builtin/providers/aws/diff_suppress_funcs.go @@ -1,6 +1,8 @@ package aws import ( + "bytes" + "encoding/json" "log" "strings" @@ -42,3 +44,17 @@ func suppressAwsDbEngineVersionDiffs(k, old, new string, d *schema.ResourceData) // Throw a diff by default return false } + +func suppressEquivalentJsonDiffs(k, old, new string, d *schema.ResourceData) bool { + ob := bytes.NewBufferString("") + if err := json.Compact(ob, []byte(old)); err != nil { + return false + } + + nb := bytes.NewBufferString("") + if err := json.Compact(nb, []byte(new)); err != nil { + return false + } + + return jsonBytesEqual(ob.Bytes(), nb.Bytes()) +} diff --git a/builtin/providers/aws/diff_suppress_funcs_test.go b/builtin/providers/aws/diff_suppress_funcs_test.go new file mode 100644 index 0000000000..0727a1042b --- /dev/null +++ b/builtin/providers/aws/diff_suppress_funcs_test.go @@ -0,0 +1,31 @@ +package aws + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/schema" +) + +func TestSuppressEquivalentJsonDiffsWhitespaceAndNoWhitespace(t *testing.T) { + d := new(schema.ResourceData) + + noWhitespace := `{"test":"test"}` + whitespace := ` +{ + "test": "test" +}` + + if !suppressEquivalentJsonDiffs("", noWhitespace, whitespace, d) { + t.Errorf("Expected suppressEquivalentJsonDiffs to return true for %s == %s", noWhitespace, whitespace) + } + + noWhitespaceDiff := `{"test":"test"}` + whitespaceDiff := ` +{ + "test": "tested" +}` + + if suppressEquivalentJsonDiffs("", noWhitespaceDiff, whitespaceDiff, d) { + t.Errorf("Expected suppressEquivalentJsonDiffs to return false for %s == %s", noWhitespaceDiff, whitespaceDiff) + } +} diff --git a/builtin/providers/aws/resource_aws_dms_replication_task.go b/builtin/providers/aws/resource_aws_dms_replication_task.go index c797b82c5f..dbd60da97a 100644 --- a/builtin/providers/aws/resource_aws_dms_replication_task.go +++ b/builtin/providers/aws/resource_aws_dms_replication_task.go @@ -57,9 +57,10 @@ func resourceAwsDmsReplicationTask() *schema.Resource { ValidateFunc: validateDmsReplicationTaskId, }, "replication_task_settings": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validateJsonString, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateJsonString, + DiffSuppressFunc: suppressEquivalentJsonDiffs, }, "source_endpoint_arn": { Type: schema.TypeString, @@ -68,9 +69,10 @@ func resourceAwsDmsReplicationTask() *schema.Resource { ValidateFunc: validateArn, }, "table_mappings": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validateJsonString, + Type: schema.TypeString, + Required: true, + ValidateFunc: validateJsonString, + DiffSuppressFunc: suppressEquivalentJsonDiffs, }, "tags": { Type: schema.TypeMap, diff --git a/builtin/providers/aws/resource_aws_ecs_task_definition.go b/builtin/providers/aws/resource_aws_ecs_task_definition.go index 5a81ec2b8b..2734afba96 100644 --- a/builtin/providers/aws/resource_aws_ecs_task_definition.go +++ b/builtin/providers/aws/resource_aws_ecs_task_definition.go @@ -70,11 +70,13 @@ func resourceAwsEcsTaskDefinition() *schema.Resource { "name": { Type: schema.TypeString, Required: true, + ForceNew: true, }, "host_path": { Type: schema.TypeString, Optional: true, + ForceNew: true, }, }, }, diff --git a/builtin/providers/aws/resource_aws_ecs_task_definition_test.go b/builtin/providers/aws/resource_aws_ecs_task_definition_test.go index c80f0fe6b5..a414130cdf 100644 --- a/builtin/providers/aws/resource_aws_ecs_task_definition_test.go +++ b/builtin/providers/aws/resource_aws_ecs_task_definition_test.go @@ -135,6 +135,41 @@ func TestAccAWSEcsTaskDefinition_constraint(t *testing.T) { }) } +func TestAccAWSEcsTaskDefinition_changeVolumesForcesNewResource(t *testing.T) { + var before ecs.TaskDefinition + var after ecs.TaskDefinition + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEcsTaskDefinitionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEcsTaskDefinition, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.jenkins", &before), + ), + }, + { + Config: testAccAWSEcsTaskDefinitionUpdatedVolume, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.jenkins", &after), + testAccCheckEcsTaskDefinitionRecreated(t, &before, &after), + ), + }, + }, + }) +} + +func testAccCheckEcsTaskDefinitionRecreated(t *testing.T, + before, after *ecs.TaskDefinition) resource.TestCheckFunc { + return func(s *terraform.State) error { + if *before.Revision == *after.Revision { + t.Fatalf("Expected change of TaskDefinition Revisions, but both were %v", before.Revision) + } + return nil + } +} + func testAccCheckAWSTaskDefinitionConstraintsAttrs(def *ecs.TaskDefinition) resource.TestCheckFunc { return func(s *terraform.State) error { if len(def.PlacementConstraints) != 1 { @@ -319,6 +354,55 @@ TASK_DEFINITION } ` +var testAccAWSEcsTaskDefinitionUpdatedVolume = ` +resource "aws_ecs_task_definition" "jenkins" { + family = "terraform-acc-test" + container_definitions = < 0 { - ebs.Iops = aws.Int64(int64(v)) + if "io1" == strings.ToLower(v) { + // Condition: This parameter is required for requests to create io1 + // volumes; it is not used in requests to create gp2, st1, sc1, or + // standard volumes. + // See: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_EbsBlockDevice.html + if v, ok := bd["iops"].(int); ok && v > 0 { + ebs.Iops = aws.Int64(int64(v)) + } + } } blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{ diff --git a/builtin/providers/aws/resource_aws_instance_test.go b/builtin/providers/aws/resource_aws_instance_test.go index aae53ecbde..f4ace2c444 100644 --- a/builtin/providers/aws/resource_aws_instance_test.go +++ b/builtin/providers/aws/resource_aws_instance_test.go @@ -1060,7 +1060,6 @@ resource "aws_instance" "foo" { root_block_device { volume_type = "gp2" volume_size = 11 - iops = 330 } } ` diff --git a/builtin/providers/aws/resource_aws_spot_fleet_request.go b/builtin/providers/aws/resource_aws_spot_fleet_request.go index 8f1e1969f6..c7cccd5120 100644 --- a/builtin/providers/aws/resource_aws_spot_fleet_request.go +++ b/builtin/providers/aws/resource_aws_spot_fleet_request.go @@ -2,9 +2,6 @@ package aws import ( "bytes" - "crypto/sha1" - "encoding/base64" - "encoding/hex" "fmt" "log" "strconv" @@ -168,6 +165,7 @@ func resourceAwsSpotFleetRequest() *schema.Resource { "ebs_optimized": &schema.Schema{ Type: schema.TypeBool, Optional: true, + Default: false, }, "iam_instance_profile": &schema.Schema{ Type: schema.TypeString, @@ -194,6 +192,7 @@ func resourceAwsSpotFleetRequest() *schema.Resource { "monitoring": &schema.Schema{ Type: schema.TypeBool, Optional: true, + Default: false, }, "placement_group": &schema.Schema{ Type: schema.TypeString, @@ -213,8 +212,7 @@ func resourceAwsSpotFleetRequest() *schema.Resource { StateFunc: func(v interface{}) string { switch v.(type) { case string: - hash := sha1.Sum([]byte(v.(string))) - return hex.EncodeToString(hash[:]) + return userDataHashSum(v.(string)) default: return "" } @@ -323,8 +321,7 @@ func buildSpotFleetLaunchSpecification(d map[string]interface{}, meta interface{ } if v, ok := d["user_data"]; ok { - opts.UserData = aws.String( - base64Encode([]byte(v.(string)))) + opts.UserData = aws.String(base64Encode([]byte(v.(string)))) } if v, ok := d["key_name"]; ok { @@ -339,21 +336,11 @@ func buildSpotFleetLaunchSpecification(d map[string]interface{}, meta interface{ opts.WeightedCapacity = aws.Float64(wc) } - var groups []*string - if v, ok := d["security_groups"]; ok { - sgs := v.(*schema.Set).List() - for _, v := range sgs { - str := v.(string) - groups = append(groups, aws.String(str)) - } - } - - var groupIds []*string + var securityGroupIds []*string if v, ok := d["vpc_security_group_ids"]; ok { if s := v.(*schema.Set); s.Len() > 0 { for _, v := range s.List() { - opts.SecurityGroups = append(opts.SecurityGroups, &ec2.GroupIdentifier{GroupId: aws.String(v.(string))}) - groupIds = append(groupIds, aws.String(v.(string))) + securityGroupIds = append(securityGroupIds, aws.String(v.(string))) } } } @@ -378,11 +365,15 @@ func buildSpotFleetLaunchSpecification(d map[string]interface{}, meta interface{ DeleteOnTermination: aws.Bool(true), DeviceIndex: aws.Int64(int64(0)), SubnetId: aws.String(subnetId.(string)), - Groups: groupIds, + Groups: securityGroupIds, } opts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ni} opts.SubnetId = aws.String("") + } else { + for _, id := range securityGroupIds { + opts.SecurityGroups = append(opts.SecurityGroups, &ec2.GroupIdentifier{GroupId: id}) + } } blockDevices, err := readSpotFleetBlockDeviceMappingsFromConfig(d, conn) @@ -730,24 +721,20 @@ func resourceAwsSpotFleetRequestRead(d *schema.ResourceData, meta interface{}) e return nil } -func launchSpecsToSet(ls []*ec2.SpotFleetLaunchSpecification, conn *ec2.EC2) *schema.Set { - specs := &schema.Set{F: hashLaunchSpecification} - for _, val := range ls { - dn, err := fetchRootDeviceName(aws.StringValue(val.ImageId), conn) +func launchSpecsToSet(launchSpecs []*ec2.SpotFleetLaunchSpecification, conn *ec2.EC2) *schema.Set { + specSet := &schema.Set{F: hashLaunchSpecification} + for _, spec := range launchSpecs { + rootDeviceName, err := fetchRootDeviceName(aws.StringValue(spec.ImageId), conn) if err != nil { log.Panic(err) - } else { - ls := launchSpecToMap(val, dn) - specs.Add(ls) } + + specSet.Add(launchSpecToMap(spec, rootDeviceName)) } - return specs + return specSet } -func launchSpecToMap( - l *ec2.SpotFleetLaunchSpecification, - rootDevName *string, -) map[string]interface{} { +func launchSpecToMap(l *ec2.SpotFleetLaunchSpecification, rootDevName *string) map[string]interface{} { m := make(map[string]interface{}) m["root_block_device"] = rootBlockDeviceToSet(l.BlockDeviceMappings, rootDevName) @@ -779,10 +766,7 @@ func launchSpecToMap( } if l.UserData != nil { - ud_dec, err := base64.StdEncoding.DecodeString(aws.StringValue(l.UserData)) - if err == nil { - m["user_data"] = string(ud_dec) - } + m["user_data"] = userDataHashSum(aws.StringValue(l.UserData)) } if l.KeyName != nil { @@ -797,11 +781,23 @@ func launchSpecToMap( m["subnet_id"] = aws.StringValue(l.SubnetId) } + securityGroupIds := &schema.Set{F: schema.HashString} + if len(l.NetworkInterfaces) > 0 { + // This resource auto-creates one network interface when associate_public_ip_address is true + for _, group := range l.NetworkInterfaces[0].Groups { + securityGroupIds.Add(aws.StringValue(group)) + } + } else { + for _, group := range l.SecurityGroups { + securityGroupIds.Add(aws.StringValue(group.GroupId)) + } + } + m["vpc_security_group_ids"] = securityGroupIds + if l.WeightedCapacity != nil { m["weighted_capacity"] = strconv.FormatFloat(*l.WeightedCapacity, 'f', 0, 64) } - // m["security_groups"] = securityGroupsToSet(l.SecutiryGroups) return m } @@ -1009,7 +1005,6 @@ func hashLaunchSpecification(v interface{}) int { } buf.WriteString(fmt.Sprintf("%s-", m["instance_type"].(string))) buf.WriteString(fmt.Sprintf("%s-", m["spot_price"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["user_data"].(string))) return hashcode.String(buf.String()) } diff --git a/builtin/providers/aws/resource_aws_spot_fleet_request_test.go b/builtin/providers/aws/resource_aws_spot_fleet_request_test.go index 5cb8c99155..0f90a57f65 100644 --- a/builtin/providers/aws/resource_aws_spot_fleet_request_test.go +++ b/builtin/providers/aws/resource_aws_spot_fleet_request_test.go @@ -100,9 +100,9 @@ func TestAccAWSSpotFleetRequest_lowestPriceAzInGivenList(t *testing.T) { resource.TestCheckResourceAttr( "aws_spot_fleet_request.foo", "launch_specification.#", "2"), resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.1590006269.availability_zone", "us-west-2a"), + "aws_spot_fleet_request.foo", "launch_specification.335709043.availability_zone", "us-west-2a"), resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.3809475891.availability_zone", "us-west-2b"), + "aws_spot_fleet_request.foo", "launch_specification.1671188867.availability_zone", "us-west-2b"), ), }, }, @@ -154,13 +154,13 @@ func TestAccAWSSpotFleetRequest_multipleInstanceTypesInSameAz(t *testing.T) { resource.TestCheckResourceAttr( "aws_spot_fleet_request.foo", "launch_specification.#", "2"), resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.1590006269.instance_type", "m1.small"), + "aws_spot_fleet_request.foo", "launch_specification.335709043.instance_type", "m1.small"), resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.1590006269.availability_zone", "us-west-2a"), + "aws_spot_fleet_request.foo", "launch_specification.335709043.availability_zone", "us-west-2a"), resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.3079734941.instance_type", "m3.large"), + "aws_spot_fleet_request.foo", "launch_specification.590403189.instance_type", "m3.large"), resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.3079734941.availability_zone", "us-west-2a"), + "aws_spot_fleet_request.foo", "launch_specification.590403189.availability_zone", "us-west-2a"), ), }, }, @@ -214,13 +214,13 @@ func TestAccAWSSpotFleetRequest_overriddingSpotPrice(t *testing.T) { resource.TestCheckResourceAttr( "aws_spot_fleet_request.foo", "launch_specification.#", "2"), resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.522395050.spot_price", "0.01"), + "aws_spot_fleet_request.foo", "launch_specification.4143232216.spot_price", "0.01"), resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.522395050.instance_type", "m3.large"), + "aws_spot_fleet_request.foo", "launch_specification.4143232216.instance_type", "m3.large"), resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.1590006269.spot_price", ""), //there will not be a value here since it's not overriding + "aws_spot_fleet_request.foo", "launch_specification.335709043.spot_price", ""), //there will not be a value here since it's not overriding resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.1590006269.instance_type", "m1.small"), + "aws_spot_fleet_request.foo", "launch_specification.335709043.instance_type", "m1.small"), ), }, }, @@ -289,13 +289,13 @@ func TestAccAWSSpotFleetRequest_withWeightedCapacity(t *testing.T) { resource.TestCheckResourceAttr( "aws_spot_fleet_request.foo", "launch_specification.#", "2"), resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.2325690000.weighted_capacity", "3"), + "aws_spot_fleet_request.foo", "launch_specification.4120185872.weighted_capacity", "3"), resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.2325690000.instance_type", "r3.large"), + "aws_spot_fleet_request.foo", "launch_specification.4120185872.instance_type", "r3.large"), resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.3079734941.weighted_capacity", "6"), + "aws_spot_fleet_request.foo", "launch_specification.590403189.weighted_capacity", "6"), resource.TestCheckResourceAttr( - "aws_spot_fleet_request.foo", "launch_specification.3079734941.instance_type", "m3.large"), + "aws_spot_fleet_request.foo", "launch_specification.590403189.instance_type", "m3.large"), ), }, }, diff --git a/builtin/providers/aws/resource_aws_volume_attachment.go b/builtin/providers/aws/resource_aws_volume_attachment.go index b469417d4b..9aed74a421 100644 --- a/builtin/providers/aws/resource_aws_volume_attachment.go +++ b/builtin/providers/aws/resource_aws_volume_attachment.go @@ -77,6 +77,25 @@ func resourceAwsVolumeAttachmentCreate(d *schema.ResourceData, meta interface{}) vols, err := conn.DescribeVolumes(request) if (err != nil) || (len(vols.Volumes) == 0) { + // This handles the situation where the instance is created by + // a spot request and whilst the request has been fulfilled the + // instance is not running yet + stateConf := &resource.StateChangeConf{ + Pending: []string{"pending"}, + Target: []string{"running"}, + Refresh: InstanceStateRefreshFunc(conn, iID), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for instance (%s) to become ready: %s", + iID, err) + } + // not attached opts := &ec2.AttachVolumeInput{ Device: aws.String(name), diff --git a/builtin/providers/aws/utils.go b/builtin/providers/aws/utils.go index 0510c21d6e..bfca044cfb 100644 --- a/builtin/providers/aws/utils.go +++ b/builtin/providers/aws/utils.go @@ -2,6 +2,8 @@ package aws import ( "encoding/base64" + "encoding/json" + "reflect" "regexp" ) @@ -24,3 +26,17 @@ func isBase64Encoded(data []byte) bool { func looksLikeJsonString(s interface{}) bool { return regexp.MustCompile(`^\s*{`).MatchString(s.(string)) } + +func jsonBytesEqual(b1, b2 []byte) bool { + var o1 interface{} + if err := json.Unmarshal(b1, &o1); err != nil { + return false + } + + var o2 interface{} + if err := json.Unmarshal(b2, &o2); err != nil { + return false + } + + return reflect.DeepEqual(o1, o2) +} diff --git a/builtin/providers/aws/utils_test.go b/builtin/providers/aws/utils_test.go index 9975c00984..8248f4384d 100644 --- a/builtin/providers/aws/utils_test.go +++ b/builtin/providers/aws/utils_test.go @@ -32,3 +32,41 @@ func TestLooksLikeJsonString(t *testing.T) { t.Errorf("Expected looksLikeJson to return false for %s", doesNotLookLikeJson) } } + +func TestJsonBytesEqualQuotedAndUnquoted(t *testing.T) { + unquoted := `{"test": "test"}` + quoted := "{\"test\": \"test\"}" + + if !jsonBytesEqual([]byte(unquoted), []byte(quoted)) { + t.Errorf("Expected jsonBytesEqual to return true for %s == %s", unquoted, quoted) + } + + unquotedDiff := `{"test": "test"}` + quotedDiff := "{\"test\": \"tested\"}" + + if jsonBytesEqual([]byte(unquotedDiff), []byte(quotedDiff)) { + t.Errorf("Expected jsonBytesEqual to return false for %s == %s", unquotedDiff, quotedDiff) + } +} + +func TestJsonBytesEqualWhitespaceAndNoWhitespace(t *testing.T) { + noWhitespace := `{"test":"test"}` + whitespace := ` +{ + "test": "test" +}` + + if !jsonBytesEqual([]byte(noWhitespace), []byte(whitespace)) { + t.Errorf("Expected jsonBytesEqual to return true for %s == %s", noWhitespace, whitespace) + } + + noWhitespaceDiff := `{"test":"test"}` + whitespaceDiff := ` +{ + "test": "tested" +}` + + if jsonBytesEqual([]byte(noWhitespaceDiff), []byte(whitespaceDiff)) { + t.Errorf("Expected jsonBytesEqual to return false for %s == %s", noWhitespaceDiff, whitespaceDiff) + } +} diff --git a/builtin/providers/datadog/resource_datadog_monitor.go b/builtin/providers/datadog/resource_datadog_monitor.go index 8237e56925..9b6b858069 100644 --- a/builtin/providers/datadog/resource_datadog_monitor.go +++ b/builtin/providers/datadog/resource_datadog_monitor.go @@ -324,7 +324,9 @@ func resourceDatadogMonitorUpdate(d *schema.ResourceData, meta interface{}) erro } o := datadog.Options{ - NotifyNoData: datadog.Bool(d.Get("notify_no_data").(bool)), + NotifyNoData: datadog.Bool(d.Get("notify_no_data").(bool)), + RequireFullWindow: datadog.Bool(d.Get("require_full_window").(bool)), + IncludeTags: datadog.Bool(d.Get("include_tags").(bool)), } if attr, ok := d.GetOk("thresholds"); ok { thresholds := attr.(map[string]interface{}) @@ -340,9 +342,6 @@ func resourceDatadogMonitorUpdate(d *schema.ResourceData, meta interface{}) erro } } - if attr, ok := d.GetOk("notify_no_data"); ok { - o.SetNotifyNoData(attr.(bool)) - } if attr, ok := d.GetOk("new_host_delay"); ok { o.SetNewHostDelay(attr.(int)) } @@ -369,12 +368,6 @@ func resourceDatadogMonitorUpdate(d *schema.ResourceData, meta interface{}) erro } o.Silenced = s } - if attr, ok := d.GetOk("include_tags"); ok { - o.SetIncludeTags(attr.(bool)) - } - if attr, ok := d.GetOk("require_full_window"); ok { - o.SetRequireFullWindow(attr.(bool)) - } if attr, ok := d.GetOk("locked"); ok { o.SetLocked(attr.(bool)) } diff --git a/builtin/providers/google/container_operation.go b/builtin/providers/google/container_operation.go new file mode 100644 index 0000000000..fb1b9cab83 --- /dev/null +++ b/builtin/providers/google/container_operation.go @@ -0,0 +1,59 @@ +package google + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "google.golang.org/api/container/v1" +) + +type ContainerOperationWaiter struct { + Service *container.Service + Op *container.Operation + Project string + Zone string +} + +func (w *ContainerOperationWaiter) Conf() *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{"PENDING", "RUNNING"}, + Target: []string{"DONE"}, + Refresh: w.RefreshFunc(), + } +} + +func (w *ContainerOperationWaiter) RefreshFunc() resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := w.Service.Projects.Zones.Operations.Get( + w.Project, w.Zone, w.Op.Name).Do() + + if err != nil { + return nil, "", err + } + + log.Printf("[DEBUG] Progress of operation %q: %q", w.Op.Name, resp.Status) + + return resp, resp.Status, err + } +} + +func containerOperationWait(config *Config, op *container.Operation, project, zone, activity string, timeoutMinutes, minTimeoutSeconds int) error { + w := &ContainerOperationWaiter{ + Service: config.clientContainer, + Op: op, + Project: project, + Zone: zone, + } + + state := w.Conf() + state.Timeout = time.Duration(timeoutMinutes) * time.Minute + state.MinTimeout = time.Duration(minTimeoutSeconds) * time.Second + _, err := state.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for %s: %s", activity, err) + } + + return nil +} diff --git a/builtin/providers/google/provider.go b/builtin/providers/google/provider.go index f4d7d5f7b7..7984a1f225 100644 --- a/builtin/providers/google/provider.go +++ b/builtin/providers/google/provider.go @@ -91,6 +91,7 @@ func Provider() terraform.ResourceProvider { "google_compute_vpn_gateway": resourceComputeVpnGateway(), "google_compute_vpn_tunnel": resourceComputeVpnTunnel(), "google_container_cluster": resourceContainerCluster(), + "google_container_node_pool": resourceContainerNodePool(), "google_dns_managed_zone": resourceDnsManagedZone(), "google_dns_record_set": resourceDnsRecordSet(), "google_sql_database": resourceSqlDatabase(), diff --git a/builtin/providers/google/resource_container_cluster.go b/builtin/providers/google/resource_container_cluster.go index fd9aa43a96..1337e0d920 100644 --- a/builtin/providers/google/resource_container_cluster.go +++ b/builtin/providers/google/resource_container_cluster.go @@ -5,9 +5,7 @@ import ( "log" "net" "regexp" - "time" - "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "google.golang.org/api/container/v1" "google.golang.org/api/googleapi" @@ -389,23 +387,11 @@ func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) er } // Wait until it's created - wait := resource.StateChangeConf{ - Pending: []string{"PENDING", "RUNNING"}, - Target: []string{"DONE"}, - Timeout: 30 * time.Minute, - MinTimeout: 3 * time.Second, - Refresh: func() (interface{}, string, error) { - resp, err := config.clientContainer.Projects.Zones.Operations.Get( - project, zoneName, op.Name).Do() - log.Printf("[DEBUG] Progress of creating GKE cluster %s: %s", - clusterName, resp.Status) - return resp, resp.Status, err - }, - } - - _, err = wait.WaitForState() - if err != nil { - return err + waitErr := containerOperationWait(config, op, project, zoneName, "creating GKE cluster", 30, 3) + if waitErr != nil { + // The resource didn't actually create + d.SetId("") + return waitErr } log.Printf("[INFO] GKE cluster %s has been created", clusterName) @@ -503,24 +489,9 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er } // Wait until it's updated - wait := resource.StateChangeConf{ - Pending: []string{"PENDING", "RUNNING"}, - Target: []string{"DONE"}, - Timeout: 10 * time.Minute, - MinTimeout: 2 * time.Second, - Refresh: func() (interface{}, string, error) { - log.Printf("[DEBUG] Checking if GKE cluster %s is updated", clusterName) - resp, err := config.clientContainer.Projects.Zones.Operations.Get( - project, zoneName, op.Name).Do() - log.Printf("[DEBUG] Progress of updating GKE cluster %s: %s", - clusterName, resp.Status) - return resp, resp.Status, err - }, - } - - _, err = wait.WaitForState() - if err != nil { - return err + waitErr := containerOperationWait(config, op, project, zoneName, "updating GKE cluster", 10, 2) + if waitErr != nil { + return waitErr } log.Printf("[INFO] GKE cluster %s has been updated to %s", d.Id(), @@ -548,24 +519,9 @@ func resourceContainerClusterDelete(d *schema.ResourceData, meta interface{}) er } // Wait until it's deleted - wait := resource.StateChangeConf{ - Pending: []string{"PENDING", "RUNNING"}, - Target: []string{"DONE"}, - Timeout: 10 * time.Minute, - MinTimeout: 3 * time.Second, - Refresh: func() (interface{}, string, error) { - log.Printf("[DEBUG] Checking if GKE cluster %s is deleted", clusterName) - resp, err := config.clientContainer.Projects.Zones.Operations.Get( - project, zoneName, op.Name).Do() - log.Printf("[DEBUG] Progress of deleting GKE cluster %s: %s", - clusterName, resp.Status) - return resp, resp.Status, err - }, - } - - _, err = wait.WaitForState() - if err != nil { - return err + waitErr := containerOperationWait(config, op, project, zoneName, "deleting GKE cluster", 10, 3) + if waitErr != nil { + return waitErr } log.Printf("[INFO] GKE cluster %s has been deleted", d.Id()) diff --git a/builtin/providers/google/resource_container_node_pool.go b/builtin/providers/google/resource_container_node_pool.go new file mode 100644 index 0000000000..24f2c97a78 --- /dev/null +++ b/builtin/providers/google/resource_container_node_pool.go @@ -0,0 +1,191 @@ +package google + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/container/v1" + "google.golang.org/api/googleapi" +) + +func resourceContainerNodePool() *schema.Resource { + return &schema.Resource{ + Create: resourceContainerNodePoolCreate, + Read: resourceContainerNodePoolRead, + Delete: resourceContainerNodePoolDelete, + Exists: resourceContainerNodePoolExists, + + Schema: map[string]*schema.Schema{ + "project": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"name_prefix"}, + ForceNew: true, + }, + + "name_prefix": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "zone": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "cluster": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "initial_node_count": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceContainerNodePoolCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + zone := d.Get("zone").(string) + cluster := d.Get("cluster").(string) + nodeCount := d.Get("initial_node_count").(int) + + var name string + if v, ok := d.GetOk("name"); ok { + name = v.(string) + } else if v, ok := d.GetOk("name_prefix"); ok { + name = resource.PrefixedUniqueId(v.(string)) + } else { + name = resource.UniqueId() + } + + nodePool := &container.NodePool{ + Name: name, + InitialNodeCount: int64(nodeCount), + } + + req := &container.CreateNodePoolRequest{ + NodePool: nodePool, + } + + op, err := config.clientContainer.Projects.Zones.Clusters.NodePools.Create(project, zone, cluster, req).Do() + + if err != nil { + return fmt.Errorf("Error creating NodePool: %s", err) + } + + waitErr := containerOperationWait(config, op, project, zone, "creating GKE NodePool", 10, 3) + if waitErr != nil { + // The resource didn't actually create + d.SetId("") + return waitErr + } + + log.Printf("[INFO] GKE NodePool %s has been created", name) + + d.SetId(name) + + return resourceContainerNodePoolRead(d, meta) +} + +func resourceContainerNodePoolRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + zone := d.Get("zone").(string) + name := d.Get("name").(string) + cluster := d.Get("cluster").(string) + + nodePool, err := config.clientContainer.Projects.Zones.Clusters.NodePools.Get( + project, zone, cluster, name).Do() + if err != nil { + return fmt.Errorf("Error reading NodePool: %s", err) + } + + d.Set("name", nodePool.Name) + d.Set("initial_node_count", nodePool.InitialNodeCount) + + return nil +} + +func resourceContainerNodePoolDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + zone := d.Get("zone").(string) + name := d.Get("name").(string) + cluster := d.Get("cluster").(string) + + op, err := config.clientContainer.Projects.Zones.Clusters.NodePools.Delete( + project, zone, cluster, name).Do() + if err != nil { + return fmt.Errorf("Error deleting NodePool: %s", err) + } + + // Wait until it's deleted + waitErr := containerOperationWait(config, op, project, zone, "deleting GKE NodePool", 10, 2) + if waitErr != nil { + return waitErr + } + + log.Printf("[INFO] GKE NodePool %s has been deleted", d.Id()) + + d.SetId("") + + return nil +} + +func resourceContainerNodePoolExists(d *schema.ResourceData, meta interface{}) (bool, error) { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return false, err + } + + zone := d.Get("zone").(string) + name := d.Get("name").(string) + cluster := d.Get("cluster").(string) + + _, err = config.clientContainer.Projects.Zones.Clusters.NodePools.Get( + project, zone, cluster, name).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { + log.Printf("[WARN] Removing Container NodePool %q because it's gone", name) + // The resource doesn't exist anymore + return false, err + } + // There was some other error in reading the resource + return true, err + } + return true, nil +} diff --git a/builtin/providers/google/resource_container_node_pool_test.go b/builtin/providers/google/resource_container_node_pool_test.go new file mode 100644 index 0000000000..a6b0da8092 --- /dev/null +++ b/builtin/providers/google/resource_container_node_pool_test.go @@ -0,0 +1,101 @@ +package google + +import ( + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccContainerNodePool_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckContainerNodePoolDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccContainerNodePool_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerNodePoolMatches("google_container_node_pool.np"), + ), + }, + }, + }) +} + +func testAccCheckContainerNodePoolDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_container_node_pool" { + continue + } + + attributes := rs.Primary.Attributes + _, err := config.clientContainer.Projects.Zones.Clusters.NodePools.Get( + config.Project, attributes["zone"], attributes["cluster"], attributes["name"]).Do() + if err == nil { + return fmt.Errorf("NodePool still exists") + } + } + + return nil +} + +func testAccCheckContainerNodePoolMatches(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + attributes := rs.Primary.Attributes + found, err := config.clientContainer.Projects.Zones.Clusters.NodePools.Get( + config.Project, attributes["zone"], attributes["cluster"], attributes["name"]).Do() + if err != nil { + return err + } + + if found.Name != attributes["name"] { + return fmt.Errorf("NodePool not found") + } + + inc, err := strconv.Atoi(attributes["initial_node_count"]) + if err != nil { + return err + } + if found.InitialNodeCount != int64(inc) { + return fmt.Errorf("Mismatched initialNodeCount. TF State: %s. GCP State: %d", + attributes["initial_node_count"], found.InitialNodeCount) + } + return nil + } +} + +var testAccContainerNodePool_basic = fmt.Sprintf(` +resource "google_container_cluster" "cluster" { + name = "tf-cluster-nodepool-test-%s" + zone = "us-central1-a" + initial_node_count = 3 + + master_auth { + username = "mr.yoda" + password = "adoy.rm" + } +} + +resource "google_container_node_pool" "np" { + name = "tf-nodepool-test-%s" + zone = "us-central1-a" + cluster = "${google_container_cluster.cluster.name}" + initial_node_count = 2 +}`, acctest.RandString(10), acctest.RandString(10)) diff --git a/command/internal_plugin_list.go b/command/internal_plugin_list.go index 82e2f1df58..ce36d47778 100644 --- a/command/internal_plugin_list.go +++ b/command/internal_plugin_list.go @@ -23,12 +23,13 @@ import ( datadogprovider "github.com/hashicorp/terraform/builtin/providers/datadog" digitaloceanprovider "github.com/hashicorp/terraform/builtin/providers/digitalocean" dmeprovider "github.com/hashicorp/terraform/builtin/providers/dme" - dnsprovider "github.com/hashicorp/terraform/builtin/providers/dns" dnsimpleprovider "github.com/hashicorp/terraform/builtin/providers/dnsimple" + dnsprovider "github.com/hashicorp/terraform/builtin/providers/dns" dockerprovider "github.com/hashicorp/terraform/builtin/providers/docker" dynprovider "github.com/hashicorp/terraform/builtin/providers/dyn" externalprovider "github.com/hashicorp/terraform/builtin/providers/external" fastlyprovider "github.com/hashicorp/terraform/builtin/providers/fastly" + fileprovisioner "github.com/hashicorp/terraform/builtin/provisioners/file" githubprovider "github.com/hashicorp/terraform/builtin/providers/github" googleprovider "github.com/hashicorp/terraform/builtin/providers/google" grafanaprovider "github.com/hashicorp/terraform/builtin/providers/grafana" @@ -37,6 +38,7 @@ import ( ignitionprovider "github.com/hashicorp/terraform/builtin/providers/ignition" influxdbprovider "github.com/hashicorp/terraform/builtin/providers/influxdb" libratoprovider "github.com/hashicorp/terraform/builtin/providers/librato" + localexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/local-exec" logentriesprovider "github.com/hashicorp/terraform/builtin/providers/logentries" mailgunprovider "github.com/hashicorp/terraform/builtin/providers/mailgun" mysqlprovider "github.com/hashicorp/terraform/builtin/providers/mysql" @@ -54,6 +56,7 @@ import ( rabbitmqprovider "github.com/hashicorp/terraform/builtin/providers/rabbitmq" rancherprovider "github.com/hashicorp/terraform/builtin/providers/rancher" randomprovider "github.com/hashicorp/terraform/builtin/providers/random" + remoteexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/remote-exec" rundeckprovider "github.com/hashicorp/terraform/builtin/providers/rundeck" scalewayprovider "github.com/hashicorp/terraform/builtin/providers/scaleway" softlayerprovider "github.com/hashicorp/terraform/builtin/providers/softlayer" @@ -68,9 +71,6 @@ import ( vaultprovider "github.com/hashicorp/terraform/builtin/providers/vault" vcdprovider "github.com/hashicorp/terraform/builtin/providers/vcd" vsphereprovider "github.com/hashicorp/terraform/builtin/providers/vsphere" - fileprovisioner "github.com/hashicorp/terraform/builtin/provisioners/file" - localexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/local-exec" - remoteexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/remote-exec" "github.com/hashicorp/terraform/plugin" "github.com/hashicorp/terraform/terraform" @@ -80,74 +80,76 @@ import ( ) var InternalProviders = map[string]plugin.ProviderFunc{ - "alicloud": alicloudprovider.Provider, - "archive": archiveprovider.Provider, - "arukas": arukasprovider.Provider, - "atlas": atlasprovider.Provider, - "aws": awsprovider.Provider, - "azure": azureprovider.Provider, - "azurerm": azurermprovider.Provider, - "bitbucket": bitbucketprovider.Provider, - "chef": chefprovider.Provider, - "clc": clcprovider.Provider, + "alicloud": alicloudprovider.Provider, + "archive": archiveprovider.Provider, + "arukas": arukasprovider.Provider, + "atlas": atlasprovider.Provider, + "aws": awsprovider.Provider, + "azure": azureprovider.Provider, + "azurerm": azurermprovider.Provider, + "bitbucket": bitbucketprovider.Provider, + "chef": chefprovider.Provider, + "clc": clcprovider.Provider, "cloudflare": cloudflareprovider.Provider, "cloudstack": cloudstackprovider.Provider, - "cobbler": cobblerprovider.Provider, - "consul": consulprovider.Provider, - "datadog": datadogprovider.Provider, - "digitalocean": digitaloceanprovider.Provider, - "dme": dmeprovider.Provider, - "dns": dnsprovider.Provider, - "dnsimple": dnsimpleprovider.Provider, - "docker": dockerprovider.Provider, - "dyn": dynprovider.Provider, - "external": externalprovider.Provider, - "fastly": fastlyprovider.Provider, - "github": githubprovider.Provider, - "google": googleprovider.Provider, - "grafana": grafanaprovider.Provider, - "heroku": herokuprovider.Provider, - "icinga2": icinga2provider.Provider, - "ignition": ignitionprovider.Provider, - "influxdb": influxdbprovider.Provider, - "librato": libratoprovider.Provider, + "cobbler": cobblerprovider.Provider, + "consul": consulprovider.Provider, + "datadog": datadogprovider.Provider, + "digitalocean": digitaloceanprovider.Provider, + "dme": dmeprovider.Provider, + "dns": dnsprovider.Provider, + "dnsimple": dnsimpleprovider.Provider, + "docker": dockerprovider.Provider, + "dyn": dynprovider.Provider, + "external": externalprovider.Provider, + "fastly": fastlyprovider.Provider, + "github": githubprovider.Provider, + "google": googleprovider.Provider, + "grafana": grafanaprovider.Provider, + "heroku": herokuprovider.Provider, + "icinga2": icinga2provider.Provider, + "ignition": ignitionprovider.Provider, + "influxdb": influxdbprovider.Provider, + "librato": libratoprovider.Provider, "logentries": logentriesprovider.Provider, - "mailgun": mailgunprovider.Provider, - "mysql": mysqlprovider.Provider, - "newrelic": newrelicprovider.Provider, - "nomad": nomadprovider.Provider, - "ns1": ns1provider.Provider, - "null": nullprovider.Provider, - "openstack": openstackprovider.Provider, - "opsgenie": opsgenieprovider.Provider, - "packet": packetprovider.Provider, - "pagerduty": pagerdutyprovider.Provider, + "mailgun": mailgunprovider.Provider, + "mysql": mysqlprovider.Provider, + "newrelic": newrelicprovider.Provider, + "nomad": nomadprovider.Provider, + "ns1": ns1provider.Provider, + "null": nullprovider.Provider, + "openstack": openstackprovider.Provider, + "opsgenie": opsgenieprovider.Provider, + "packet": packetprovider.Provider, + "pagerduty": pagerdutyprovider.Provider, "postgresql": postgresqlprovider.Provider, - "powerdns": powerdnsprovider.Provider, - "profitbricks": profitbricksprovider.Provider, - "rabbitmq": rabbitmqprovider.Provider, - "rancher": rancherprovider.Provider, - "random": randomprovider.Provider, - "rundeck": rundeckprovider.Provider, - "scaleway": scalewayprovider.Provider, - "softlayer": softlayerprovider.Provider, - "spotinst": spotinstprovider.Provider, + "powerdns": powerdnsprovider.Provider, + "profitbricks": profitbricksprovider.Provider, + "rabbitmq": rabbitmqprovider.Provider, + "rancher": rancherprovider.Provider, + "random": randomprovider.Provider, + "rundeck": rundeckprovider.Provider, + "scaleway": scalewayprovider.Provider, + "softlayer": softlayerprovider.Provider, + "spotinst": spotinstprovider.Provider, "statuscake": statuscakeprovider.Provider, - "template": templateprovider.Provider, - "terraform": terraformprovider.Provider, - "test": testprovider.Provider, - "tls": tlsprovider.Provider, - "triton": tritonprovider.Provider, - "ultradns": ultradnsprovider.Provider, - "vault": vaultprovider.Provider, - "vcd": vcdprovider.Provider, - "vsphere": vsphereprovider.Provider, + "template": templateprovider.Provider, + "terraform": terraformprovider.Provider, + "test": testprovider.Provider, + "tls": tlsprovider.Provider, + "triton": tritonprovider.Provider, + "ultradns": ultradnsprovider.Provider, + "vault": vaultprovider.Provider, + "vcd": vcdprovider.Provider, + "vsphere": vsphereprovider.Provider, + } var InternalProvisioners = map[string]plugin.ProvisionerFunc{ - "file": fileprovisioner.Provisioner, - "local-exec": localexecprovisioner.Provisioner, - "remote-exec": remoteexecprovisioner.Provisioner, + "file": fileprovisioner.Provisioner, + "local-exec": localexecprovisioner.Provisioner, + "remote-exec": remoteexecprovisioner.Provisioner, + } func init() { @@ -155,3 +157,4 @@ func init() { // built-in provisioners. InternalProvisioners["chef"] = func() terraform.ResourceProvisioner { return new(chefprovisioner.ResourceProvisioner) } } + diff --git a/website/source/docs/providers/aws/r/iam_role.html.markdown b/website/source/docs/providers/aws/r/iam_role.html.markdown index f83613fa1e..4064a73517 100644 --- a/website/source/docs/providers/aws/r/iam_role.html.markdown +++ b/website/source/docs/providers/aws/r/iam_role.html.markdown @@ -54,6 +54,7 @@ The following attributes are exported: * `arn` - The Amazon Resource Name (ARN) specifying the role. * `create_date` - The creation date of the IAM role. * `unique_id` - The stable and unique string identifying the role. +* `name` - The name of the role. ## Example of Using Data Source for Assume Role Policy diff --git a/website/source/docs/providers/aws/r/instance.html.markdown b/website/source/docs/providers/aws/r/instance.html.markdown index fe3491d626..a71080a871 100644 --- a/website/source/docs/providers/aws/r/instance.html.markdown +++ b/website/source/docs/providers/aws/r/instance.html.markdown @@ -102,7 +102,8 @@ The `root_block_device` mapping supports the following: * `volume_size` - (Optional) The size of the volume in gigabytes. * `iops` - (Optional) The amount of provisioned [IOPS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html). - This must be set with a `volume_type` of `"io1"`. + This is only valid for `volume_type` of `"io1"`, and must be specified if + using that type * `delete_on_termination` - (Optional) Whether the volume should be destroyed on instance termination (Default: `true`). diff --git a/website/source/docs/providers/consul/r/catalog_entry.html.markdown b/website/source/docs/providers/consul/r/catalog_entry.html.markdown index 2a978875be..62d4fbf02f 100644 --- a/website/source/docs/providers/consul/r/catalog_entry.html.markdown +++ b/website/source/docs/providers/consul/r/catalog_entry.html.markdown @@ -3,13 +3,13 @@ layout: "consul" page_title: "Consul: consul_catalog_entry" sidebar_current: "docs-consul-resource-catalog-entry" description: |- - Provides access to Catalog data in Consul. This can be used to define a node or a service. Currently, defining health checks is not supported. + Registers a node or service with the Consul Catalog. Currently, defining health checks is not supported. --- # consul\_catalog\_entry -Provides access to Catalog data in Consul. This can be used to define a -node or a service. Currently, defining health checks is not supported. +Registers a node or service with the [Consul Catalog](https://www.consul.io/docs/agent/http/catalog.html#catalog_register). +Currently, defining health checks is not supported. ## Example Usage @@ -41,6 +41,11 @@ The following arguments are supported: * `service` - (Optional) A service to optionally associated with the node. Supported values are documented below. +* `datacenter` - (Optional) The datacenter to use. This overrides the + datacenter in the provider setup and the agent's default datacenter. + +* `token` - (Optional) ACL token. + The `service` block supports the following: * `address` - (Optional) The address of the service. Defaults to the diff --git a/website/source/docs/providers/vault/index.html.markdown b/website/source/docs/providers/vault/index.html.markdown index cdf6549490..ba67c94b02 100644 --- a/website/source/docs/providers/vault/index.html.markdown +++ b/website/source/docs/providers/vault/index.html.markdown @@ -129,13 +129,23 @@ The `client_auth` configuration block accepts the following arguments: ``` provider "vault" { # It is strongly recommended to configure this provider through the - # environment variables described below, so that each user can have + # environment variables described above, so that each user can have # separate credentials set in the environment. - address = "https://vault.example.net:8200" + # + # This will default to using $VAULT_ADDR + # But can be set explicitly + # address = "https://vault.example.net:8200" } -data "vault_generic_secret" "example" { +resource "vault_generic_secret" "example" { path = "secret/foo" + + data_json = <