diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f07534fe..95fcd359d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ IMPROVEMENTS: * provider/aws: Add a computed ARN for S3 Buckets [GH-3685] * provider/aws: Add S3 support for Lambda Function resource [GH-3794] * provider/aws: Add `name_prefix` option to launch configurations [GH-3802] + * provider/aws: add support for group name and path changes with IAM group update function [GH-3237] * provider/aws: Provide `source_security_group_id` for ELBs inside a VPC [GH-3780] * provider/aws: Add snapshot window and retention limits for ElastiCache (Redis) [GH-3707] * provider/aws: Add username updates for `aws_iam_user` [GH-3227] @@ -55,6 +56,7 @@ IMPROVEMENTS: * provider/vsphere: Do not add network interfaces by default [GH-3652] * provider/openstack: Configure Fixed IPs through ports [GH-3772] * provider/openstack: Specify a port ID on a Router Interface [GH-3903] + * provider/openstack: Made LBaaS Virtual IP computed [GH-3927] BUG FIXES: @@ -70,6 +72,7 @@ BUG FIXES: * provider/aws: ignore association not exist on route table destroy [GH-3615] * provider/aws: Fix policy encoding issue with SNS Topics [GH-3700] * provider/aws: Correctly export ARN in `aws_iam_saml_provider` [GH-3827] + * provider/aws: Fix crash in Route53 Record if Zone not found [GH-3945] * provider/aws: Tolerate ElastiCache clusters being deleted outside Terraform [GH-3767] * provider/aws: Downcase Route 53 record names in statefile to match API output [GH-3574] * provider/aws: Fix issue that could occur if no ECS Cluster was found for a give name [GH-3829] @@ -79,6 +82,7 @@ BUG FIXES: * provider/aws: Expand ~ to homedir in `aws_s3_bucket_object.source` [GH-3910] * provider/aws: Fix issue with updating the `aws_ecs_task_definition` where `aws_ecs_service` didn't wait for a new computed ARN [GH-3924] * provider/aws: Prevent crashing when deleting `aws_ecs_service` that is already gone [GH-3914] + * provider/aws: Allow spaces in `aws_db_subnet_group.name` (undocumented in the API) [GH-3955] * provider/azure: various bugfixes [GH-3695] * provider/digitalocean: fix issue preventing SSH fingerprints from working [GH-3633] * provider/digitalocean: Fixing the DigitalOcean Droplet 404 potential on refresh of state [GH-3768] @@ -91,6 +95,7 @@ BUG FIXES: * provider/openstack: Better handling of network resource state changes [GH-3712] * provider/openstack: Fix crashing when no security group is specified [GH-3801] * provider/packet: Fix issue that could cause errors when provisioning many devices at once [GH-3847] + * provider/packet: Fix connection information for devices, allowing provisioners to run [GH-3948] * provider/openstack: Fix issue preventing security group rules from being removed [GH-3796] * provider/template: template_file: source contents instead of path [GH-3909] diff --git a/Makefile b/Makefile index e531063078..fea0478cdc 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,12 @@ dev: generate quickdev: generate @TF_QUICKDEV=1 TF_DEV=1 sh -c "'$(CURDIR)/scripts/build.sh'" +# Shorthand for quickly building the core of Terraform. Note that some +# changes will require a rebuild of everything, in which case the dev +# target should be used. +core-dev: generate + go install github.com/hashicorp/terraform + # Shorthand for building and installing just one plugin for local testing. # Run as (for example): make plugin-dev PLUGIN=provider-aws plugin-dev: generate diff --git a/builtin/providers/aws/resource_aws_db_subnet_group.go b/builtin/providers/aws/resource_aws_db_subnet_group.go index e6b17ea1fe..cbfed609a9 100644 --- a/builtin/providers/aws/resource_aws_db_subnet_group.go +++ b/builtin/providers/aws/resource_aws_db_subnet_group.go @@ -29,9 +29,9 @@ func resourceAwsDbSubnetGroup() *schema.Resource { Required: true, ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { value := v.(string) - if !regexp.MustCompile(`^[.0-9A-Za-z-_]+$`).MatchString(value) { + if !regexp.MustCompile(`^[ .0-9A-Za-z-_]+$`).MatchString(value) { errors = append(errors, fmt.Errorf( - "only alphanumeric characters, hyphens, underscores, and periods allowed in %q", k)) + "only alphanumeric characters, hyphens, underscores, periods, and spaces allowed in %q", k)) } if len(value) > 255 { errors = append(errors, fmt.Errorf( diff --git a/builtin/providers/aws/resource_aws_db_subnet_group_test.go b/builtin/providers/aws/resource_aws_db_subnet_group_test.go index e189b1e217..d943294a97 100644 --- a/builtin/providers/aws/resource_aws_db_subnet_group_test.go +++ b/builtin/providers/aws/resource_aws_db_subnet_group_test.go @@ -51,12 +51,14 @@ func TestAccAWSDBSubnetGroup_withUndocumentedCharacters(t *testing.T) { CheckDestroy: testAccCheckDBSubnetGroupDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccDBSubnetGroupConfig_withUnderscoresAndPeriods, + Config: testAccDBSubnetGroupConfig_withUnderscoresAndPeriodsAndSpaces, Check: resource.ComposeTestCheckFunc( testAccCheckDBSubnetGroupExists( "aws_db_subnet_group.underscores", &v), testAccCheckDBSubnetGroupExists( "aws_db_subnet_group.periods", &v), + testAccCheckDBSubnetGroupExists( + "aws_db_subnet_group.spaces", &v), testCheck, ), }, @@ -156,7 +158,7 @@ resource "aws_db_subnet_group" "foo" { } ` -const testAccDBSubnetGroupConfig_withUnderscoresAndPeriods = ` +const testAccDBSubnetGroupConfig_withUnderscoresAndPeriodsAndSpaces = ` resource "aws_vpc" "main" { cidr_block = "192.168.0.0/16" } @@ -184,4 +186,10 @@ resource "aws_db_subnet_group" "periods" { description = "Our main group of subnets" subnet_ids = ["${aws_subnet.frontend.id}", "${aws_subnet.backend.id}"] } + +resource "aws_db_subnet_group" "spaces" { + name = "with spaces" + description = "Our main group of subnets" + subnet_ids = ["${aws_subnet.frontend.id}", "${aws_subnet.backend.id}"] +} ` diff --git a/builtin/providers/aws/resource_aws_iam_group.go b/builtin/providers/aws/resource_aws_iam_group.go index 45defaaaf9..f2415f585b 100644 --- a/builtin/providers/aws/resource_aws_iam_group.go +++ b/builtin/providers/aws/resource_aws_iam_group.go @@ -14,8 +14,7 @@ func resourceAwsIamGroup() *schema.Resource { return &schema.Resource{ Create: resourceAwsIamGroupCreate, Read: resourceAwsIamGroupRead, - // TODO - //Update: resourceAwsIamGroupUpdate, + Update: resourceAwsIamGroupUpdate, Delete: resourceAwsIamGroupDelete, Schema: map[string]*schema.Schema{ @@ -30,13 +29,11 @@ func resourceAwsIamGroup() *schema.Resource { "name": &schema.Schema{ Type: schema.TypeString, Required: true, - ForceNew: true, }, "path": &schema.Schema{ Type: schema.TypeString, Optional: true, Default: "/", - ForceNew: true, }, }, } @@ -45,9 +42,10 @@ func resourceAwsIamGroup() *schema.Resource { func resourceAwsIamGroupCreate(d *schema.ResourceData, meta interface{}) error { iamconn := meta.(*AWSClient).iamconn name := d.Get("name").(string) + path := d.Get("path").(string) request := &iam.CreateGroupInput{ - Path: aws.String(d.Get("path").(string)), + Path: aws.String(path), GroupName: aws.String(name), } @@ -60,9 +58,10 @@ func resourceAwsIamGroupCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsIamGroupRead(d *schema.ResourceData, meta interface{}) error { iamconn := meta.(*AWSClient).iamconn + name := d.Get("name").(string) request := &iam.GetGroupInput{ - GroupName: aws.String(d.Id()), + GroupName: aws.String(name), } getResp, err := iamconn.GetGroup(request) @@ -93,6 +92,26 @@ func resourceAwsIamGroupReadResult(d *schema.ResourceData, group *iam.Group) err return nil } +func resourceAwsIamGroupUpdate(d *schema.ResourceData, meta interface{}) error { + if d.HasChange("name") || d.HasChange("path") { + iamconn := meta.(*AWSClient).iamconn + on, nn := d.GetChange("name") + _, np := d.GetChange("path") + + request := &iam.UpdateGroupInput{ + GroupName: aws.String(on.(string)), + NewGroupName: aws.String(nn.(string)), + NewPath: aws.String(np.(string)), + } + _, err := iamconn.UpdateGroup(request) + if err != nil { + return fmt.Errorf("Error updating IAM Group %s: %s", d.Id(), err) + } + return resourceAwsIamGroupRead(d, meta) + } + return nil +} + func resourceAwsIamGroupDelete(d *schema.ResourceData, meta interface{}) error { iamconn := meta.(*AWSClient).iamconn diff --git a/builtin/providers/aws/resource_aws_iam_group_test.go b/builtin/providers/aws/resource_aws_iam_group_test.go index 67a72733af..977889bd65 100644 --- a/builtin/providers/aws/resource_aws_iam_group_test.go +++ b/builtin/providers/aws/resource_aws_iam_group_test.go @@ -23,7 +23,14 @@ func TestAccAWSIAMGroup_basic(t *testing.T) { Config: testAccAWSGroupConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSGroupExists("aws_iam_group.group", &conf), - testAccCheckAWSGroupAttributes(&conf), + testAccCheckAWSGroupAttributes(&conf, "test-group", "/"), + ), + }, + resource.TestStep{ + Config: testAccAWSGroupConfig2, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSGroupExists("aws_iam_group.group", &conf), + testAccCheckAWSGroupAttributes(&conf, "test-group2", "/funnypath/"), ), }, }, @@ -85,14 +92,14 @@ func testAccCheckAWSGroupExists(n string, res *iam.GetGroupOutput) resource.Test } } -func testAccCheckAWSGroupAttributes(group *iam.GetGroupOutput) resource.TestCheckFunc { +func testAccCheckAWSGroupAttributes(group *iam.GetGroupOutput, name string, path string) resource.TestCheckFunc { return func(s *terraform.State) error { - if *group.Group.GroupName != "test-group" { - return fmt.Errorf("Bad name: %s", *group.Group.GroupName) + if *group.Group.GroupName != name { + return fmt.Errorf("Bad name: %s when %s was expected", *group.Group.GroupName, name) } - if *group.Group.Path != "/" { - return fmt.Errorf("Bad path: %s", *group.Group.Path) + if *group.Group.Path != path { + return fmt.Errorf("Bad path: %s when %s was expected", *group.Group.Path, path) } return nil @@ -105,3 +112,9 @@ resource "aws_iam_group" "group" { path = "/" } ` +const testAccAWSGroupConfig2 = ` +resource "aws_iam_group" "group" { + name = "test-group2" + path = "/funnypath/" +} +` diff --git a/builtin/providers/aws/resource_aws_route53_record.go b/builtin/providers/aws/resource_aws_route53_record.go index a5b9ef4685..cf99b9b9b3 100644 --- a/builtin/providers/aws/resource_aws_route53_record.go +++ b/builtin/providers/aws/resource_aws_route53_record.go @@ -49,6 +49,13 @@ func resourceAwsRoute53Record() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { + value := v.(string) + if value == "" { + es = append(es, fmt.Errorf("Cannot have empty zone_id")) + } + return + }, }, "ttl": &schema.Schema{ @@ -136,6 +143,9 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er if err != nil { return err } + if zoneRecord.HostedZone == nil { + return fmt.Errorf("[WARN] No Route53 Zone found for id (%s)", zone) + } // Get the record rec, err := resourceAwsRoute53RecordBuildSet(d, *zoneRecord.HostedZone.Name) diff --git a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go index 934215d2c1..dd165df772 100644 --- a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go @@ -3,7 +3,6 @@ package openstack import ( "fmt" "log" - "strconv" "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" @@ -53,16 +52,19 @@ func resourceLBVipV1() *schema.Resource { "tenant_id": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, "address": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, "description": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: false, }, "persistence": &schema.Schema{ @@ -73,6 +75,7 @@ func resourceLBVipV1() *schema.Resource { "conn_limit": &schema.Schema{ Type: schema.TypeInt, Optional: true, + Computed: true, ForceNew: false, }, "port_id": &schema.Schema{ @@ -86,8 +89,9 @@ func resourceLBVipV1() *schema.Resource { ForceNew: false, }, "admin_state_up": &schema.Schema{ - Type: schema.TypeString, + Type: schema.TypeBool, Optional: true, + Computed: true, ForceNew: false, }, }, @@ -114,14 +118,8 @@ func resourceLBVipV1Create(d *schema.ResourceData, meta interface{}) error { ConnLimit: gophercloud.MaybeInt(d.Get("conn_limit").(int)), } - asuRaw := d.Get("admin_state_up").(string) - if asuRaw != "" { - asu, err := strconv.ParseBool(asuRaw) - if err != nil { - return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'") - } - createOpts.AdminStateUp = &asu - } + asu := d.Get("admin_state_up").(bool) + createOpts.AdminStateUp = &asu log.Printf("[DEBUG] Create Options: %#v", createOpts) p, err := vips.Create(networkingClient, createOpts).Extract() @@ -160,40 +158,11 @@ func resourceLBVipV1Read(d *schema.ResourceData, meta interface{}) error { d.Set("port", p.ProtocolPort) d.Set("pool_id", p.PoolID) d.Set("port_id", p.PortID) - - if t, exists := d.GetOk("tenant_id"); exists && t != "" { - d.Set("tenant_id", p.TenantID) - } else { - d.Set("tenant_id", "") - } - - if t, exists := d.GetOk("address"); exists && t != "" { - d.Set("address", p.Address) - } else { - d.Set("address", "") - } - - if t, exists := d.GetOk("description"); exists && t != "" { - d.Set("description", p.Description) - } else { - d.Set("description", "") - } - - if t, exists := d.GetOk("persistence"); exists && t != "" { - d.Set("persistence", p.Description) - } - - if t, exists := d.GetOk("conn_limit"); exists && t != "" { - d.Set("conn_limit", p.ConnLimit) - } else { - d.Set("conn_limit", "") - } - - if t, exists := d.GetOk("admin_state_up"); exists && t != "" { - d.Set("admin_state_up", strconv.FormatBool(p.AdminStateUp)) - } else { - d.Set("admin_state_up", "") - } + d.Set("tenant_id", p.TenantID) + d.Set("address", p.Address) + d.Set("description", p.Description) + d.Set("conn_limit", p.ConnLimit) + d.Set("admin_state_up", p.AdminStateUp) return nil } @@ -255,14 +224,8 @@ func resourceLBVipV1Update(d *schema.ResourceData, meta interface{}) error { } } if d.HasChange("admin_state_up") { - asuRaw := d.Get("admin_state_up").(string) - if asuRaw != "" { - asu, err := strconv.ParseBool(asuRaw) - if err != nil { - return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'") - } - updateOpts.AdminStateUp = &asu - } + asu := d.Get("admin_state_up").(bool) + updateOpts.AdminStateUp = &asu } log.Printf("[DEBUG] Updating OpenStack LB VIP %s with options: %+v", d.Id(), updateOpts) diff --git a/builtin/providers/openstack/resource_openstack_lb_vip_v1_test.go b/builtin/providers/openstack/resource_openstack_lb_vip_v1_test.go index f30cd9d56d..0ef369a4e4 100644 --- a/builtin/providers/openstack/resource_openstack_lb_vip_v1_test.go +++ b/builtin/providers/openstack/resource_openstack_lb_vip_v1_test.go @@ -116,6 +116,9 @@ var testAccLBV1VIP_basic = fmt.Sprintf(` protocol = "HTTP" port = 80 pool_id = "${openstack_lb_pool_v1.pool_1.id}" + persistence { + type = "SOURCE_IP" + } }`, OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME) @@ -148,5 +151,8 @@ var testAccLBV1VIP_update = fmt.Sprintf(` protocol = "HTTP" port = 80 pool_id = "${openstack_lb_pool_v1.pool_1.id}" + persistence { + type = "SOURCE_IP" + } }`, OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME) diff --git a/builtin/providers/packet/resource_packet_device.go b/builtin/providers/packet/resource_packet_device.go index 6bee26cf96..c7cd777a2f 100644 --- a/builtin/providers/packet/resource_packet_device.go +++ b/builtin/providers/packet/resource_packet_device.go @@ -184,7 +184,7 @@ func resourcePacketDeviceRead(d *schema.ResourceData, meta interface{}) error { d.Set("billing_cycle", device.BillingCycle) d.Set("locked", device.Locked) d.Set("created", device.Created) - d.Set("udpated", device.Updated) + d.Set("updated", device.Updated) tags := make([]string, 0) for _, tag := range device.Tags { @@ -192,6 +192,8 @@ func resourcePacketDeviceRead(d *schema.ResourceData, meta interface{}) error { } d.Set("tags", tags) + provisionerAddress := "" + networks := make([]map[string]interface{}, 0, 1) for _, ip := range device.Network { network := make(map[string]interface{}) @@ -201,9 +203,21 @@ func resourcePacketDeviceRead(d *schema.ResourceData, meta interface{}) error { network["cidr"] = ip.Cidr network["public"] = ip.Public networks = append(networks, network) + if ip.Family == 4 && ip.Public == true { + provisionerAddress = ip.Address + } } d.Set("network", networks) + log.Printf("[DEBUG] Provisioner Address set to %v", provisionerAddress) + + if provisionerAddress != "" { + d.SetConnInfo(map[string]string{ + "type": "ssh", + "host": provisionerAddress, + }) + } + return nil } diff --git a/website/source/docs/providers/cloudstack/r/disk.html.markdown b/website/source/docs/providers/cloudstack/r/disk.html.markdown index 2089277380..b6e56199c9 100644 --- a/website/source/docs/providers/cloudstack/r/disk.html.markdown +++ b/website/source/docs/providers/cloudstack/r/disk.html.markdown @@ -19,7 +19,7 @@ resource "cloudstack_disk" "default" { attach = "true" disk_offering = "custom" size = 50 - virtual-machine = "server-1" + virtual_machine = "server-1" zone = "zone-1" } ``` diff --git a/website/source/docs/providers/template/r/file.html.md b/website/source/docs/providers/template/r/file.html.md index 7c9e2c59ec..7d102457be 100644 --- a/website/source/docs/providers/template/r/file.html.md +++ b/website/source/docs/providers/template/r/file.html.md @@ -14,7 +14,7 @@ Renders a template from a file. ``` resource "template_file" "init" { - template = "${file(${path.module}/init.tpl)}" + template = "${file("${path.module}/init.tpl")}" vars { consul_address = "${aws_instance.consul.private_ip}" diff --git a/website/source/docs/providers/vsphere/index.html.markdown b/website/source/docs/providers/vsphere/index.html.markdown index 9a6880a413..48fb9dbefe 100644 --- a/website/source/docs/providers/vsphere/index.html.markdown +++ b/website/source/docs/providers/vsphere/index.html.markdown @@ -17,7 +17,8 @@ The provider needs to be configured with the proper credentials before it can be Use the navigation to the left to read about the available resources. ~> **NOTE:** The VMware vSphere Provider currently represents _initial support_ -and therefore may undergo significant changes as the community improves it. +and therefore may undergo significant changes as the community improves it. This +provider at this time only supports IPv4 addresses on virtual machines. ## Example Usage @@ -72,7 +73,7 @@ In addition, the following environment variables are used in tests, and must be * VSPHERE\_TEMPLATE The following environment variables depend on your vSphere environment: - + * VSPHERE\_DATACENTER * VSPHERE\_CLUSTER * VSPHERE\_RESOURCE\_POOL diff --git a/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown b/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown index d008357ecc..19421aaa9c 100644 --- a/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown +++ b/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown @@ -55,7 +55,7 @@ The following arguments are supported: Network interfaces support the following attributes: * `label` - (Required) Label to assign to this network interface -* `ip_address` - (Optional) Static IP to assign to this network interface. Interface will use DHCP if this is left blank. +* `ip_address` - (Optional) Static IP to assign to this network interface. Interface will use DHCP if this is left blank. Currently only IPv4 IP addresses are supported. * `subnet_mask` - (Optional) Subnet mask to use when statically assigning an IP.