From 5576ec6b107b4786b6879f6d2827d332b2ee7dbe Mon Sep 17 00:00:00 2001 From: Patrick Gray Date: Sun, 13 Sep 2015 03:27:07 -0400 Subject: [PATCH 01/95] implemented update function for aws_iam_user and it works but still causes transient error for aws_iam_group_membership --- .../providers/aws/resource_aws_iam_user.go | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/builtin/providers/aws/resource_aws_iam_user.go b/builtin/providers/aws/resource_aws_iam_user.go index c0ef4b8a4..e122e521e 100644 --- a/builtin/providers/aws/resource_aws_iam_user.go +++ b/builtin/providers/aws/resource_aws_iam_user.go @@ -14,9 +14,7 @@ func resourceAwsIamUser() *schema.Resource { return &schema.Resource{ Create: resourceAwsIamUserCreate, Read: resourceAwsIamUserRead, - // There is an UpdateUser API call, but goamz doesn't support it yet. - // XXX but we aren't using goamz anymore. - //Update: resourceAwsIamUserUpdate, + Update: resourceAwsIamUserUpdate, Delete: resourceAwsIamUserDelete, Schema: map[string]*schema.Schema{ @@ -39,7 +37,6 @@ func resourceAwsIamUser() *schema.Resource { "name": &schema.Schema{ Type: schema.TypeString, Required: true, - ForceNew: true, }, "path": &schema.Schema{ Type: schema.TypeString, @@ -54,9 +51,10 @@ func resourceAwsIamUser() *schema.Resource { func resourceAwsIamUserCreate(d *schema.ResourceData, meta interface{}) error { iamconn := meta.(*AWSClient).iamconn name := d.Get("name").(string) + path := d.Get("path").(string) request := &iam.CreateUserInput{ - Path: aws.String(d.Get("path").(string)), + Path: aws.String(path), UserName: aws.String(name), } @@ -69,9 +67,9 @@ func resourceAwsIamUserCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsIamUserRead(d *schema.ResourceData, meta interface{}) error { iamconn := meta.(*AWSClient).iamconn - + name := d.Get("name").(string) request := &iam.GetUserInput{ - UserName: aws.String(d.Id()), + UserName: aws.String(name), } getResp, err := iamconn.GetUser(request) @@ -102,6 +100,29 @@ func resourceAwsIamUserReadResult(d *schema.ResourceData, user *iam.User) error return nil } +func resourceAwsIamUserUpdate(d *schema.ResourceData, meta interface{}) error { + if d.HasChange("name") || d.HasChange("path") { + iamconn := meta.(*AWSClient).iamconn + on, nn := d.GetChange("name") + op, np := d.GetChange("path") + fmt.Println(on, nn, op, np) + request := &iam.UpdateUserInput{ + UserName: aws.String(on.(string)), + NewUserName: aws.String(nn.(string)), + NewPath: aws.String(np.(string)), + } + _, err := iamconn.UpdateUser(request) + if err != nil { + if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" { + d.SetId("") + return nil + } + return fmt.Errorf("Error updating IAM User %s: %s", d.Id(), err) + } + return resourceAwsIamUserRead(d, meta) + } + return nil +} func resourceAwsIamUserDelete(d *schema.ResourceData, meta interface{}) error { iamconn := meta.(*AWSClient).iamconn From 0ab12a54f138a9d70e9bc6ba97b955c58f57f874 Mon Sep 17 00:00:00 2001 From: Patrick Gray Date: Sun, 13 Sep 2015 03:37:45 -0400 Subject: [PATCH 02/95] handle error of trying to remove a user from a group when their name has already changed --- builtin/providers/aws/resource_aws_iam_group_membership.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builtin/providers/aws/resource_aws_iam_group_membership.go b/builtin/providers/aws/resource_aws_iam_group_membership.go index c90511cd6..14bdd3713 100644 --- a/builtin/providers/aws/resource_aws_iam_group_membership.go +++ b/builtin/providers/aws/resource_aws_iam_group_membership.go @@ -135,6 +135,9 @@ func removeUsersFromGroup(conn *iam.IAM, users []*string, group string) error { }) if err != nil { + if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" { + return nil + } return err } } From 37e280f6be4de2b6983d869096198261159b64b8 Mon Sep 17 00:00:00 2001 From: Patrick Gray Date: Sun, 13 Sep 2015 03:52:40 -0400 Subject: [PATCH 03/95] add tests for updating a user --- builtin/providers/aws/resource_aws_iam_user_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/builtin/providers/aws/resource_aws_iam_user_test.go b/builtin/providers/aws/resource_aws_iam_user_test.go index 0dd2530a1..4a7939658 100644 --- a/builtin/providers/aws/resource_aws_iam_user_test.go +++ b/builtin/providers/aws/resource_aws_iam_user_test.go @@ -26,6 +26,13 @@ func TestAccAWSUser_basic(t *testing.T) { testAccCheckAWSUserAttributes(&conf), ), }, + resource.TestStep{ + Config: testAccAWSUserConfig2, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSUserExists("aws_iam_user.user", &conf), + testAccCheckAWSUserAttributes(&conf), + ), + }, }, }) } @@ -105,3 +112,9 @@ resource "aws_iam_user" "user" { path = "/" } ` +const testAccAWSUserConfig2 = ` +resource "aws_iam_user" "user" { + name = "test-user2" + path = "/balls/" +} +` From d798042475f8f9c4545052bb67b8941dc565e939 Mon Sep 17 00:00:00 2001 From: clint shryock Date: Tue, 20 Oct 2015 13:32:35 -0500 Subject: [PATCH 04/95] provider/aws: Downcase Route 53 record names in statefile --- builtin/providers/aws/resource_aws_route53_record.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_route53_record.go b/builtin/providers/aws/resource_aws_route53_record.go index 1966c33de..9944d4312 100644 --- a/builtin/providers/aws/resource_aws_route53_record.go +++ b/builtin/providers/aws/resource_aws_route53_record.go @@ -28,6 +28,10 @@ func resourceAwsRoute53Record() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, + StateFunc: func(v interface{}) string { + value := v.(string) + return strings.ToLower(value) + }, }, "fqdn": &schema.Schema{ @@ -242,6 +246,8 @@ func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) erro StartRecordType: aws.String(d.Get("type").(string)), } + log.Printf("[DEBUG] List resource records sets for zone: %s, opts: %s", + zone, lopts) resp, err := conn.ListResourceRecordSets(lopts) if err != nil { return err @@ -251,7 +257,7 @@ func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) erro found := false for _, record := range resp.ResourceRecordSets { name := cleanRecordName(*record.Name) - if FQDN(name) != FQDN(*lopts.StartRecordName) { + if FQDN(strings.ToLower(name)) != FQDN(strings.ToLower(*lopts.StartRecordName)) { continue } if strings.ToUpper(*record.Type) != strings.ToUpper(*lopts.StartRecordType) { @@ -279,6 +285,7 @@ func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) erro } if !found { + log.Printf("[DEBUG] No matching record found for: %s, removing from state file", en) d.SetId("") } From 953f38c534f1ca5b271127d85732691080d8ecba Mon Sep 17 00:00:00 2001 From: clint shryock Date: Tue, 20 Oct 2015 16:36:25 -0500 Subject: [PATCH 05/95] lowercase everything in r53 names --- builtin/providers/aws/resource_aws_route53_record.go | 5 +++-- builtin/providers/aws/resource_aws_route53_record_test.go | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/builtin/providers/aws/resource_aws_route53_record.go b/builtin/providers/aws/resource_aws_route53_record.go index 9944d4312..25d73683c 100644 --- a/builtin/providers/aws/resource_aws_route53_record.go +++ b/builtin/providers/aws/resource_aws_route53_record.go @@ -196,12 +196,13 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er // Generate an ID vars := []string{ zone, - d.Get("name").(string), + strings.ToLower(d.Get("name").(string)), d.Get("type").(string), } if v, ok := d.GetOk("set_identifier"); ok { vars = append(vars, v.(string)) } + d.SetId(strings.Join(vars, "_")) // Wait until we are done @@ -447,7 +448,7 @@ func cleanRecordName(name string) string { // If it does not, add the zone name to form a fully qualified name // and keep AWS happy. func expandRecordName(name, zone string) string { - rn := strings.TrimSuffix(name, ".") + rn := strings.ToLower(strings.TrimSuffix(name, ".")) zone = strings.TrimSuffix(zone, ".") if !strings.HasSuffix(rn, zone) { rn = strings.Join([]string{name, zone}, ".") diff --git a/builtin/providers/aws/resource_aws_route53_record_test.go b/builtin/providers/aws/resource_aws_route53_record_test.go index bbeb859cd..d10653a0c 100644 --- a/builtin/providers/aws/resource_aws_route53_record_test.go +++ b/builtin/providers/aws/resource_aws_route53_record_test.go @@ -291,7 +291,7 @@ func testAccCheckRoute53RecordExists(n string) resource.TestCheckFunc { // rec := resp.ResourceRecordSets[0] for _, rec := range resp.ResourceRecordSets { recName := cleanRecordName(*rec.Name) - if FQDN(recName) == FQDN(en) && *rec.Type == rType { + if FQDN(strings.ToLower(recName)) == FQDN(strings.ToLower(en)) && *rec.Type == rType { return nil } } @@ -306,7 +306,7 @@ resource "aws_route53_zone" "main" { resource "aws_route53_record" "default" { zone_id = "${aws_route53_zone.main.zone_id}" - name = "www.notexample.com" + name = "www.NOTexamplE.com" type = "A" ttl = "30" records = ["127.0.0.1", "127.0.0.27"] From 65cd53f70afac43665150b30c7e3287236648ce9 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Thu, 22 Oct 2015 21:14:54 +0200 Subject: [PATCH 06/95] digitalocean: update index docs for do_token var Updates the docs and clarifies the usage of `do_token` variable. I was experiencing an issue mentioned here https://github.com/hashicorp/terraform/issues/124 and so adding more docs should be helpful. --- website/source/docs/providers/do/index.html.markdown | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/source/docs/providers/do/index.html.markdown b/website/source/docs/providers/do/index.html.markdown index 9e18277a3..468539c57 100644 --- a/website/source/docs/providers/do/index.html.markdown +++ b/website/source/docs/providers/do/index.html.markdown @@ -17,6 +17,9 @@ Use the navigation to the left to read about the available resources. ## Example Usage ``` +# Set the variable value in *.tfvars file or using -var="do_token=..." CLI option +variable "do_token" {} + # Configure the DigitalOcean Provider provider "digitalocean" { token = "${var.do_token}" From 8dd4b155e08614e59f26e89ae41938c97d0e15c6 Mon Sep 17 00:00:00 2001 From: Eloy Coto Date: Tue, 27 Oct 2015 09:43:01 +0000 Subject: [PATCH 07/95] Vsphere_virtual_machine: Delete all network interfaces from template before added new one. Fixes #3559 and #3560 --- .../vsphere/resource_vsphere_virtual_machine.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index c6b1292ac..33c4ff227 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -890,6 +890,22 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { } log.Printf("[DEBUG] template: %#v", template) + devices, err := template.Device(context.TODO()) + if err != nil { + log.Printf("[DEBUG] Template devices can't be found") + return err + } + + for _, dvc := range devices { + // Issue 3559/3560: Delete all ethernet devices to add the correct ones later + if devices.Type(dvc) == "ethernet" { + err := template.RemoveDevice(context.TODO(), dvc) + if err != nil { + return err + } + } + } + var resourcePool *object.ResourcePool if vm.resourcePool == "" { if vm.cluster == "" { From 8c494031327e21aa7f549e0155eda64cc5a1f86a Mon Sep 17 00:00:00 2001 From: Eloy Coto Date: Wed, 28 Oct 2015 07:43:43 +0000 Subject: [PATCH 08/95] Vsphere_virtual_machine: Keep template as it is and apply the customizations and add devices in the new server. When finished PowerOn --- .../resource_vsphere_virtual_machine.go | 63 ++++++++++++------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index 33c4ff227..c1e4225f3 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -890,22 +890,6 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { } log.Printf("[DEBUG] template: %#v", template) - devices, err := template.Device(context.TODO()) - if err != nil { - log.Printf("[DEBUG] Template devices can't be found") - return err - } - - for _, dvc := range devices { - // Issue 3559/3560: Delete all ethernet devices to add the correct ones later - if devices.Type(dvc) == "ethernet" { - err := template.RemoveDevice(context.TODO(), dvc) - if err != nil { - return err - } - } - } - var resourcePool *object.ResourcePool if vm.resourcePool == "" { if vm.cluster == "" { @@ -1013,7 +997,6 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { NumCPUs: vm.vcpu, NumCoresPerSocket: 1, MemoryMB: vm.memoryMb, - DeviceChange: networkDevices, } log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) @@ -1037,11 +1020,10 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { // make vm clone spec cloneSpec := types.VirtualMachineCloneSpec{ - Location: relocateSpec, - Template: false, - Config: &configSpec, - Customization: &customSpec, - PowerOn: true, + Location: relocateSpec, + Template: false, + Config: &configSpec, + PowerOn: false, } log.Printf("[DEBUG] clone spec: %v", cloneSpec) @@ -1061,6 +1043,43 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { } log.Printf("[DEBUG] new vm: %v", newVM) + devices, err := newVM.Device(context.TODO()) + if err != nil { + log.Printf("[DEBUG] Template devices can't be found") + return err + } + + for _, dvc := range devices { + // Issue 3559/3560: Delete all ethernet devices to add the correct ones later + if devices.Type(dvc) == "ethernet" { + err := newVM.RemoveDevice(context.TODO(), dvc) + if err != nil { + return err + } + } + } + // Add Network devices + for _, dvc := range networkDevices { + err := newVM.AddDevice( + context.TODO(), dvc.GetVirtualDeviceConfigSpec().Device) + if err != nil { + return err + } + } + + taskb, err := newVM.Customize(context.TODO(), customSpec) + if err != nil { + return err + } + + _, err = taskb.WaitForResult(context.TODO(), nil) + if err != nil { + return err + } + log.Printf("[DEBUG]VM customization finished") + + newVM.PowerOn(context.TODO()) + ip, err := newVM.WaitForIP(context.TODO()) if err != nil { return err From 7dd15469a5aa97a973f0a5590a7c2ac84cb9373c Mon Sep 17 00:00:00 2001 From: stack72 Date: Fri, 30 Oct 2015 23:55:00 +0000 Subject: [PATCH 09/95] Adding the ability to specify a snapshot window and retention limit for Redis ElastiCache clusters --- .../aws/resource_aws_elasticache_cluster.go | 37 ++++++++++++ .../resource_aws_elasticache_cluster_test.go | 58 +++++++++++++++++++ .../aws/r/elasticache_cluster.html.markdown | 9 +++ 3 files changed, 104 insertions(+) diff --git a/builtin/providers/aws/resource_aws_elasticache_cluster.go b/builtin/providers/aws/resource_aws_elasticache_cluster.go index 3460fb292..dde2cd5e3 100644 --- a/builtin/providers/aws/resource_aws_elasticache_cluster.go +++ b/builtin/providers/aws/resource_aws_elasticache_cluster.go @@ -138,6 +138,24 @@ func resourceAwsElasticacheCluster() *schema.Resource { }, }, + "snapshot_window": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "snapshot_retention_limit": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { + value := v.(int) + if value > 35 { + es = append(es, fmt.Errorf( + "snapshot retention limit cannot be more than 35 days")) + } + return + }, + }, + "tags": tagsSchema(), // apply_immediately is used to determine when the update modifications @@ -187,6 +205,14 @@ func resourceAwsElasticacheClusterCreate(d *schema.ResourceData, meta interface{ req.CacheParameterGroupName = aws.String(v.(string)) } + if v, ok := d.GetOk("snapshot_retention_limit"); ok { + req.SnapshotRetentionLimit = aws.Int64(int64(v.(int))) + } + + if v, ok := d.GetOk("snapshot_window"); ok { + req.SnapshotWindow = aws.String(v.(string)) + } + if v, ok := d.GetOk("maintenance_window"); ok { req.PreferredMaintenanceWindow = aws.String(v.(string)) } @@ -261,6 +287,8 @@ func resourceAwsElasticacheClusterRead(d *schema.ResourceData, meta interface{}) d.Set("security_group_ids", c.SecurityGroups) d.Set("parameter_group_name", c.CacheParameterGroup) d.Set("maintenance_window", c.PreferredMaintenanceWindow) + d.Set("snapshot_window", c.SnapshotWindow) + d.Set("snapshot_retention_limit", c.SnapshotRetentionLimit) if c.NotificationConfiguration != nil { if *c.NotificationConfiguration.TopicStatus == "active" { d.Set("notification_topic_arn", c.NotificationConfiguration.TopicArn) @@ -344,6 +372,15 @@ func resourceAwsElasticacheClusterUpdate(d *schema.ResourceData, meta interface{ requestUpdate = true } + if d.HasChange("snapshot_window") { + req.EngineVersion = aws.String(d.Get("snapshot_window").(string)) + requestUpdate = true + } + + if d.HasChange("snapshot_retention_limit") { + req.NumCacheNodes = aws.Int64(int64(d.Get("snapshot_retention_limit").(int))) + } + if d.HasChange("num_cache_nodes") { req.NumCacheNodes = aws.Int64(int64(d.Get("num_cache_nodes").(int))) requestUpdate = true diff --git a/builtin/providers/aws/resource_aws_elasticache_cluster_test.go b/builtin/providers/aws/resource_aws_elasticache_cluster_test.go index b93060028..d084224fb 100644 --- a/builtin/providers/aws/resource_aws_elasticache_cluster_test.go +++ b/builtin/providers/aws/resource_aws_elasticache_cluster_test.go @@ -33,6 +33,28 @@ func TestAccAWSElasticacheCluster_basic(t *testing.T) { }) } +func TestAccAWSElasticacheCluster_snapshots(t *testing.T) { + var ec elasticache.CacheCluster + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSElasticacheClusterDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSElasticacheClusterConfig_snapshots, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheSecurityGroupExists("aws_elasticache_security_group.bar"), + testAccCheckAWSElasticacheClusterExists("aws_elasticache_cluster.bar", &ec), + resource.TestCheckResourceAttr( + "aws_elasticache_cluster.bar", "snapshot_window", "05:00-09:00"), + resource.TestCheckResourceAttr( + "aws_elasticache_cluster.bar", "snapshot_retention_limit", "3"), + ), + }, + }, + }) +} + func TestAccAWSElasticacheCluster_vpc(t *testing.T) { var csg elasticache.CacheSubnetGroup var ec elasticache.CacheCluster @@ -149,6 +171,42 @@ resource "aws_elasticache_cluster" "bar" { port = 11211 parameter_group_name = "default.memcached1.4" security_group_names = ["${aws_elasticache_security_group.bar.name}"] + snapshot_window = "05:00-09:00" + snapshot_retention_limit = 3 +} +`, genRandInt(), genRandInt(), genRandInt()) + +var testAccAWSElasticacheClusterConfig_snapshots = fmt.Sprintf(` +provider "aws" { + region = "us-east-1" +} +resource "aws_security_group" "bar" { + name = "tf-test-security-group-%03d" + description = "tf-test-security-group-descr" + ingress { + from_port = -1 + to_port = -1 + protocol = "icmp" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_elasticache_security_group" "bar" { + name = "tf-test-security-group-%03d" + description = "tf-test-security-group-descr" + security_group_names = ["${aws_security_group.bar.name}"] +} + +resource "aws_elasticache_cluster" "bar" { + cluster_id = "tf-test-%03d" + engine = "redis" + node_type = "cache.m1.small" + num_cache_nodes = 1 + port = 6379 + parameter_group_name = "default.redis2.8" + security_group_names = ["${aws_elasticache_security_group.bar.name}"] + snapshot_window = "05:00-09:00" + snapshot_retention_limit = 3 } `, genRandInt(), genRandInt(), genRandInt()) 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 ef1d69ed4..4a4cb4d76 100644 --- a/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown +++ b/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown @@ -73,6 +73,15 @@ names to associate with this cache cluster Amazon Resource Name (ARN) of a Redis RDB snapshot file stored in Amazon S3. Example: `arn:aws:s3:::my_bucket/snapshot1.rdb` +* `snapshot_window` - (Optional) The daily time range (in UTC) during which ElastiCache will +begin taking a daily snapshot of your cache cluster. Can only be used for the Redis engine. Example: 05:00-09:00 + +* `snapshow_retention_limit` - (Optional) The number of days for which ElastiCache will +retain automatic cache cluster snapshots before deleting them. For example, if you set +SnapshotRetentionLimit to 5, then a snapshot that was taken today will be retained for 5 days +before being deleted. If the value of SnapshotRetentionLimit is set to zero (0), backups are turned off. +Can only be used for the Redis engine. + * `notification_topic_arn` – (Optional) An Amazon Resource Name (ARN) of an SNS topic to send ElastiCache notifications to. Example: `arn:aws:sns:us-east-1:012345678999:my_sns_topic` From 12fc9a1d7887c530cc982d8964d321159e66dd4c Mon Sep 17 00:00:00 2001 From: Takaaki Furukawa Date: Fri, 30 Oct 2015 18:18:25 +0900 Subject: [PATCH 10/95] Fix acceptance tests for using optional parameters at different environment --- .../resource_vsphere_virtual_machine_test.go | 86 ++++++++----------- .../providers/vsphere/index.html.markdown | 12 ++- 2 files changed, 46 insertions(+), 52 deletions(-) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go index 75bc339e8..66d6ea44f 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go @@ -15,9 +15,21 @@ import ( func TestAccVSphereVirtualMachine_basic(t *testing.T) { var vm virtualMachine - datacenter := os.Getenv("VSPHERE_DATACENTER") - cluster := os.Getenv("VSPHERE_CLUSTER") - datastore := os.Getenv("VSPHERE_DATASTORE") + var locationOpt string + var datastoreOpt string + + if v := os.Getenv("VSPHERE_DATACENTER"); v != "" { + locationOpt += fmt.Sprintf(" datacenter = \"%s\"\n", v) + } + if v := os.Getenv("VSPHERE_CLUSTER"); v != "" { + locationOpt += fmt.Sprintf(" cluster = \"%s\"\n", v) + } + if v := os.Getenv("VSPHERE_RESOURCE_POOL"); v != "" { + locationOpt += fmt.Sprintf(" resource_pool = \"%s\"\n", v) + } + if v := os.Getenv("VSPHERE_DATASTORE"); v != "" { + datastoreOpt = fmt.Sprintf(" datastore = \"%s\"\n", v) + } template := os.Getenv("VSPHERE_TEMPLATE") gateway := os.Getenv("VSPHERE_NETWORK_GATEWAY") label := os.Getenv("VSPHERE_NETWORK_LABEL") @@ -31,28 +43,23 @@ func TestAccVSphereVirtualMachine_basic(t *testing.T) { resource.TestStep{ Config: fmt.Sprintf( testAccCheckVSphereVirtualMachineConfig_basic, - datacenter, - cluster, + locationOpt, gateway, label, ip_address, - datastore, + datastoreOpt, template, ), Check: resource.ComposeTestCheckFunc( testAccCheckVSphereVirtualMachineExists("vsphere_virtual_machine.foo", &vm), resource.TestCheckResourceAttr( "vsphere_virtual_machine.foo", "name", "terraform-test"), - resource.TestCheckResourceAttr( - "vsphere_virtual_machine.foo", "datacenter", datacenter), resource.TestCheckResourceAttr( "vsphere_virtual_machine.foo", "vcpu", "2"), resource.TestCheckResourceAttr( "vsphere_virtual_machine.foo", "memory", "4096"), resource.TestCheckResourceAttr( "vsphere_virtual_machine.foo", "disk.#", "2"), - resource.TestCheckResourceAttr( - "vsphere_virtual_machine.foo", "disk.0.datastore", datastore), resource.TestCheckResourceAttr( "vsphere_virtual_machine.foo", "disk.0.template", template), resource.TestCheckResourceAttr( @@ -67,12 +74,23 @@ func TestAccVSphereVirtualMachine_basic(t *testing.T) { func TestAccVSphereVirtualMachine_dhcp(t *testing.T) { var vm virtualMachine - datacenter := os.Getenv("VSPHERE_DATACENTER") - cluster := os.Getenv("VSPHERE_CLUSTER") - datastore := os.Getenv("VSPHERE_DATASTORE") + var locationOpt string + var datastoreOpt string + + if v := os.Getenv("VSPHERE_DATACENTER"); v != "" { + locationOpt += fmt.Sprintf(" datacenter = \"%s\"\n", v) + } + if v := os.Getenv("VSPHERE_CLUSTER"); v != "" { + locationOpt += fmt.Sprintf(" cluster = \"%s\"\n", v) + } + if v := os.Getenv("VSPHERE_RESOURCE_POOL"); v != "" { + locationOpt += fmt.Sprintf(" resource_pool = \"%s\"\n", v) + } + if v := os.Getenv("VSPHERE_DATASTORE"); v != "" { + datastoreOpt = fmt.Sprintf(" datastore = \"%s\"\n", v) + } template := os.Getenv("VSPHERE_TEMPLATE") label := os.Getenv("VSPHERE_NETWORK_LABEL_DHCP") - password := os.Getenv("VSPHERE_VM_PASSWORD") resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -82,27 +100,21 @@ func TestAccVSphereVirtualMachine_dhcp(t *testing.T) { resource.TestStep{ Config: fmt.Sprintf( testAccCheckVSphereVirtualMachineConfig_dhcp, - datacenter, - cluster, + locationOpt, label, - datastore, + datastoreOpt, template, - password, ), Check: resource.ComposeTestCheckFunc( testAccCheckVSphereVirtualMachineExists("vsphere_virtual_machine.bar", &vm), resource.TestCheckResourceAttr( "vsphere_virtual_machine.bar", "name", "terraform-test"), - resource.TestCheckResourceAttr( - "vsphere_virtual_machine.bar", "datacenter", datacenter), resource.TestCheckResourceAttr( "vsphere_virtual_machine.bar", "vcpu", "2"), resource.TestCheckResourceAttr( "vsphere_virtual_machine.bar", "memory", "4096"), resource.TestCheckResourceAttr( "vsphere_virtual_machine.bar", "disk.#", "1"), - resource.TestCheckResourceAttr( - "vsphere_virtual_machine.bar", "disk.0.datastore", datastore), resource.TestCheckResourceAttr( "vsphere_virtual_machine.bar", "disk.0.template", template), resource.TestCheckResourceAttr( @@ -168,20 +180,6 @@ func testAccCheckVSphereVirtualMachineExists(n string, vm *virtualMachine) resou } _, err = object.NewSearchIndex(client.Client).FindChild(context.TODO(), dcFolders.VmFolder, rs.Primary.Attributes["name"]) - /* - vmRef, err := client.SearchIndex().FindChild(dcFolders.VmFolder, rs.Primary.Attributes["name"]) - if err != nil { - return fmt.Errorf("error %s", err) - } - - found := govmomi.NewVirtualMachine(client, vmRef.Reference()) - fmt.Printf("%v", found) - - if found.Name != rs.Primary.ID { - return fmt.Errorf("Instance not found") - } - *instance = *found - */ *vm = virtualMachine{ name: rs.Primary.ID, @@ -194,8 +192,7 @@ func testAccCheckVSphereVirtualMachineExists(n string, vm *virtualMachine) resou const testAccCheckVSphereVirtualMachineConfig_basic = ` resource "vsphere_virtual_machine" "foo" { name = "terraform-test" - datacenter = "%s" - cluster = "%s" +%s vcpu = 2 memory = 4096 gateway = "%s" @@ -205,7 +202,7 @@ resource "vsphere_virtual_machine" "foo" { subnet_mask = "255.255.255.0" } disk { - datastore = "%s" +%s template = "%s" iops = 500 } @@ -219,22 +216,15 @@ resource "vsphere_virtual_machine" "foo" { const testAccCheckVSphereVirtualMachineConfig_dhcp = ` resource "vsphere_virtual_machine" "bar" { name = "terraform-test" - datacenter = "%s" - cluster = "%s" +%s vcpu = 2 memory = 4096 network_interface { label = "%s" } disk { - datastore = "%s" +%s template = "%s" } - - connection { - host = "${self.network_interface.0.ip_address}" - user = "root" - password = "%s" - } } ` diff --git a/website/source/docs/providers/vsphere/index.html.markdown b/website/source/docs/providers/vsphere/index.html.markdown index d8168e072..303033787 100644 --- a/website/source/docs/providers/vsphere/index.html.markdown +++ b/website/source/docs/providers/vsphere/index.html.markdown @@ -64,15 +64,19 @@ configuration fields to be set using the documented environment variables. In addition, the following environment variables are used in tests, and must be set to valid values for your vSphere environment: - * VSPHERE\_CLUSTER - * VSPHERE\_DATACENTER - * VSPHERE\_DATASTORE * VSPHERE\_NETWORK\_GATEWAY * VSPHERE\_NETWORK\_IP\_ADDRESS * VSPHERE\_NETWORK\_LABEL * VSPHERE\_NETWORK\_LABEL\_DHCP * VSPHERE\_TEMPLATE - * VSPHERE\_VM\_PASSWORD + +The following environment variables depend on your vSphere environment: + + * VSPHERE\_DATACENTER + * VSPHERE\_CLUSTER + * VSPHERE\_RESOURCE\_POOL + * VSPHERE\_DATASTORE + These are used to set and verify attributes on the `vsphere_virtual_machine` resource in tests. From 4f05df6cad9773c2bda92bae8def4017ec3d1041 Mon Sep 17 00:00:00 2001 From: stack72 Date: Mon, 2 Nov 2015 20:57:04 +0000 Subject: [PATCH 11/95] When I was setting the update parameters for the Snapshotting, I didn't update the copy/pasted params --- builtin/providers/aws/resource_aws_elasticache_cluster.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_elasticache_cluster.go b/builtin/providers/aws/resource_aws_elasticache_cluster.go index dde2cd5e3..a33321c3b 100644 --- a/builtin/providers/aws/resource_aws_elasticache_cluster.go +++ b/builtin/providers/aws/resource_aws_elasticache_cluster.go @@ -373,12 +373,11 @@ func resourceAwsElasticacheClusterUpdate(d *schema.ResourceData, meta interface{ } if d.HasChange("snapshot_window") { - req.EngineVersion = aws.String(d.Get("snapshot_window").(string)) - requestUpdate = true + req.SnapshotWindow = aws.String(d.Get("snapshot_window").(string)) } if d.HasChange("snapshot_retention_limit") { - req.NumCacheNodes = aws.Int64(int64(d.Get("snapshot_retention_limit").(int))) + req.SnapshotRetentionLimit = aws.Int64(int64(d.Get("snapshot_retention_limit").(int))) } if d.HasChange("num_cache_nodes") { From 964c9cd7e8c6a3b7e0cebb56195079366cf8b62c Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Mon, 2 Nov 2015 18:21:08 -0800 Subject: [PATCH 12/95] Change all occurrences of 'vSphere' to 'VMware vSphere' --- .../providers/vsphere/index.html.markdown | 25 ++++++++++--------- website/source/layouts/docs.erb | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/website/source/docs/providers/vsphere/index.html.markdown b/website/source/docs/providers/vsphere/index.html.markdown index d8168e072..f4999be73 100644 --- a/website/source/docs/providers/vsphere/index.html.markdown +++ b/website/source/docs/providers/vsphere/index.html.markdown @@ -1,27 +1,28 @@ --- layout: "vsphere" -page_title: "Provider: vSphere" +page_title: "Provider: VMware vSphere" sidebar_current: "docs-vsphere-index" description: |- - The vSphere provider is used to interact with the resources supported by - vSphere. The provider needs to be configured with the proper credentials before - it can be used. + The VMware vSphere provider is used to interact with the resources supported by + VMware vSphere. The provider needs to be configured with the proper credentials + before it can be used. --- -# vSphere Provider +# VMware vSphere Provider -The vSphere provider is used to interact with the resources supported by vSphere. +The VMware vSphere provider is used to interact with the resources supported by +VMware vSphere. The provider needs to be configured with the proper credentials before it can be used. Use the navigation to the left to read about the available resources. -~> **NOTE:** The vSphere Provider currently represents _initial support_ and -therefore may undergo significant changes as the community improves it. +~> **NOTE:** The VMware vSphere Provider currently represents _initial support_ +and therefore may undergo significant changes as the community improves it. ## Example Usage ``` -# Configure the vSphere Provider +# Configure the VMware vSphere Provider provider "vsphere" { user = "${var.vsphere_user}" password = "${var.vsphere_password}" @@ -47,7 +48,7 @@ resource "vsphere_virtual_machine" "web" { ## Argument Reference -The following arguments are used to configure the vSphere Provider: +The following arguments are used to configure the VMware vSphere Provider: * `user` - (Required) This is the username for vSphere API operations. Can also be specified with the `VSPHERE_USER` environment variable. @@ -59,10 +60,10 @@ The following arguments are used to configure the vSphere Provider: ## Acceptance Tests -The vSphere provider's acceptance tests require the above provider +The VMware vSphere provider's acceptance tests require the above provider configuration fields to be set using the documented environment variables. -In addition, the following environment variables are used in tests, and must be set to valid values for your vSphere environment: +In addition, the following environment variables are used in tests, and must be set to valid values for your VMware vSphere environment: * VSPHERE\_CLUSTER * VSPHERE\_DATACENTER diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index a5e8eb1c9..59eb10df9 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -194,7 +194,7 @@ > - vSphere + VMware vSphere From d99b9aaa6f0e977aa2c19818a9788ddbf3aaf95b Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Mon, 2 Nov 2015 18:25:05 -0800 Subject: [PATCH 13/95] vSphere occurrence in layout --- website/source/layouts/vsphere.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/layouts/vsphere.erb b/website/source/layouts/vsphere.erb index 49e58c057..511511511 100644 --- a/website/source/layouts/vsphere.erb +++ b/website/source/layouts/vsphere.erb @@ -7,7 +7,7 @@ > - vSphere Provider + VMware vSphere Provider > From 05a0a41236d04d0a29a45ff97207abd0ae3aa58c Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Mon, 2 Nov 2015 18:27:22 -0800 Subject: [PATCH 14/95] vSphere occurrences in vsphere_virtual_machine resource page --- .../docs/providers/vsphere/r/virtual_machine.html.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 6ce012d65..d008357ec 100644 --- a/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown +++ b/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown @@ -1,14 +1,14 @@ --- layout: "vsphere" -page_title: "vSphere: vsphere_virtual_machine" +page_title: "VMware vSphere: vsphere_virtual_machine" sidebar_current: "docs-vsphere-resource-virtual-machine" description: |- - Provides a vSphere virtual machine resource. This can be used to create, modify, and delete virtual machines. + Provides a VMware vSphere virtual machine resource. This can be used to create, modify, and delete virtual machines. --- # vsphere\_virtual\_machine -Provides a vSphere virtual machine resource. This can be used to create, +Provides a VMware vSphere virtual machine resource. This can be used to create, modify, and delete virtual machines. ## Example Usage From 707bfd739aa2bdd9748896e3cbc5b6e02d1e1077 Mon Sep 17 00:00:00 2001 From: stack72 Date: Tue, 3 Nov 2015 12:35:06 +0000 Subject: [PATCH 15/95] Added an extra test for the Elasticache Cluster to show that updates work. Also added some debugging to show that the API returns the Elasticache retention period info --- .../aws/resource_aws_elasticache_cluster.go | 2 + .../resource_aws_elasticache_cluster_test.go | 67 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/builtin/providers/aws/resource_aws_elasticache_cluster.go b/builtin/providers/aws/resource_aws_elasticache_cluster.go index a33321c3b..a2c312d35 100644 --- a/builtin/providers/aws/resource_aws_elasticache_cluster.go +++ b/builtin/providers/aws/resource_aws_elasticache_cluster.go @@ -287,7 +287,9 @@ func resourceAwsElasticacheClusterRead(d *schema.ResourceData, meta interface{}) d.Set("security_group_ids", c.SecurityGroups) d.Set("parameter_group_name", c.CacheParameterGroup) d.Set("maintenance_window", c.PreferredMaintenanceWindow) + log.Printf("[INFO] Found %s as the Snapshow Window", *c.SnapshotWindow) d.Set("snapshot_window", c.SnapshotWindow) + log.Printf("[INFO] Found %d as the Snapshow Retention Limit", *c.SnapshotRetentionLimit) d.Set("snapshot_retention_limit", c.SnapshotRetentionLimit) if c.NotificationConfiguration != nil { if *c.NotificationConfiguration.TopicStatus == "active" { diff --git a/builtin/providers/aws/resource_aws_elasticache_cluster_test.go b/builtin/providers/aws/resource_aws_elasticache_cluster_test.go index d084224fb..666be2c8a 100644 --- a/builtin/providers/aws/resource_aws_elasticache_cluster_test.go +++ b/builtin/providers/aws/resource_aws_elasticache_cluster_test.go @@ -54,6 +54,39 @@ func TestAccAWSElasticacheCluster_snapshots(t *testing.T) { }, }) } +func TestAccAWSElasticacheCluster_snapshotsWithUpdates(t *testing.T) { + var ec elasticache.CacheCluster + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSElasticacheClusterDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSElasticacheClusterConfig_snapshots, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheSecurityGroupExists("aws_elasticache_security_group.bar"), + testAccCheckAWSElasticacheClusterExists("aws_elasticache_cluster.bar", &ec), + resource.TestCheckResourceAttr( + "aws_elasticache_cluster.bar", "snapshot_window", "05:00-09:00"), + resource.TestCheckResourceAttr( + "aws_elasticache_cluster.bar", "snapshot_retention_limit", "3"), + ), + }, + + resource.TestStep{ + Config: testAccAWSElasticacheClusterConfig_snapshotsUpdated, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheSecurityGroupExists("aws_elasticache_security_group.bar"), + testAccCheckAWSElasticacheClusterExists("aws_elasticache_cluster.bar", &ec), + resource.TestCheckResourceAttr( + "aws_elasticache_cluster.bar", "snapshot_window", "07:00-09:00"), + resource.TestCheckResourceAttr( + "aws_elasticache_cluster.bar", "snapshot_retention_limit", "7"), + ), + }, + }, + }) +} func TestAccAWSElasticacheCluster_vpc(t *testing.T) { var csg elasticache.CacheSubnetGroup @@ -210,6 +243,40 @@ resource "aws_elasticache_cluster" "bar" { } `, genRandInt(), genRandInt(), genRandInt()) +var testAccAWSElasticacheClusterConfig_snapshotsUpdated = fmt.Sprintf(` +provider "aws" { + region = "us-east-1" +} +resource "aws_security_group" "bar" { + name = "tf-test-security-group-%03d" + description = "tf-test-security-group-descr" + ingress { + from_port = -1 + to_port = -1 + protocol = "icmp" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_elasticache_security_group" "bar" { + name = "tf-test-security-group-%03d" + description = "tf-test-security-group-descr" + security_group_names = ["${aws_security_group.bar.name}"] +} + +resource "aws_elasticache_cluster" "bar" { + cluster_id = "tf-test-%03d" + engine = "redis" + node_type = "cache.m1.small" + num_cache_nodes = 1 + port = 6379 + parameter_group_name = "default.redis2.8" + security_group_names = ["${aws_elasticache_security_group.bar.name}"] + snapshot_window = "07:00-09:00" + snapshot_retention_limit = 7 +} +`, genRandInt(), genRandInt(), genRandInt()) + var testAccAWSElasticacheClusterInVPCConfig = fmt.Sprintf(` resource "aws_vpc" "foo" { cidr_block = "192.168.0.0/16" From ffdd4de326c1821df33af32703f1c7d86c73b12a Mon Sep 17 00:00:00 2001 From: stack72 Date: Tue, 3 Nov 2015 20:53:54 +0000 Subject: [PATCH 16/95] Making user_data force a new droplet for DigitalOcean --- .../resource_digitalocean_droplet.go | 1 + .../resource_digitalocean_droplet_test.go | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/builtin/providers/digitalocean/resource_digitalocean_droplet.go b/builtin/providers/digitalocean/resource_digitalocean_droplet.go index 4dbb77ab7..ef25b0074 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_droplet.go +++ b/builtin/providers/digitalocean/resource_digitalocean_droplet.go @@ -100,6 +100,7 @@ func resourceDigitalOceanDroplet() *schema.Resource { "user_data": &schema.Schema{ Type: schema.TypeString, Optional: true, + ForceNew: true, }, }, } diff --git a/builtin/providers/digitalocean/resource_digitalocean_droplet_test.go b/builtin/providers/digitalocean/resource_digitalocean_droplet_test.go index 730718c3f..7f85034cb 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_droplet_test.go +++ b/builtin/providers/digitalocean/resource_digitalocean_droplet_test.go @@ -71,6 +71,36 @@ func TestAccDigitalOceanDroplet_Update(t *testing.T) { }) } +func TestAccDigitalOceanDroplet_UpdateUserData(t *testing.T) { + var droplet godo.Droplet + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDigitalOceanDropletDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckDigitalOceanDropletConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &droplet), + testAccCheckDigitalOceanDropletAttributes(&droplet), + ), + }, + + resource.TestStep{ + Config: testAccCheckDigitalOceanDropletConfig_userdata_update, + Check: resource.ComposeTestCheckFunc( + testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &droplet), + resource.TestCheckResourceAttr( + "digitalocean_droplet.foobar", + "user_data", + "foobar foobar"), + ), + }, + }, + }) +} + func TestAccDigitalOceanDroplet_PrivateNetworkingIpv6(t *testing.T) { var droplet godo.Droplet @@ -261,6 +291,16 @@ resource "digitalocean_droplet" "foobar" { } ` +const testAccCheckDigitalOceanDropletConfig_userdata_update = ` +resource "digitalocean_droplet" "foobar" { + name = "foo" + size = "512mb" + image = "centos-5-8-x32" + region = "nyc3" + user_data = "foobar foobar" +} +` + const testAccCheckDigitalOceanDropletConfig_RenameAndResize = ` resource "digitalocean_droplet" "foobar" { name = "baz" From 87dd5c5bd077f67631a0103ea8db3d16fb2c9055 Mon Sep 17 00:00:00 2001 From: Tomas Doran Date: Wed, 4 Nov 2015 04:23:13 -0800 Subject: [PATCH 17/95] Fix panic I see when upgrading to 0.6.6 Check if the policy is nil or not before type casting it --- builtin/providers/aws/resource_aws_sns_topic.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_sns_topic.go b/builtin/providers/aws/resource_aws_sns_topic.go index 6a1c46590..26e98a9b1 100644 --- a/builtin/providers/aws/resource_aws_sns_topic.go +++ b/builtin/providers/aws/resource_aws_sns_topic.go @@ -47,7 +47,10 @@ func resourceAwsSnsTopic() *schema.Resource { ForceNew: false, Computed: true, StateFunc: func(v interface{}) string { - jsonb := []byte(v.(string)) + jsonb := []byte{} + if v != nil { + jsonb = []byte(v.(string)) + } buffer := new(bytes.Buffer) if err := json.Compact(buffer, jsonb); err != nil { log.Printf("[WARN] Error compacting JSON for Policy in SNS Topic") From 1d0dbc5d19bfb3451d07070a25d4a9095dd0b124 Mon Sep 17 00:00:00 2001 From: stack72 Date: Wed, 4 Nov 2015 19:40:22 +0000 Subject: [PATCH 18/95] Adding backup_retention_period, preferred_backup_window and preferred_maintenance_window to RDS Cluster --- .../providers/aws/resource_aws_rds_cluster.go | 55 +++++++++++++++++++ .../aws/resource_aws_rds_cluster_test.go | 9 +++ .../providers/aws/r/rds_cluster.html.markdown | 9 ++- 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_rds_cluster.go b/builtin/providers/aws/resource_aws_rds_cluster.go index 57f3a27b3..f4c54c3d6 100644 --- a/builtin/providers/aws/resource_aws_rds_cluster.go +++ b/builtin/providers/aws/resource_aws_rds_cluster.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "regexp" + "strings" "time" "github.com/aws/aws-sdk-go/aws" @@ -122,6 +123,33 @@ func resourceAwsRDSCluster() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, + + "preferred_backup_window": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "preferred_maintenance_window": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + StateFunc: func(val interface{}) string { + return strings.ToLower(val.(string)) + }, + }, + + "backup_retention_period": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 1, + ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { + value := v.(int) + if value > 35 { + es = append(es, fmt.Errorf( + "backup retention period cannot be more than 35 days")) + } + return + }, + }, }, } } @@ -156,6 +184,18 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error createOpts.AvailabilityZones = expandStringList(attr.List()) } + if v, ok := d.GetOk("backup_retention_period"); ok { + createOpts.BackupRetentionPeriod = aws.Int64(int64(v.(int))) + } + + if v, ok := d.GetOk("preferred_backup_window"); ok { + createOpts.PreferredBackupWindow = aws.String(v.(string)) + } + + if v, ok := d.GetOk("preferred_maintenance_window"); ok { + createOpts.PreferredMaintenanceWindow = aws.String(v.(string)) + } + log.Printf("[DEBUG] RDS Cluster create options: %s", createOpts) resp, err := conn.CreateDBCluster(createOpts) if err != nil { @@ -223,6 +263,9 @@ func resourceAwsRDSClusterRead(d *schema.ResourceData, meta interface{}) error { d.Set("engine", dbc.Engine) d.Set("master_username", dbc.MasterUsername) d.Set("port", dbc.Port) + d.Set("backup_retention_period", dbc.BackupRetentionPeriod) + d.Set("preferred_backup_window", dbc.PreferredBackupWindow) + d.Set("preferred_maintenance_window", dbc.PreferredMaintenanceWindow) var vpcg []string for _, g := range dbc.VpcSecurityGroups { @@ -263,6 +306,18 @@ func resourceAwsRDSClusterUpdate(d *schema.ResourceData, meta interface{}) error } } + if d.HasChange("preferred_backup_window") { + req.PreferredBackupWindow = aws.String(d.Get("preferred_backup_window").(string)) + } + + if d.HasChange("preferred_maintaince_window") { + req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintaince_window").(string)) + } + + if d.HasChange("backup_retention_limit") { + req.BackupRetentionPeriod = aws.Int64(int64(d.Get("backup_retention_limit").(int))) + } + _, err := conn.ModifyDBCluster(req) if err != nil { return fmt.Errorf("[WARN] Error modifying RDS Cluster (%s): %s", d.Id(), err) diff --git a/builtin/providers/aws/resource_aws_rds_cluster_test.go b/builtin/providers/aws/resource_aws_rds_cluster_test.go index ffa2fa8e9..79da2acf4 100644 --- a/builtin/providers/aws/resource_aws_rds_cluster_test.go +++ b/builtin/providers/aws/resource_aws_rds_cluster_test.go @@ -26,6 +26,12 @@ func TestAccAWSRDSCluster_basic(t *testing.T) { Config: testAccAWSClusterConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSClusterExists("aws_rds_cluster.default", &v), + resource.TestCheckResourceAttr( + "aws_rds_cluster.default", "preferred_backup_window", "07:00-09:00"), + resource.TestCheckResourceAttr( + "aws_rds_cluster.default", "backup_retention_period", "5"), + resource.TestCheckResourceAttr( + "aws_rds_cluster.default", "preferred_maintenance_window", "tue:04:00-tue:04:30"), ), }, }, @@ -105,4 +111,7 @@ resource "aws_rds_cluster" "default" { database_name = "mydb" master_username = "foo" master_password = "mustbeeightcharaters" + backup_retention_period = 5 + preferred_backup_window = "07:00-09:00" + preferred_maintenance_window = "tue:04:00-tue:04:30" }`, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) diff --git a/website/source/docs/providers/aws/r/rds_cluster.html.markdown b/website/source/docs/providers/aws/r/rds_cluster.html.markdown index c45814f46..fb1f0dac8 100644 --- a/website/source/docs/providers/aws/r/rds_cluster.html.markdown +++ b/website/source/docs/providers/aws/r/rds_cluster.html.markdown @@ -24,6 +24,8 @@ resource "aws_rds_cluster" "default" { database_name = "mydb" master_username = "foo" master_password = "bar" + backup_retention_period = 5 + preferred_backup_window = "07:00-09:00" } ``` @@ -52,6 +54,9 @@ string. instances in the DB cluster can be created in * `backup_retention_period` - (Optional) The days to retain backups for. Default 1 +* `preferred_backup_window` - (Optional) The daily time range during which automated backups are created if automated backups are enabled using the BackupRetentionPeriod parameter. +Default: A 30-minute window selected at random from an 8-hour block of time per region. e.g. 04:00-09:00 +* `preferred_maintenance_window` - (Optional) The weekly time range during which system maintenance can occur, in (UTC) e.g. wed:04:00-wed:04:30 * `port` - (Optional) The port on which the DB accepts connections * `vpc_security_group_ids` - (Optional) List of VPC security groups to associate with the Cluster @@ -70,7 +75,8 @@ The following attributes are exported: * `allocated_storage` - The amount of allocated storage * `availability_zones` - The availability zone of the instance * `backup_retention_period` - The backup retention period -* `backup_window` - The backup window +* `preferred_backup_window` - The backup window +* `preferred_maintenance_window` - The maintenance window * `endpoint` - The primary, writeable connection endpoint * `engine` - The database engine * `engine_version` - The database engine version @@ -80,6 +86,7 @@ The following attributes are exported: * `status` - The RDS instance status * `username` - The master username for the database * `storage_encrypted` - Specifies whether the DB instance is encrypted +* `preferred_backup_window` - The daily time range during which the backups happen [1]: http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.Replication.html From dbaa44a2b4d9c75900e8540fe4234e03a143bad2 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Wed, 4 Nov 2015 15:03:14 -0600 Subject: [PATCH 19/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 506eb086b..220d08288 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ IMPROVEMENTS: * provider/aws: Apply security group after restoring db_instance from snapshot [GH-3513] * provider/aws: Making the AutoScalingGroup name optional [GH-3710] * provider/openstack: Add "delete on termination" boot-from-volume option [GH-3232] + * provider/digitalocean: Make user_data force a new droplet [GH-3740] BUG FIXES: From 4e485d4254ddb8ed807a701a6bed443bd4108dc3 Mon Sep 17 00:00:00 2001 From: stack72 Date: Wed, 4 Nov 2015 21:06:41 +0000 Subject: [PATCH 20/95] Fixing the spelling mistakes and adding a test to prove that the Updates to the new properties of RDS Cluster work as expected --- .../providers/aws/resource_aws_rds_cluster.go | 8 +-- .../aws/resource_aws_rds_cluster_test.go | 64 ++++++++++++++++++- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/builtin/providers/aws/resource_aws_rds_cluster.go b/builtin/providers/aws/resource_aws_rds_cluster.go index f4c54c3d6..ece9b686f 100644 --- a/builtin/providers/aws/resource_aws_rds_cluster.go +++ b/builtin/providers/aws/resource_aws_rds_cluster.go @@ -310,12 +310,12 @@ func resourceAwsRDSClusterUpdate(d *schema.ResourceData, meta interface{}) error req.PreferredBackupWindow = aws.String(d.Get("preferred_backup_window").(string)) } - if d.HasChange("preferred_maintaince_window") { - req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintaince_window").(string)) + if d.HasChange("preferred_maintenance_window") { + req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string)) } - if d.HasChange("backup_retention_limit") { - req.BackupRetentionPeriod = aws.Int64(int64(d.Get("backup_retention_limit").(int))) + if d.HasChange("backup_retention_period") { + req.BackupRetentionPeriod = aws.Int64(int64(d.Get("backup_retention_period").(int))) } _, err := conn.ModifyDBCluster(req) diff --git a/builtin/providers/aws/resource_aws_rds_cluster_test.go b/builtin/providers/aws/resource_aws_rds_cluster_test.go index 79da2acf4..a9eed687e 100644 --- a/builtin/providers/aws/resource_aws_rds_cluster_test.go +++ b/builtin/providers/aws/resource_aws_rds_cluster_test.go @@ -17,13 +17,16 @@ import ( func TestAccAWSRDSCluster_basic(t *testing.T) { var v rds.DBCluster + ri := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + config := fmt.Sprintf(testAccAWSClusterConfig, ri) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSClusterDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSClusterConfig, + Config: config, Check: resource.ComposeTestCheckFunc( testAccCheckAWSClusterExists("aws_rds_cluster.default", &v), resource.TestCheckResourceAttr( @@ -38,6 +41,47 @@ func TestAccAWSRDSCluster_basic(t *testing.T) { }) } +func TestAccAWSRDSCluster_update(t *testing.T) { + var v rds.DBCluster + + ri := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + preConfig := fmt.Sprintf(testAccAWSClusterConfig, ri) + postConfig := fmt.Sprintf(testAccAWSClusterConfig_update, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSClusterDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSClusterExists("aws_rds_cluster.default", &v), + resource.TestCheckResourceAttr( + "aws_rds_cluster.default", "preferred_backup_window", "07:00-09:00"), + resource.TestCheckResourceAttr( + "aws_rds_cluster.default", "backup_retention_period", "5"), + resource.TestCheckResourceAttr( + "aws_rds_cluster.default", "preferred_maintenance_window", "tue:04:00-tue:04:30"), + ), + }, + + resource.TestStep{ + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSClusterExists("aws_rds_cluster.default", &v), + resource.TestCheckResourceAttr( + "aws_rds_cluster.default", "preferred_backup_window", "03:00-09:00"), + resource.TestCheckResourceAttr( + "aws_rds_cluster.default", "backup_retention_period", "10"), + resource.TestCheckResourceAttr( + "aws_rds_cluster.default", "preferred_maintenance_window", "wed:01:00-wed:01:30"), + ), + }, + }, + }) +} + func testAccCheckAWSClusterDestroy(s *terraform.State) error { for _, rs := range s.RootModule().Resources { if rs.Type != "aws_rds_cluster" { @@ -104,7 +148,7 @@ func testAccCheckAWSClusterExists(n string, v *rds.DBCluster) resource.TestCheck } // Add some random to the name, to avoid collision -var testAccAWSClusterConfig = fmt.Sprintf(` +var testAccAWSClusterConfig = ` resource "aws_rds_cluster" "default" { cluster_identifier = "tf-aurora-cluster-%d" availability_zones = ["us-west-2a","us-west-2b","us-west-2c"] @@ -114,4 +158,18 @@ resource "aws_rds_cluster" "default" { backup_retention_period = 5 preferred_backup_window = "07:00-09:00" preferred_maintenance_window = "tue:04:00-tue:04:30" -}`, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) +}` + +// Add some random to the name, to avoid collision +var testAccAWSClusterConfig_update = ` +resource "aws_rds_cluster" "default" { + cluster_identifier = "tf-aurora-cluster-%d" + availability_zones = ["us-west-2a","us-west-2b","us-west-2c"] + database_name = "mydb" + master_username = "foo" + master_password = "mustbeeightcharaters" + backup_retention_period = 10 + preferred_backup_window = "03:00-09:00" + preferred_maintenance_window = "wed:01:00-wed:01:30" + apply_immediately = true +}` From bf11be82c8ee131ddc5c001f3556817b99e7764f Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Wed, 4 Nov 2015 15:20:52 -0600 Subject: [PATCH 21/95] provider/digitalocean: enhance user_data test Checks to ensure that the droplet is recreated. Commenting out `ForceNew` on `user_data` fails the test now. /cc @stack72 --- .../resource_digitalocean_droplet_test.go | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/builtin/providers/digitalocean/resource_digitalocean_droplet_test.go b/builtin/providers/digitalocean/resource_digitalocean_droplet_test.go index 7f85034cb..d3a37a82c 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_droplet_test.go +++ b/builtin/providers/digitalocean/resource_digitalocean_droplet_test.go @@ -72,7 +72,7 @@ func TestAccDigitalOceanDroplet_Update(t *testing.T) { } func TestAccDigitalOceanDroplet_UpdateUserData(t *testing.T) { - var droplet godo.Droplet + var afterCreate, afterUpdate godo.Droplet resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -82,19 +82,21 @@ func TestAccDigitalOceanDroplet_UpdateUserData(t *testing.T) { resource.TestStep{ Config: testAccCheckDigitalOceanDropletConfig_basic, Check: resource.ComposeTestCheckFunc( - testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &droplet), - testAccCheckDigitalOceanDropletAttributes(&droplet), + testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &afterCreate), + testAccCheckDigitalOceanDropletAttributes(&afterCreate), ), }, resource.TestStep{ Config: testAccCheckDigitalOceanDropletConfig_userdata_update, Check: resource.ComposeTestCheckFunc( - testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &droplet), + testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &afterUpdate), resource.TestCheckResourceAttr( "digitalocean_droplet.foobar", "user_data", "foobar foobar"), + testAccCheckDigitalOceanDropletRecreated( + t, &afterCreate, &afterUpdate), ), }, }, @@ -263,6 +265,16 @@ func testAccCheckDigitalOceanDropletExists(n string, droplet *godo.Droplet) reso } } +func testAccCheckDigitalOceanDropletRecreated(t *testing.T, + before, after *godo.Droplet) resource.TestCheckFunc { + return func(s *terraform.State) error { + if before.ID == after.ID { + t.Fatalf("Expected change of droplet IDs, but both were %v", before.ID) + } + return nil + } +} + // Not sure if this check should remain here as the underlaying // function is changed and is tested indirectly by almost all // other test already From 409df4866d8a05293ba3d1c87aee18684a8d12a8 Mon Sep 17 00:00:00 2001 From: stack72 Date: Thu, 5 Nov 2015 10:25:01 +0000 Subject: [PATCH 22/95] Changes after the feedback from @catsby - these all made perfect sense --- .../providers/aws/resource_aws_rds_cluster.go | 5 +++ .../aws/resource_aws_rds_cluster_test.go | 40 ++++++++++++++++--- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/builtin/providers/aws/resource_aws_rds_cluster.go b/builtin/providers/aws/resource_aws_rds_cluster.go index ece9b686f..6dac39f0e 100644 --- a/builtin/providers/aws/resource_aws_rds_cluster.go +++ b/builtin/providers/aws/resource_aws_rds_cluster.go @@ -127,12 +127,17 @@ func resourceAwsRDSCluster() *schema.Resource { "preferred_backup_window": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, }, "preferred_maintenance_window": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, StateFunc: func(val interface{}) string { + if val == nil { + return "" + } return strings.ToLower(val.(string)) }, }, diff --git a/builtin/providers/aws/resource_aws_rds_cluster_test.go b/builtin/providers/aws/resource_aws_rds_cluster_test.go index a9eed687e..d0d285362 100644 --- a/builtin/providers/aws/resource_aws_rds_cluster_test.go +++ b/builtin/providers/aws/resource_aws_rds_cluster_test.go @@ -20,6 +20,27 @@ func TestAccAWSRDSCluster_basic(t *testing.T) { ri := rand.New(rand.NewSource(time.Now().UnixNano())).Int() config := fmt.Sprintf(testAccAWSClusterConfig, ri) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSClusterDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: config, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSClusterExists("aws_rds_cluster.default", &v), + ), + }, + }, + }) +} + +func TestAccAWSRDSCluster_backups(t *testing.T) { + var v rds.DBCluster + + ri := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + config := fmt.Sprintf(testAccAWSClusterConfig_backups, ri) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -41,12 +62,12 @@ func TestAccAWSRDSCluster_basic(t *testing.T) { }) } -func TestAccAWSRDSCluster_update(t *testing.T) { +func TestAccAWSRDSCluster_backupsUpdate(t *testing.T) { var v rds.DBCluster ri := rand.New(rand.NewSource(time.Now().UnixNano())).Int() - preConfig := fmt.Sprintf(testAccAWSClusterConfig, ri) - postConfig := fmt.Sprintf(testAccAWSClusterConfig_update, ri) + preConfig := fmt.Sprintf(testAccAWSClusterConfig_backups, ri) + postConfig := fmt.Sprintf(testAccAWSClusterConfig_backupsUpdate, ri) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -147,8 +168,16 @@ func testAccCheckAWSClusterExists(n string, v *rds.DBCluster) resource.TestCheck } } -// Add some random to the name, to avoid collision var testAccAWSClusterConfig = ` +resource "aws_rds_cluster" "default" { + cluster_identifier = "tf-aurora-cluster-%d" + availability_zones = ["us-west-2a","us-west-2b","us-west-2c"] + database_name = "mydb" + master_username = "foo" + master_password = "mustbeeightcharaters" +}` + +var testAccAWSClusterConfig_backups = ` resource "aws_rds_cluster" "default" { cluster_identifier = "tf-aurora-cluster-%d" availability_zones = ["us-west-2a","us-west-2b","us-west-2c"] @@ -160,8 +189,7 @@ resource "aws_rds_cluster" "default" { preferred_maintenance_window = "tue:04:00-tue:04:30" }` -// Add some random to the name, to avoid collision -var testAccAWSClusterConfig_update = ` +var testAccAWSClusterConfig_backupsUpdate = ` resource "aws_rds_cluster" "default" { cluster_identifier = "tf-aurora-cluster-%d" availability_zones = ["us-west-2a","us-west-2b","us-west-2c"] From ca2ea80af36ddd1907c0daa8bcd74e4985301145 Mon Sep 17 00:00:00 2001 From: stack72 Date: Thu, 5 Nov 2015 12:23:07 +0000 Subject: [PATCH 23/95] Making the changes to the snapshotting for Elasticache Redis as per @catsby's findings --- .../aws/resource_aws_elasticache_cluster.go | 37 +++++++++++-------- .../resource_aws_elasticache_cluster_test.go | 34 ++++++++++------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/builtin/providers/aws/resource_aws_elasticache_cluster.go b/builtin/providers/aws/resource_aws_elasticache_cluster.go index a2c312d35..69777a866 100644 --- a/builtin/providers/aws/resource_aws_elasticache_cluster.go +++ b/builtin/providers/aws/resource_aws_elasticache_cluster.go @@ -205,12 +205,14 @@ func resourceAwsElasticacheClusterCreate(d *schema.ResourceData, meta interface{ req.CacheParameterGroupName = aws.String(v.(string)) } - if v, ok := d.GetOk("snapshot_retention_limit"); ok { - req.SnapshotRetentionLimit = aws.Int64(int64(v.(int))) - } + if !strings.Contains(d.Get("node_type").(string), "cache.t2") { + if v, ok := d.GetOk("snapshot_retention_limit"); ok { + req.SnapshotRetentionLimit = aws.Int64(int64(v.(int))) + } - if v, ok := d.GetOk("snapshot_window"); ok { - req.SnapshotWindow = aws.String(v.(string)) + if v, ok := d.GetOk("snapshot_window"); ok { + req.SnapshotWindow = aws.String(v.(string)) + } } if v, ok := d.GetOk("maintenance_window"); ok { @@ -287,10 +289,12 @@ func resourceAwsElasticacheClusterRead(d *schema.ResourceData, meta interface{}) d.Set("security_group_ids", c.SecurityGroups) d.Set("parameter_group_name", c.CacheParameterGroup) d.Set("maintenance_window", c.PreferredMaintenanceWindow) - log.Printf("[INFO] Found %s as the Snapshow Window", *c.SnapshotWindow) - d.Set("snapshot_window", c.SnapshotWindow) - log.Printf("[INFO] Found %d as the Snapshow Retention Limit", *c.SnapshotRetentionLimit) - d.Set("snapshot_retention_limit", c.SnapshotRetentionLimit) + if c.SnapshotWindow != nil { + d.Set("snapshot_window", c.SnapshotWindow) + } + if c.SnapshotRetentionLimit != nil { + d.Set("snapshot_retention_limit", c.SnapshotRetentionLimit) + } if c.NotificationConfiguration != nil { if *c.NotificationConfiguration.TopicStatus == "active" { d.Set("notification_topic_arn", c.NotificationConfiguration.TopicArn) @@ -373,13 +377,16 @@ func resourceAwsElasticacheClusterUpdate(d *schema.ResourceData, meta interface{ req.EngineVersion = aws.String(d.Get("engine_version").(string)) requestUpdate = true } + if !strings.Contains(d.Get("node_type").(string), "cache.t2") { + if d.HasChange("snapshot_window") { + req.SnapshotWindow = aws.String(d.Get("snapshot_window").(string)) + requestUpdate = true + } - if d.HasChange("snapshot_window") { - req.SnapshotWindow = aws.String(d.Get("snapshot_window").(string)) - } - - if d.HasChange("snapshot_retention_limit") { - req.SnapshotRetentionLimit = aws.Int64(int64(d.Get("snapshot_retention_limit").(int))) + if d.HasChange("snapshot_retention_limit") { + req.SnapshotRetentionLimit = aws.Int64(int64(d.Get("snapshot_retention_limit").(int))) + requestUpdate = true + } } if d.HasChange("num_cache_nodes") { diff --git a/builtin/providers/aws/resource_aws_elasticache_cluster_test.go b/builtin/providers/aws/resource_aws_elasticache_cluster_test.go index 666be2c8a..78e28763d 100644 --- a/builtin/providers/aws/resource_aws_elasticache_cluster_test.go +++ b/builtin/providers/aws/resource_aws_elasticache_cluster_test.go @@ -35,13 +35,17 @@ func TestAccAWSElasticacheCluster_basic(t *testing.T) { func TestAccAWSElasticacheCluster_snapshots(t *testing.T) { var ec elasticache.CacheCluster + + ri := genRandInt() + config := fmt.Sprintf(testAccAWSElasticacheClusterConfig_snapshots, ri) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSElasticacheClusterDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSElasticacheClusterConfig_snapshots, + Config: config, Check: resource.ComposeTestCheckFunc( testAccCheckAWSElasticacheSecurityGroupExists("aws_elasticache_security_group.bar"), testAccCheckAWSElasticacheClusterExists("aws_elasticache_cluster.bar", &ec), @@ -56,13 +60,18 @@ func TestAccAWSElasticacheCluster_snapshots(t *testing.T) { } func TestAccAWSElasticacheCluster_snapshotsWithUpdates(t *testing.T) { var ec elasticache.CacheCluster + + ri := genRandInt() + preConfig := fmt.Sprintf(testAccAWSElasticacheClusterConfig_snapshots, ri) + postConfig := fmt.Sprintf(testAccAWSElasticacheClusterConfig_snapshotsUpdated, ri) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSElasticacheClusterDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSElasticacheClusterConfig_snapshots, + Config: preConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSElasticacheSecurityGroupExists("aws_elasticache_security_group.bar"), testAccCheckAWSElasticacheClusterExists("aws_elasticache_cluster.bar", &ec), @@ -74,7 +83,7 @@ func TestAccAWSElasticacheCluster_snapshotsWithUpdates(t *testing.T) { }, resource.TestStep{ - Config: testAccAWSElasticacheClusterConfig_snapshotsUpdated, + Config: postConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSElasticacheSecurityGroupExists("aws_elasticache_security_group.bar"), testAccCheckAWSElasticacheClusterExists("aws_elasticache_cluster.bar", &ec), @@ -204,17 +213,15 @@ resource "aws_elasticache_cluster" "bar" { port = 11211 parameter_group_name = "default.memcached1.4" security_group_names = ["${aws_elasticache_security_group.bar.name}"] - snapshot_window = "05:00-09:00" - snapshot_retention_limit = 3 } `, genRandInt(), genRandInt(), genRandInt()) -var testAccAWSElasticacheClusterConfig_snapshots = fmt.Sprintf(` +var testAccAWSElasticacheClusterConfig_snapshots = ` provider "aws" { region = "us-east-1" } resource "aws_security_group" "bar" { - name = "tf-test-security-group-%03d" + name = "tf-test-security-group" description = "tf-test-security-group-descr" ingress { from_port = -1 @@ -225,7 +232,7 @@ resource "aws_security_group" "bar" { } resource "aws_elasticache_security_group" "bar" { - name = "tf-test-security-group-%03d" + name = "tf-test-security-group" description = "tf-test-security-group-descr" security_group_names = ["${aws_security_group.bar.name}"] } @@ -241,14 +248,14 @@ resource "aws_elasticache_cluster" "bar" { snapshot_window = "05:00-09:00" snapshot_retention_limit = 3 } -`, genRandInt(), genRandInt(), genRandInt()) +` -var testAccAWSElasticacheClusterConfig_snapshotsUpdated = fmt.Sprintf(` +var testAccAWSElasticacheClusterConfig_snapshotsUpdated = ` provider "aws" { region = "us-east-1" } resource "aws_security_group" "bar" { - name = "tf-test-security-group-%03d" + name = "tf-test-security-group" description = "tf-test-security-group-descr" ingress { from_port = -1 @@ -259,7 +266,7 @@ resource "aws_security_group" "bar" { } resource "aws_elasticache_security_group" "bar" { - name = "tf-test-security-group-%03d" + name = "tf-test-security-group" description = "tf-test-security-group-descr" security_group_names = ["${aws_security_group.bar.name}"] } @@ -274,8 +281,9 @@ resource "aws_elasticache_cluster" "bar" { security_group_names = ["${aws_elasticache_security_group.bar.name}"] snapshot_window = "07:00-09:00" snapshot_retention_limit = 7 + apply_immediately = true } -`, genRandInt(), genRandInt(), genRandInt()) +` var testAccAWSElasticacheClusterInVPCConfig = fmt.Sprintf(` resource "aws_vpc" "foo" { From 032a42797ea8b41bc29cade1223716284259f30b Mon Sep 17 00:00:00 2001 From: stack72 Date: Thu, 5 Nov 2015 15:01:07 +0000 Subject: [PATCH 24/95] Fixing the DigitalOcean Droplet 404 potential on refresh of state --- builtin/providers/digitalocean/resource_digitalocean_droplet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/digitalocean/resource_digitalocean_droplet.go b/builtin/providers/digitalocean/resource_digitalocean_droplet.go index ef25b0074..decf94476 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_droplet.go +++ b/builtin/providers/digitalocean/resource_digitalocean_droplet.go @@ -189,7 +189,7 @@ func resourceDigitalOceanDropletRead(d *schema.ResourceData, meta interface{}) e droplet, _, err := client.Droplets.Get(id) if err != nil { // check if the droplet no longer exists. - if err.Error() == "Error retrieving droplet: API Error: 404 Not Found" { + if strings.Contains(err.Error(), "404 The resource you were accessing could not be found") { d.SetId("") return nil } From 2504cb46246d7cac8ce9d1f08fb002337b1fedbc Mon Sep 17 00:00:00 2001 From: stack72 Date: Thu, 5 Nov 2015 16:33:29 +0000 Subject: [PATCH 25/95] Changing the DigitalOcean Droplet 404 from a message string to an actual 404 status code --- .../providers/digitalocean/resource_digitalocean_droplet.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/providers/digitalocean/resource_digitalocean_droplet.go b/builtin/providers/digitalocean/resource_digitalocean_droplet.go index decf94476..a4f80015b 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_droplet.go +++ b/builtin/providers/digitalocean/resource_digitalocean_droplet.go @@ -186,10 +186,10 @@ func resourceDigitalOceanDropletRead(d *schema.ResourceData, meta interface{}) e } // Retrieve the droplet properties for updating the state - droplet, _, err := client.Droplets.Get(id) + droplet, resp, err := client.Droplets.Get(id) if err != nil { // check if the droplet no longer exists. - if strings.Contains(err.Error(), "404 The resource you were accessing could not be found") { + if resp.StatusCode == 404 { d.SetId("") return nil } From 9cee18b3de8ea63c5403cc6176f501ea4aaf33bb Mon Sep 17 00:00:00 2001 From: stack72 Date: Thu, 5 Nov 2015 14:31:55 +0000 Subject: [PATCH 26/95] ElastiCache cluster read tolerates removed cluster. Previously it would fail if a Terraform-managed ElastiCache cluster were deleted outside of Terraform. Now it marks it as deleted in the state so that Terraform can know it doesn't need to be destroyed, and can potentially recreate it if asked. --- builtin/providers/aws/resource_aws_elasticache_cluster.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/builtin/providers/aws/resource_aws_elasticache_cluster.go b/builtin/providers/aws/resource_aws_elasticache_cluster.go index 3460fb292..f2410dcd5 100644 --- a/builtin/providers/aws/resource_aws_elasticache_cluster.go +++ b/builtin/providers/aws/resource_aws_elasticache_cluster.go @@ -241,6 +241,12 @@ func resourceAwsElasticacheClusterRead(d *schema.ResourceData, meta interface{}) res, err := conn.DescribeCacheClusters(req) if err != nil { + if eccErr, ok := err.(awserr.Error); ok && eccErr.Code() == "CacheClusterNotFound" { + log.Printf("[WARN] ElastiCache Cluster (%s) not found", d.Id()) + d.SetId("") + return nil + } + return err } From 6a811e2e4f075b645249ecfb2512e0266b602e8e Mon Sep 17 00:00:00 2001 From: stack72 Date: Thu, 5 Nov 2015 17:32:57 +0000 Subject: [PATCH 27/95] Logging that the DO droplet wasn't found before removing it --- builtin/providers/digitalocean/resource_digitalocean_droplet.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/providers/digitalocean/resource_digitalocean_droplet.go b/builtin/providers/digitalocean/resource_digitalocean_droplet.go index a4f80015b..050577854 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_droplet.go +++ b/builtin/providers/digitalocean/resource_digitalocean_droplet.go @@ -190,6 +190,7 @@ func resourceDigitalOceanDropletRead(d *schema.ResourceData, meta interface{}) e if err != nil { // check if the droplet no longer exists. if resp.StatusCode == 404 { + log.Printf("[WARN] DigitalOcean Droplet (%s) not found", d.Id()) d.SetId("") return nil } From 99f3c0580b8aa0005d90f8ff5923e70a4b999c3a Mon Sep 17 00:00:00 2001 From: Clint Date: Thu, 5 Nov 2015 11:38:18 -0600 Subject: [PATCH 28/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51103570d..6fc1082b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ BUG FIXES: * provider/aws: Tolerate ElastiCache clusters being deleted outside Terraform [GH-3767] * 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] * provider/openstack: Fix several issues causing unresolvable diffs [GH-3440] * provider/openstack: Safely delete security groups [GH-3696] * provider/openstack: Ignore order of security_groups in instance [GH-3651] From 3a63b48f97d1ff917fe125eef56e1c76b3c9383f Mon Sep 17 00:00:00 2001 From: Kirill Shirinkin Date: Thu, 5 Nov 2015 19:23:05 +0100 Subject: [PATCH 29/95] provider/openstack: fixed_ips implementation for ports --- ...ce_openstack_networking_network_v2_test.go | 5 ++- .../resource_openstack_networking_port_v2.go | 37 +++++++++++++++++++ ...ource_openstack_networking_port_v2_test.go | 4 ++ .../r/networking_network_v2.html.markdown | 5 ++- .../r/networking_port_v2.html.markdown | 11 ++++++ 5 files changed, 60 insertions(+), 2 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go index faa007e32..6efdf3daf 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go @@ -150,7 +150,10 @@ func TestAccNetworkingV2Network_fullstack(t *testing.T) { admin_state_up = "true" security_groups = ["${openstack_compute_secgroup_v2.foo.id}"] - depends_on = ["openstack_networking_subnet_v2.foo"] + fixed_ips { + "subnet_id" = "008ba151-0b8c-4a67-98b5-0d2b87666062" + "ip_address" = "172.24.4.2" + } } resource "openstack_compute_instance_v2" "foo" { diff --git a/builtin/providers/openstack/resource_openstack_networking_port_v2.go b/builtin/providers/openstack/resource_openstack_networking_port_v2.go index 701e42c05..318f0097b 100644 --- a/builtin/providers/openstack/resource_openstack_networking_port_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_port_v2.go @@ -78,6 +78,23 @@ func resourceNetworkingPortV2() *schema.Resource { ForceNew: true, Computed: true, }, + "fixed_ips": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: false, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subnet_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "ip_address": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, }, } } @@ -98,6 +115,7 @@ func resourceNetworkingPortV2Create(d *schema.ResourceData, meta interface{}) er DeviceOwner: d.Get("device_owner").(string), SecurityGroups: resourcePortSecurityGroupsV2(d), DeviceID: d.Get("device_id").(string), + FixedIPs: resourcePortFixedIpsV2(d), } log.Printf("[DEBUG] Create Options: %#v", createOpts) @@ -146,6 +164,7 @@ func resourceNetworkingPortV2Read(d *schema.ResourceData, meta interface{}) erro d.Set("device_owner", p.DeviceOwner) d.Set("security_groups", p.SecurityGroups) d.Set("device_id", p.DeviceID) + d.Set("fixed_ips", p.FixedIPs) return nil } @@ -179,6 +198,10 @@ func resourceNetworkingPortV2Update(d *schema.ResourceData, meta interface{}) er updateOpts.DeviceID = d.Get("device_id").(string) } + if d.HasChange("fixed_ips") { + updateOpts.FixedIPs = resourcePortFixedIpsV2(d) + } + log.Printf("[DEBUG] Updating Port %s with options: %+v", d.Id(), updateOpts) _, err = ports.Update(networkingClient, d.Id(), updateOpts).Extract() @@ -223,6 +246,20 @@ func resourcePortSecurityGroupsV2(d *schema.ResourceData) []string { return groups } +func resourcePortFixedIpsV2(d *schema.ResourceData) []ports.IP { + rawIP := d.Get("fixed_ips").([]interface{}) + ip := make([]ports.IP, len(rawIP)) + for i, raw := range rawIP { + rawMap := raw.(map[string]interface{}) + ip[i] = ports.IP{ + SubnetID: rawMap["subnet_id"].(string), + IPAddress: rawMap["ip_address"].(string), + } + } + + return ip +} + func resourcePortAdminStateUpV2(d *schema.ResourceData) *bool { value := false diff --git a/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go index edeb61901..1e2a98a77 100644 --- a/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go @@ -30,6 +30,10 @@ func TestAccNetworkingV2Port_basic(t *testing.T) { name = "port_1" network_id = "${openstack_networking_network_v2.foo.id}" admin_state_up = "true" + fixed_ips { + ip_address = "192.168.0.0" + subnet_id = "008ba151-0b8c-4a67-98b5-0d2b87666062" + } }`, region, region) resource.Test(t, resource.TestCase{ diff --git a/website/source/docs/providers/openstack/r/networking_network_v2.html.markdown b/website/source/docs/providers/openstack/r/networking_network_v2.html.markdown index 9a9eab935..ce4f46db8 100644 --- a/website/source/docs/providers/openstack/r/networking_network_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/networking_network_v2.html.markdown @@ -42,7 +42,10 @@ resource "openstack_networking_port_v2" "port_1" { admin_state_up = "true" security_groups = ["${openstack_compute_secgroup_v2.secgroup_1.id}"] - depends_on = ["openstack_networking_subnet_v2.subnet_1"] + fixed_ips { + "subnet_id" = "008ba151-0b8c-4a67-98b5-0d2b87666062" + "ip_address" = "172.24.4.2" + } } resource "openstack_compute_instance_v2" "instance_1" { diff --git a/website/source/docs/providers/openstack/r/networking_port_v2.html.markdown b/website/source/docs/providers/openstack/r/networking_port_v2.html.markdown index d10130a56..df1e1c914 100644 --- a/website/source/docs/providers/openstack/r/networking_port_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/networking_port_v2.html.markdown @@ -60,6 +60,17 @@ The following arguments are supported: * `device_id` - (Optional) The ID of the device attached to the port. Changing this creates a new port. +* `fixed_ips` - (Optional) An array of desired IPs for this port. + + +The `fixed_ips` block supports: + +* `subnet_id` - (Required) Subnet in which to allocate IP address for +this port. + +* `ip_address` - (Required) IP address desired in the subnet for this +port. + ## Attributes Reference The following attributes are exported: From 6a5e591143a3789e050780ffd96ba64ff8891230 Mon Sep 17 00:00:00 2001 From: stack72 Date: Thu, 5 Nov 2015 19:01:41 +0000 Subject: [PATCH 30/95] Removing an unnecessary duplicate test for the RDS Cluster Backups --- .../aws/resource_aws_rds_cluster_test.go | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/builtin/providers/aws/resource_aws_rds_cluster_test.go b/builtin/providers/aws/resource_aws_rds_cluster_test.go index d0d285362..dfb4fcc51 100644 --- a/builtin/providers/aws/resource_aws_rds_cluster_test.go +++ b/builtin/providers/aws/resource_aws_rds_cluster_test.go @@ -35,33 +35,6 @@ func TestAccAWSRDSCluster_basic(t *testing.T) { }) } -func TestAccAWSRDSCluster_backups(t *testing.T) { - var v rds.DBCluster - - ri := rand.New(rand.NewSource(time.Now().UnixNano())).Int() - config := fmt.Sprintf(testAccAWSClusterConfig_backups, ri) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSClusterDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: config, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSClusterExists("aws_rds_cluster.default", &v), - resource.TestCheckResourceAttr( - "aws_rds_cluster.default", "preferred_backup_window", "07:00-09:00"), - resource.TestCheckResourceAttr( - "aws_rds_cluster.default", "backup_retention_period", "5"), - resource.TestCheckResourceAttr( - "aws_rds_cluster.default", "preferred_maintenance_window", "tue:04:00-tue:04:30"), - ), - }, - }, - }) -} - func TestAccAWSRDSCluster_backupsUpdate(t *testing.T) { var v rds.DBCluster From e6c4a4fd1155a29bbfbe0398deb14858af0941cd Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Thu, 5 Nov 2015 13:07:08 -0600 Subject: [PATCH 31/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fc1082b0..4e92937ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ IMPROVEMENTS: * provider/aws: Making the AutoScalingGroup name optional [GH-3710] * provider/openstack: Add "delete on termination" boot-from-volume option [GH-3232] * provider/digitalocean: Make user_data force a new droplet [GH-3740] + * provider/vsphere: Do not add network interfaces by default [GH-3652] BUG FIXES: From 364b297700a4837802023361c3861e7ddc5d8b23 Mon Sep 17 00:00:00 2001 From: Clint Date: Thu, 5 Nov 2015 14:29:23 -0600 Subject: [PATCH 32/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e92937ee..8c8fb36f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ IMPROVEMENTS: * provider/aws: Add `kinesis_endpoint` for configuring Kinesis [GH-3255] * provider/aws: Add a computed ARN for S3 Buckets [GH-3685] * provider/aws: Add configuration to enable copying RDS tags to final snapshot [GH-3529] + * provider/aws: RDS Cluster additions (`backup_retention_period`, `preferred_backup_window`, `preferred_maintenance_window`) [GH-3757] * provider/openstack: Use IPv4 as the defeault IP version for subnets [GH-3091] * provider/aws: Apply security group after restoring db_instance from snapshot [GH-3513] * provider/aws: Making the AutoScalingGroup name optional [GH-3710] From 274781224ee8cb1b4b8c9f6dffa299201680fcc8 Mon Sep 17 00:00:00 2001 From: clint shryock Date: Thu, 5 Nov 2015 15:25:04 -0600 Subject: [PATCH 33/95] provider/aws: fix panic with SNS topic policy if omitted --- builtin/providers/aws/resource_aws_sns_topic.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/builtin/providers/aws/resource_aws_sns_topic.go b/builtin/providers/aws/resource_aws_sns_topic.go index 26e98a9b1..b0d6fecf5 100644 --- a/builtin/providers/aws/resource_aws_sns_topic.go +++ b/builtin/providers/aws/resource_aws_sns_topic.go @@ -44,13 +44,12 @@ func resourceAwsSnsTopic() *schema.Resource { "policy": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: false, Computed: true, StateFunc: func(v interface{}) string { - jsonb := []byte{} - if v != nil { - jsonb = []byte(v.(string)) + if v == nil { + return "" } + jsonb := []byte{} buffer := new(bytes.Buffer) if err := json.Compact(buffer, jsonb); err != nil { log.Printf("[WARN] Error compacting JSON for Policy in SNS Topic") From 9390674a05edf53cf2e08084f0f602b323788191 Mon Sep 17 00:00:00 2001 From: clint shryock Date: Thu, 5 Nov 2015 16:43:49 -0600 Subject: [PATCH 34/95] providers/aws: Provide source security group id for ELBs --- builtin/providers/aws/resource_aws_elb.go | 67 +++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/builtin/providers/aws/resource_aws_elb.go b/builtin/providers/aws/resource_aws_elb.go index 9955c7cf0..6be03b112 100644 --- a/builtin/providers/aws/resource_aws_elb.go +++ b/builtin/providers/aws/resource_aws_elb.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/elb" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" @@ -74,6 +75,11 @@ func resourceAwsElb() *schema.Resource { Computed: true, }, + "source_security_group_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "subnets": &schema.Schema{ Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, @@ -300,6 +306,18 @@ func resourceAwsElbRead(d *schema.ResourceData, meta interface{}) error { d.Set("security_groups", lb.SecurityGroups) if lb.SourceSecurityGroup != nil { d.Set("source_security_group", lb.SourceSecurityGroup.GroupName) + + // Manually look up the ELB Security Group ID, since it's not provided + var elbVpc string + if lb.VPCId != nil { + elbVpc = *lb.VPCId + } + sgId, err := sourceSGIdByName(meta, *lb.SourceSecurityGroup.GroupName, elbVpc) + if err != nil { + log.Printf("[WARN] Error looking up ELB Security Group ID: %s", err) + } else { + d.Set("source_security_group_id", sgId) + } } d.Set("subnets", lb.Subnets) d.Set("idle_timeout", lbAttrs.ConnectionSettings.IdleTimeout) @@ -594,3 +612,52 @@ func validateElbName(v interface{}, k string) (ws []string, errors []error) { return } + +func sourceSGIdByName(meta interface{}, sg, vpcId string) (string, error) { + conn := meta.(*AWSClient).ec2conn + var filters []*ec2.Filter + var sgFilterName, sgFilterVPCID *ec2.Filter + sgFilterName = &ec2.Filter{ + Name: aws.String("group-name"), + Values: []*string{aws.String(sg)}, + } + + if vpcId != "" { + sgFilterVPCID = &ec2.Filter{ + Name: aws.String("vpc-id"), + Values: []*string{aws.String(vpcId)}, + } + } + + filters = append(filters, sgFilterName) + + if sgFilterVPCID != nil { + filters = append(filters, sgFilterVPCID) + } + + req := &ec2.DescribeSecurityGroupsInput{ + Filters: filters, + } + resp, err := conn.DescribeSecurityGroups(req) + if err != nil { + if ec2err, ok := err.(awserr.Error); ok { + if ec2err.Code() == "InvalidSecurityGroupID.NotFound" || + ec2err.Code() == "InvalidGroup.NotFound" { + resp = nil + err = nil + } + } + + if err != nil { + log.Printf("Error on ELB SG look up: %s", err) + return "", err + } + } + + if resp == nil || len(resp.SecurityGroups) == 0 { + return "", fmt.Errorf("No security groups found for name %s and vpc id %s", sg, vpcId) + } + + group := resp.SecurityGroups[0] + return *group.GroupId, nil +} From d5e6929118c3291081a4ba282e8d3f9adab933d7 Mon Sep 17 00:00:00 2001 From: Kirill Shirinkin Date: Fri, 6 Nov 2015 11:10:44 +0100 Subject: [PATCH 35/95] Fix tests --- ...urce_openstack_networking_network_v2_test.go | 4 ++-- ...esource_openstack_networking_port_v2_test.go | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go index 6efdf3daf..32a0c96be 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go @@ -151,8 +151,8 @@ func TestAccNetworkingV2Network_fullstack(t *testing.T) { security_groups = ["${openstack_compute_secgroup_v2.foo.id}"] fixed_ips { - "subnet_id" = "008ba151-0b8c-4a67-98b5-0d2b87666062" - "ip_address" = "172.24.4.2" + "subnet_id" = "${openstack_networking_subnet_v2.foo.id}" + "ip_address" = "192.168.199.23" } } diff --git a/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go index 1e2a98a77..c1e100d4f 100644 --- a/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go @@ -10,6 +10,7 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/networks" "github.com/rackspace/gophercloud/openstack/networking/v2/ports" + "github.com/rackspace/gophercloud/openstack/networking/v2/subnets" ) func TestAccNetworkingV2Port_basic(t *testing.T) { @@ -17,6 +18,7 @@ func TestAccNetworkingV2Port_basic(t *testing.T) { var network networks.Network var port ports.Port + var subnet subnets.Subnet var testAccNetworkingV2Port_basic = fmt.Sprintf(` resource "openstack_networking_network_v2" "foo" { @@ -25,16 +27,24 @@ func TestAccNetworkingV2Port_basic(t *testing.T) { admin_state_up = "true" } + resource "openstack_networking_subnet_v2" "foo" { + region = "%s" + name = "subnet_1" + network_id = "${openstack_networking_network_v2.foo.id}" + cidr = "192.168.199.0/24" + ip_version = 4 + } + resource "openstack_networking_port_v2" "foo" { region = "%s" name = "port_1" network_id = "${openstack_networking_network_v2.foo.id}" admin_state_up = "true" fixed_ips { - ip_address = "192.168.0.0" - subnet_id = "008ba151-0b8c-4a67-98b5-0d2b87666062" + subnet_id = "${openstack_networking_subnet_v2.foo.id}" + ip_address = "192.168.199.23" } - }`, region, region) + }`, region, region, region) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -44,6 +54,7 @@ func TestAccNetworkingV2Port_basic(t *testing.T) { resource.TestStep{ Config: testAccNetworkingV2Port_basic, Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkingV2SubnetExists(t, "openstack_networking_subnet_v2.foo", &subnet), testAccCheckNetworkingV2NetworkExists(t, "openstack_networking_network_v2.foo", &network), testAccCheckNetworkingV2PortExists(t, "openstack_networking_port_v2.foo", &port), ), From 350f91ec063b0057ea7fd37ef87227ca6225ad3a Mon Sep 17 00:00:00 2001 From: stack72 Date: Fri, 6 Nov 2015 11:16:51 +0000 Subject: [PATCH 36/95] Removing the instance_type check in the ElastiCache cluster creation. We now allow the error to bubble up to the userr when the wrong instance type is used. The limitation for t2 instance types now allowing snapshotting is also now documented --- .../aws/resource_aws_elasticache_cluster.go | 37 ++++++++----------- .../resource_aws_elasticache_cluster_test.go | 35 +++--------------- .../aws/r/elasticache_cluster.html.markdown | 1 + 3 files changed, 21 insertions(+), 52 deletions(-) diff --git a/builtin/providers/aws/resource_aws_elasticache_cluster.go b/builtin/providers/aws/resource_aws_elasticache_cluster.go index 69777a866..6f178b71e 100644 --- a/builtin/providers/aws/resource_aws_elasticache_cluster.go +++ b/builtin/providers/aws/resource_aws_elasticache_cluster.go @@ -205,14 +205,12 @@ func resourceAwsElasticacheClusterCreate(d *schema.ResourceData, meta interface{ req.CacheParameterGroupName = aws.String(v.(string)) } - if !strings.Contains(d.Get("node_type").(string), "cache.t2") { - if v, ok := d.GetOk("snapshot_retention_limit"); ok { - req.SnapshotRetentionLimit = aws.Int64(int64(v.(int))) - } + if v, ok := d.GetOk("snapshot_retention_limit"); ok { + req.SnapshotRetentionLimit = aws.Int64(int64(v.(int))) + } - if v, ok := d.GetOk("snapshot_window"); ok { - req.SnapshotWindow = aws.String(v.(string)) - } + if v, ok := d.GetOk("snapshot_window"); ok { + req.SnapshotWindow = aws.String(v.(string)) } if v, ok := d.GetOk("maintenance_window"); ok { @@ -289,12 +287,8 @@ func resourceAwsElasticacheClusterRead(d *schema.ResourceData, meta interface{}) d.Set("security_group_ids", c.SecurityGroups) d.Set("parameter_group_name", c.CacheParameterGroup) d.Set("maintenance_window", c.PreferredMaintenanceWindow) - if c.SnapshotWindow != nil { - d.Set("snapshot_window", c.SnapshotWindow) - } - if c.SnapshotRetentionLimit != nil { - d.Set("snapshot_retention_limit", c.SnapshotRetentionLimit) - } + d.Set("snapshot_window", c.SnapshotWindow) + d.Set("snapshot_retention_limit", c.SnapshotRetentionLimit) if c.NotificationConfiguration != nil { if *c.NotificationConfiguration.TopicStatus == "active" { d.Set("notification_topic_arn", c.NotificationConfiguration.TopicArn) @@ -377,16 +371,15 @@ func resourceAwsElasticacheClusterUpdate(d *schema.ResourceData, meta interface{ req.EngineVersion = aws.String(d.Get("engine_version").(string)) requestUpdate = true } - if !strings.Contains(d.Get("node_type").(string), "cache.t2") { - if d.HasChange("snapshot_window") { - req.SnapshotWindow = aws.String(d.Get("snapshot_window").(string)) - requestUpdate = true - } - if d.HasChange("snapshot_retention_limit") { - req.SnapshotRetentionLimit = aws.Int64(int64(d.Get("snapshot_retention_limit").(int))) - requestUpdate = true - } + if d.HasChange("snapshot_window") { + req.SnapshotWindow = aws.String(d.Get("snapshot_window").(string)) + requestUpdate = true + } + + if d.HasChange("snapshot_retention_limit") { + req.SnapshotRetentionLimit = aws.Int64(int64(d.Get("snapshot_retention_limit").(int))) + requestUpdate = true } if d.HasChange("num_cache_nodes") { diff --git a/builtin/providers/aws/resource_aws_elasticache_cluster_test.go b/builtin/providers/aws/resource_aws_elasticache_cluster_test.go index 78e28763d..0620ef47b 100644 --- a/builtin/providers/aws/resource_aws_elasticache_cluster_test.go +++ b/builtin/providers/aws/resource_aws_elasticache_cluster_test.go @@ -33,37 +33,12 @@ func TestAccAWSElasticacheCluster_basic(t *testing.T) { }) } -func TestAccAWSElasticacheCluster_snapshots(t *testing.T) { - var ec elasticache.CacheCluster - - ri := genRandInt() - config := fmt.Sprintf(testAccAWSElasticacheClusterConfig_snapshots, ri) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSElasticacheClusterDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: config, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSElasticacheSecurityGroupExists("aws_elasticache_security_group.bar"), - testAccCheckAWSElasticacheClusterExists("aws_elasticache_cluster.bar", &ec), - resource.TestCheckResourceAttr( - "aws_elasticache_cluster.bar", "snapshot_window", "05:00-09:00"), - resource.TestCheckResourceAttr( - "aws_elasticache_cluster.bar", "snapshot_retention_limit", "3"), - ), - }, - }, - }) -} func TestAccAWSElasticacheCluster_snapshotsWithUpdates(t *testing.T) { var ec elasticache.CacheCluster ri := genRandInt() - preConfig := fmt.Sprintf(testAccAWSElasticacheClusterConfig_snapshots, ri) - postConfig := fmt.Sprintf(testAccAWSElasticacheClusterConfig_snapshotsUpdated, ri) + preConfig := fmt.Sprintf(testAccAWSElasticacheClusterConfig_snapshots, ri, ri, ri) + postConfig := fmt.Sprintf(testAccAWSElasticacheClusterConfig_snapshotsUpdated, ri, ri, ri) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -221,7 +196,7 @@ provider "aws" { region = "us-east-1" } resource "aws_security_group" "bar" { - name = "tf-test-security-group" + name = "tf-test-security-group-%03d" description = "tf-test-security-group-descr" ingress { from_port = -1 @@ -232,7 +207,7 @@ resource "aws_security_group" "bar" { } resource "aws_elasticache_security_group" "bar" { - name = "tf-test-security-group" + name = "tf-test-security-group-%03d" description = "tf-test-security-group-descr" security_group_names = ["${aws_security_group.bar.name}"] } @@ -240,7 +215,7 @@ resource "aws_elasticache_security_group" "bar" { resource "aws_elasticache_cluster" "bar" { cluster_id = "tf-test-%03d" engine = "redis" - node_type = "cache.m1.small" + node_type = "cache.t2.small" num_cache_nodes = 1 port = 6379 parameter_group_name = "default.redis2.8" 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 4a4cb4d76..e39d6172a 100644 --- a/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown +++ b/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown @@ -88,6 +88,7 @@ SNS topic to send ElastiCache notifications to. Example: * `tags` - (Optional) A mapping of tags to assign to the resource. +~> **NOTE:** Snapshotting functionality is not compatible with t2 instance types. ## Attributes Reference From e8a411ce5f1b21211080eb5a2abe22111b5a4801 Mon Sep 17 00:00:00 2001 From: stack72 Date: Fri, 6 Nov 2015 16:10:21 +0000 Subject: [PATCH 37/95] Fixing the RDS Cluster Instance docs to show that either an instance must be set as public OR have a db_subnet_group_name set --- .../docs/providers/aws/r/rds_cluster_instance.html.markdown | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/source/docs/providers/aws/r/rds_cluster_instance.html.markdown b/website/source/docs/providers/aws/r/rds_cluster_instance.html.markdown index 782339a34..0d64a3f87 100644 --- a/website/source/docs/providers/aws/r/rds_cluster_instance.html.markdown +++ b/website/source/docs/providers/aws/r/rds_cluster_instance.html.markdown @@ -64,6 +64,10 @@ and memory, see [Scaling Aurora DB Instances][4]. Aurora currently Default `false`. See the documentation on [Creating DB Instances][6] for more details on controlling this property. +* `db_subnet_group_name` - (Optional) A DB subnet group to associate with this DB instance. + +~> **NOTE:** `db_subnet_group_name` is a required field when you are trying to create a private instance (`publicly_accessible` = false) + ## Attributes Reference The following attributes are exported: From c905bfef226ba9b1bdd5f6fb67fbbc8fdfd19497 Mon Sep 17 00:00:00 2001 From: clint shryock Date: Fri, 6 Nov 2015 11:18:57 -0600 Subject: [PATCH 38/95] Test source_security_group_id for ELBs --- builtin/providers/aws/resource_aws_elb_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/builtin/providers/aws/resource_aws_elb_test.go b/builtin/providers/aws/resource_aws_elb_test.go index dadf4aba3..9d010d846 100644 --- a/builtin/providers/aws/resource_aws_elb_test.go +++ b/builtin/providers/aws/resource_aws_elb_test.go @@ -611,6 +611,15 @@ func testAccCheckAWSELBExists(n string, res *elb.LoadBalancerDescription) resour *res = *describe.LoadBalancerDescriptions[0] + // Confirm source_security_group_id for ELBs in a VPC + // See https://github.com/hashicorp/terraform/pull/3780 + if res.VPCId != nil { + sgid := rs.Primary.Attributes["source_security_group_id"] + if sgid == "" { + return fmt.Errorf("Expected to find source_security_group_id for ELB, but was empty") + } + } + return nil } } From 83ae6fe410361e16c69f9384ddd94e49271610a5 Mon Sep 17 00:00:00 2001 From: clint shryock Date: Fri, 6 Nov 2015 11:20:30 -0600 Subject: [PATCH 39/95] document source_security_group_id for ELB --- website/source/docs/providers/aws/r/elb.html.markdown | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/website/source/docs/providers/aws/r/elb.html.markdown b/website/source/docs/providers/aws/r/elb.html.markdown index 824a5507f..8d5144932 100644 --- a/website/source/docs/providers/aws/r/elb.html.markdown +++ b/website/source/docs/providers/aws/r/elb.html.markdown @@ -100,5 +100,8 @@ The following attributes are exported: * `instances` - The list of instances in the ELB * `source_security_group` - The name of the security group that you can use as part of your inbound rules for your load balancer's back-end application - instances. + instances. Use this for Classic or Default VPC only. +* `source_security_group_id` - The ID of the security group that you can use as + part of your inbound rules for your load balancer's back-end application + instances. Only available on ELBs launch in a VPC. * `zone_id` - The canonical hosted zone ID of the ELB (to be used in a Route 53 Alias record) From 7675246cccfeb9d14825cd4ae71a5caf67dae691 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Fri, 6 Nov 2015 10:22:20 -0700 Subject: [PATCH 40/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c8fb36f7..5215c84e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ IMPROVEMENTS: * provider/openstack: Add "delete on termination" boot-from-volume option [GH-3232] * provider/digitalocean: Make user_data force a new droplet [GH-3740] * provider/vsphere: Do not add network interfaces by default [GH-3652] + * provider/openstack: Configure Fixed IPs through ports [GH-3772] BUG FIXES: From 4c1083c9c5ff7e81093300436a2942db6d827a6f Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Fri, 6 Nov 2015 17:27:18 +0000 Subject: [PATCH 41/95] provider/openstack: fix test formatting --- ...ce_openstack_networking_network_v2_test.go | 77 +++++++++---------- ...ource_openstack_networking_port_v2_test.go | 18 ++--- 2 files changed, 47 insertions(+), 48 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go index 32a0c96be..0ae842293 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go @@ -117,54 +117,53 @@ func TestAccNetworkingV2Network_fullstack(t *testing.T) { var subnet subnets.Subnet var testAccNetworkingV2Network_fullstack = fmt.Sprintf(` - resource "openstack_networking_network_v2" "foo" { - region = "%s" - name = "network_1" - admin_state_up = "true" - } + resource "openstack_networking_network_v2" "foo" { + region = "%s" + name = "network_1" + admin_state_up = "true" + } - resource "openstack_networking_subnet_v2" "foo" { - region = "%s" - name = "subnet_1" - network_id = "${openstack_networking_network_v2.foo.id}" - cidr = "192.168.199.0/24" - ip_version = 4 - } + resource "openstack_networking_subnet_v2" "foo" { + region = "%s" + name = "subnet_1" + network_id = "${openstack_networking_network_v2.foo.id}" + cidr = "192.168.199.0/24" + ip_version = 4 + } - resource "openstack_compute_secgroup_v2" "foo" { - region = "%s" - name = "secgroup_1" - description = "a security group" - rule { - from_port = 22 - to_port = 22 - ip_protocol = "tcp" - cidr = "0.0.0.0/0" - } - } - - resource "openstack_networking_port_v2" "foo" { - region = "%s" - name = "port_1" - network_id = "${openstack_networking_network_v2.foo.id}" - admin_state_up = "true" - security_groups = ["${openstack_compute_secgroup_v2.foo.id}"] + resource "openstack_compute_secgroup_v2" "foo" { + region = "%s" + name = "secgroup_1" + description = "a security group" + rule { + from_port = 22 + to_port = 22 + ip_protocol = "tcp" + cidr = "0.0.0.0/0" + } + } + resource "openstack_networking_port_v2" "foo" { + region = "%s" + name = "port_1" + network_id = "${openstack_networking_network_v2.foo.id}" + admin_state_up = "true" + security_groups = ["${openstack_compute_secgroup_v2.foo.id}"] fixed_ips { "subnet_id" = "${openstack_networking_subnet_v2.foo.id}" "ip_address" = "192.168.199.23" } - } + } - resource "openstack_compute_instance_v2" "foo" { - region = "%s" - name = "terraform-test" - security_groups = ["${openstack_compute_secgroup_v2.foo.name}"] + resource "openstack_compute_instance_v2" "foo" { + region = "%s" + name = "terraform-test" + security_groups = ["${openstack_compute_secgroup_v2.foo.name}"] - network { - port = "${openstack_networking_port_v2.foo.id}" - } - }`, region, region, region, region, region) + network { + port = "${openstack_networking_port_v2.foo.id}" + } + }`, region, region, region, region, region) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, diff --git a/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go index c1e100d4f..9ad2f1deb 100644 --- a/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go @@ -27,13 +27,13 @@ func TestAccNetworkingV2Port_basic(t *testing.T) { admin_state_up = "true" } - resource "openstack_networking_subnet_v2" "foo" { - region = "%s" - name = "subnet_1" - network_id = "${openstack_networking_network_v2.foo.id}" - cidr = "192.168.199.0/24" - ip_version = 4 - } + resource "openstack_networking_subnet_v2" "foo" { + region = "%s" + name = "subnet_1" + network_id = "${openstack_networking_network_v2.foo.id}" + cidr = "192.168.199.0/24" + ip_version = 4 + } resource "openstack_networking_port_v2" "foo" { region = "%s" @@ -41,8 +41,8 @@ func TestAccNetworkingV2Port_basic(t *testing.T) { network_id = "${openstack_networking_network_v2.foo.id}" admin_state_up = "true" fixed_ips { - subnet_id = "${openstack_networking_subnet_v2.foo.id}" - ip_address = "192.168.199.23" + subnet_id = "${openstack_networking_subnet_v2.foo.id}" + ip_address = "192.168.199.23" } }`, region, region, region) From 15533dca0908815f4172bf6c6571288c406831b3 Mon Sep 17 00:00:00 2001 From: clint shryock Date: Fri, 6 Nov 2015 14:06:50 -0600 Subject: [PATCH 42/95] actually use the value --- builtin/providers/aws/resource_aws_sns_topic.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/builtin/providers/aws/resource_aws_sns_topic.go b/builtin/providers/aws/resource_aws_sns_topic.go index b0d6fecf5..6bf0127d0 100644 --- a/builtin/providers/aws/resource_aws_sns_topic.go +++ b/builtin/providers/aws/resource_aws_sns_topic.go @@ -46,10 +46,11 @@ func resourceAwsSnsTopic() *schema.Resource { Optional: true, Computed: true, StateFunc: func(v interface{}) string { - if v == nil { + s, ok := v.(string) + if !ok || s == "" { return "" } - jsonb := []byte{} + jsonb := []byte(s) buffer := new(bytes.Buffer) if err := json.Compact(buffer, jsonb); err != nil { log.Printf("[WARN] Error compacting JSON for Policy in SNS Topic") From 0e9397fc741fa69651a9dfdf2f2ef1881fd7d769 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Fri, 6 Nov 2015 21:52:30 +0000 Subject: [PATCH 43/95] provider/openstack: Security Group Rules Fix This commit fixes how security group rules are read by Terraform and enables them to be correctly removed when the rule resource is modified. --- .../resource_openstack_compute_secgroup_v2.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index 3cc0cbf0c..202c4d979 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -131,10 +131,10 @@ func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) err d.Set("description", sg.Description) rtm := rulesToMap(sg.Rules) for _, v := range rtm { - if v["group"] == d.Get("name") { - v["self"] = "1" + if v["from_group_id"] == d.Get("name") { + v["self"] = true } else { - v["self"] = "0" + v["self"] = false } } log.Printf("[DEBUG] rulesToMap(sg.Rules): %+v", rtm) @@ -283,12 +283,12 @@ func rulesToMap(sgrs []secgroups.Rule) []map[string]interface{} { sgrMap := make([]map[string]interface{}, len(sgrs)) for i, sgr := range sgrs { sgrMap[i] = map[string]interface{}{ - "id": sgr.ID, - "from_port": sgr.FromPort, - "to_port": sgr.ToPort, - "ip_protocol": sgr.IPProtocol, - "cidr": sgr.IPRange.CIDR, - "group": sgr.Group.Name, + "id": sgr.ID, + "from_port": sgr.FromPort, + "to_port": sgr.ToPort, + "ip_protocol": sgr.IPProtocol, + "cidr": sgr.IPRange.CIDR, + "from_group_id": sgr.Group.Name, } } return sgrMap From 1ab69ab6383b65b4775b955fa4e2db6f9b92f2ed Mon Sep 17 00:00:00 2001 From: Will Stern Date: Fri, 6 Nov 2015 15:55:58 -0600 Subject: [PATCH 44/95] typo in cluster_identifier --- .../docs/providers/aws/r/rds_cluster_instance.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/providers/aws/r/rds_cluster_instance.html.markdown b/website/source/docs/providers/aws/r/rds_cluster_instance.html.markdown index 0d64a3f87..c124713b3 100644 --- a/website/source/docs/providers/aws/r/rds_cluster_instance.html.markdown +++ b/website/source/docs/providers/aws/r/rds_cluster_instance.html.markdown @@ -27,7 +27,7 @@ For more information on Amazon Aurora, see [Aurora on Amazon RDS][2] in the Amaz resource "aws_rds_cluster_instance" "cluster_instances" { count = 2 identifier = "aurora-cluster-demo" - cluster_identifer = "${aws_rds_cluster.default.id}" + cluster_identifier = "${aws_rds_cluster.default.id}" instance_class = "db.r3.large" } From 327bd4f9c05afa1634358b2348c3ef9d615629c2 Mon Sep 17 00:00:00 2001 From: Rob Zienert Date: Fri, 6 Nov 2015 15:27:47 -0600 Subject: [PATCH 45/95] Adding S3 support for Lambda provider --- .../aws/resource_aws_lambda_function.go | 64 ++++++++++++++----- .../aws/r/lambda_function.html.markdown | 5 +- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/builtin/providers/aws/resource_aws_lambda_function.go b/builtin/providers/aws/resource_aws_lambda_function.go index 4ce898174..324016455 100644 --- a/builtin/providers/aws/resource_aws_lambda_function.go +++ b/builtin/providers/aws/resource_aws_lambda_function.go @@ -13,6 +13,8 @@ import ( "github.com/aws/aws-sdk-go/service/lambda" "github.com/mitchellh/go-homedir" + "errors" + "github.com/hashicorp/terraform/helper/schema" ) @@ -25,13 +27,28 @@ func resourceAwsLambdaFunction() *schema.Resource { Schema: map[string]*schema.Schema{ "filename": &schema.Schema{ - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"s3_bucket", "s3_key", "s3_object_version"}, + }, + "s3_bucket": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"filename"}, + }, + "s3_key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"filename"}, + }, + "s3_object_version": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"filename"}, }, "description": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, // TODO make this editable }, "function_name": &schema.Schema{ Type: schema.TypeString, @@ -93,22 +110,36 @@ func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) e log.Printf("[DEBUG] Creating Lambda Function %s with role %s", functionName, iamRole) - filename, err := homedir.Expand(d.Get("filename").(string)) - if err != nil { - return err + var functionCode *lambda.FunctionCode + if v, ok := d.GetOk("filename"); ok { + filename, err := homedir.Expand(v.(string)) + if err != nil { + return err + } + zipfile, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + d.Set("source_code_hash", sha256.Sum256(zipfile)) + functionCode = &lambda.FunctionCode{ + ZipFile: zipfile, + } + } else { + s3Bucket, bucketOk := d.GetOk("s3_bucket") + s3Key, keyOk := d.GetOk("s3_key") + s3ObjectVersion, versionOk := d.GetOk("s3_object_version") + if !bucketOk || !keyOk || !versionOk { + return errors.New("s3_bucket, s3_key and s3_object_version must all be set while using S3 code source") + } + functionCode = &lambda.FunctionCode{ + S3Bucket: aws.String(s3Bucket.(string)), + S3Key: aws.String(s3Key.(string)), + S3ObjectVersion: aws.String(s3ObjectVersion.(string)), + } } - zipfile, err := ioutil.ReadFile(filename) - if err != nil { - return err - } - d.Set("source_code_hash", sha256.Sum256(zipfile)) - - log.Printf("[DEBUG] ") params := &lambda.CreateFunctionInput{ - Code: &lambda.FunctionCode{ - ZipFile: zipfile, - }, + Code: functionCode, Description: aws.String(d.Get("description").(string)), FunctionName: aws.String(functionName), Handler: aws.String(d.Get("handler").(string)), @@ -118,6 +149,7 @@ func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) e Timeout: aws.Int64(int64(d.Get("timeout").(int))), } + var err error for i := 0; i < 5; i++ { _, err = conn.CreateFunction(params) if awsErr, ok := err.(awserr.Error); ok { diff --git a/website/source/docs/providers/aws/r/lambda_function.html.markdown b/website/source/docs/providers/aws/r/lambda_function.html.markdown index 4c931fbad..f9c1ea4a3 100644 --- a/website/source/docs/providers/aws/r/lambda_function.html.markdown +++ b/website/source/docs/providers/aws/r/lambda_function.html.markdown @@ -44,7 +44,10 @@ resource "aws_lambda_function" "test_lambda" { ## Argument Reference -* `filename` - (Required) A [zip file][2] containing your lambda function source code. +* `filename` - (Optional) A [zip file][2] containing your lambda function source code. If defined, The `s3_*` options cannot be used. +* `s3_bucket` - (Optional) The S3 bucket location containing your lambda function source code. Conflicts with `filename`. +* `s3_key` - (Optional) The S3 key containing your lambda function source code. Conflicts with `filename`. +* `s3_object_version` - (Optional) The object version of your lambda function source code. Conflicts with `filename`. * `function_name` - (Required) A unique name for your Lambda Function. * `handler` - (Required) The function [entrypoint][3] in your code. * `role` - (Required) IAM role attached to the Lambda Function. This governs both who / what can invoke your Lambda Function, as well as what resources our Lambda Function has access to. See [Lambda Permission Model][4] for more details. From 996f88acd8cab692cd26ea32c2c63dcb26dcb21a Mon Sep 17 00:00:00 2001 From: Clint Date: Fri, 6 Nov 2015 16:17:22 -0600 Subject: [PATCH 46/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5215c84e3..cdb5a393b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ IMPROVEMENTS: * provider/aws: Add notification topic ARN for ElastiCache clusters [GH-3674] * provider/aws: Add `kinesis_endpoint` for configuring Kinesis [GH-3255] * 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 configuration to enable copying RDS tags to final snapshot [GH-3529] * provider/aws: RDS Cluster additions (`backup_retention_period`, `preferred_backup_window`, `preferred_maintenance_window`) [GH-3757] * provider/openstack: Use IPv4 as the defeault IP version for subnets [GH-3091] From dbd2a43f464b80ab3c3893b7280e87d7105ce92f Mon Sep 17 00:00:00 2001 From: clint shryock Date: Fri, 6 Nov 2015 16:55:04 -0600 Subject: [PATCH 47/95] config updates for ElastiCache test --- .../providers/aws/resource_aws_elasticache_cluster_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_elasticache_cluster_test.go b/builtin/providers/aws/resource_aws_elasticache_cluster_test.go index 0620ef47b..a17c5d9b1 100644 --- a/builtin/providers/aws/resource_aws_elasticache_cluster_test.go +++ b/builtin/providers/aws/resource_aws_elasticache_cluster_test.go @@ -215,7 +215,7 @@ resource "aws_elasticache_security_group" "bar" { resource "aws_elasticache_cluster" "bar" { cluster_id = "tf-test-%03d" engine = "redis" - node_type = "cache.t2.small" + node_type = "cache.m1.small" num_cache_nodes = 1 port = 6379 parameter_group_name = "default.redis2.8" @@ -230,7 +230,7 @@ provider "aws" { region = "us-east-1" } resource "aws_security_group" "bar" { - name = "tf-test-security-group" + name = "tf-test-security-group-%03d" description = "tf-test-security-group-descr" ingress { from_port = -1 @@ -241,7 +241,7 @@ resource "aws_security_group" "bar" { } resource "aws_elasticache_security_group" "bar" { - name = "tf-test-security-group" + name = "tf-test-security-group-%03d" description = "tf-test-security-group-descr" security_group_names = ["${aws_security_group.bar.name}"] } From c40f689201336b363d722e56d61003c1aacc4587 Mon Sep 17 00:00:00 2001 From: Clint Date: Fri, 6 Nov 2015 16:56:54 -0600 Subject: [PATCH 48/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdb5a393b..1399eca4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ IMPROVEMENTS: * provider/aws: Add `kinesis_endpoint` for configuring Kinesis [GH-3255] * 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 snapshot window and retention limits for ElastiCache (Redis) [GH-3707] * provider/aws: Add configuration to enable copying RDS tags to final snapshot [GH-3529] * provider/aws: RDS Cluster additions (`backup_retention_period`, `preferred_backup_window`, `preferred_maintenance_window`) [GH-3757] * provider/openstack: Use IPv4 as the defeault IP version for subnets [GH-3091] From a727b0c415981afb9ff29d351b603aef120d7340 Mon Sep 17 00:00:00 2001 From: Joe Gross Date: Fri, 6 Nov 2015 19:02:05 -0800 Subject: [PATCH 49/95] add closing quote --- website/source/intro/getting-started/variables.html.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/intro/getting-started/variables.html.md b/website/source/intro/getting-started/variables.html.md index 41e828a72..24154ca25 100644 --- a/website/source/intro/getting-started/variables.html.md +++ b/website/source/intro/getting-started/variables.html.md @@ -186,7 +186,7 @@ And access them via `lookup()`: ``` output "ami" { - value = "${lookup(var.amis, var.region)} + value = "${lookup(var.amis, var.region)}" } ``` From 98a441314b7aa631ef6753d14f32b07486bf6dc5 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Sat, 7 Nov 2015 04:01:50 +0000 Subject: [PATCH 50/95] provider/openstack: Make Security Groups Computed This commit makes security groups in the openstack_compute_instance_v2 resource computed. This fixes issues where a security group is omitted which causes the instance to have the "default" group applied. When re-applying without this patch, an error will occur. --- .../openstack/resource_openstack_compute_instance_v2.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 4cf68de03..d21e1afed 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -95,6 +95,7 @@ func resourceComputeInstanceV2() *schema.Resource { Type: schema.TypeSet, Optional: true, ForceNew: false, + Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, From 4d640c6528fc7557f78f02dbe6ebbe6071000dc0 Mon Sep 17 00:00:00 2001 From: Paul Forman Date: Sat, 7 Nov 2015 00:25:07 -0700 Subject: [PATCH 51/95] providers/aws: add name_prefix option to launch config See #2911. This adds a `name_prefix` option to `aws_launch_configuration` resources. When specified, it is used instead of `terraform-` as the prefix for the launch configuration. It conflicts with `name`, so existing functionality is unchanged. `name` still sets the name explicitly. Added an acceptance test, and updated the site documentation. --- .../aws/resource_aws_launch_configuration.go | 27 ++++++++++++++++--- .../resource_aws_launch_configuration_test.go | 18 +++++++++++++ .../aws/r/launch_configuration.html.markdown | 10 ++++--- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/builtin/providers/aws/resource_aws_launch_configuration.go b/builtin/providers/aws/resource_aws_launch_configuration.go index 9b464e5d2..1cc010634 100644 --- a/builtin/providers/aws/resource_aws_launch_configuration.go +++ b/builtin/providers/aws/resource_aws_launch_configuration.go @@ -26,10 +26,11 @@ func resourceAwsLaunchConfiguration() *schema.Resource { Schema: map[string]*schema.Schema{ "name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"name_prefix"}, ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { // https://github.com/boto/botocore/blob/9f322b1/botocore/data/autoscaling/2011-01-01/service-2.json#L1932-L1939 value := v.(string) @@ -41,6 +42,22 @@ func resourceAwsLaunchConfiguration() *schema.Resource { }, }, + "name_prefix": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + // https://github.com/boto/botocore/blob/9f322b1/botocore/data/autoscaling/2011-01-01/service-2.json#L1932-L1939 + // uuid is 26 characters, limit the prefix to 229. + value := v.(string) + if len(value) > 229 { + errors = append(errors, fmt.Errorf( + "%q cannot be longer than 229 characters, name is limited to 255", k)) + } + return + }, + }, + "image_id": &schema.Schema{ Type: schema.TypeString, Required: true, @@ -386,6 +403,8 @@ func resourceAwsLaunchConfigurationCreate(d *schema.ResourceData, meta interface var lcName string if v, ok := d.GetOk("name"); ok { lcName = v.(string) + } else if v, ok := d.GetOk("name_prefix"); ok { + lcName = resource.PrefixedUniqueId(v.(string)) } else { lcName = resource.UniqueId() } diff --git a/builtin/providers/aws/resource_aws_launch_configuration_test.go b/builtin/providers/aws/resource_aws_launch_configuration_test.go index f8d4d8978..c6a0086a1 100644 --- a/builtin/providers/aws/resource_aws_launch_configuration_test.go +++ b/builtin/providers/aws/resource_aws_launch_configuration_test.go @@ -30,6 +30,14 @@ func TestAccAWSLaunchConfiguration_basic(t *testing.T) { "aws_launch_configuration.bar", "terraform-"), ), }, + resource.TestStep{ + Config: testAccAWSLaunchConfigurationPrefixNameConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLaunchConfigurationExists("aws_launch_configuration.baz", &conf), + testAccCheckAWSLaunchConfigurationGeneratedNamePrefix( + "aws_launch_configuration.baz", "baz-"), + ), + }, }, }) } @@ -255,3 +263,13 @@ resource "aws_launch_configuration" "bar" { associate_public_ip_address = false } ` + +const testAccAWSLaunchConfigurationPrefixNameConfig = ` +resource "aws_launch_configuration" "baz" { + name_prefix = "baz-" + image_id = "ami-21f78e11" + instance_type = "t1.micro" + user_data = "foobar-user-data-change" + associate_public_ip_address = false +} +` diff --git a/website/source/docs/providers/aws/r/launch_configuration.html.markdown b/website/source/docs/providers/aws/r/launch_configuration.html.markdown index 849264018..413f1b4a1 100644 --- a/website/source/docs/providers/aws/r/launch_configuration.html.markdown +++ b/website/source/docs/providers/aws/r/launch_configuration.html.markdown @@ -26,11 +26,13 @@ Launch Configurations cannot be updated after creation with the Amazon Web Service API. In order to update a Launch Configuration, Terraform will destroy the existing resource and create a replacement. In order to effectively use a Launch Configuration resource with an [AutoScaling Group resource][1], -it's recommend to omit the Launch Configuration `name` attribute, and -specify `create_before_destroy` in a [lifecycle][2] block, as shown: +it's recommended to specify `create_before_destroy` in a [lifecycle][2] block. +Either omit the Launch Configuration `name` attribute, or specify a partial name +with `name_prefix`. Example: ``` resource "aws_launch_configuration" "as_conf" { + name_prefix = "terraform-lc-example-" image_id = "ami-1234" instance_type = "m1.small" @@ -87,7 +89,9 @@ resource "aws_autoscaling_group" "bar" { The following arguments are supported: * `name` - (Optional) The name of the launch configuration. If you leave - this blank, Terraform will auto-generate it. + this blank, Terraform will auto-generate a unique name. +* `name_prefix` - (Optional) Creates a unique name beginning with the specified + prefix. Conflicts with `name`. * `image_id` - (Required) The EC2 image ID to launch. * `instance_type` - (Required) The size of instance to launch. * `iam_instance_profile` - (Optional) The IAM instance profile to associate From ad1c28990c37324575a1249a5f021ae45255a327 Mon Sep 17 00:00:00 2001 From: Eddie Forson Date: Sat, 7 Nov 2015 13:35:21 +0000 Subject: [PATCH 52/95] providers/google: add pubsub auth endpoint #3803 --- builtin/providers/google/service_scope.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/builtin/providers/google/service_scope.go b/builtin/providers/google/service_scope.go index d4c518125..5770dbeaa 100644 --- a/builtin/providers/google/service_scope.go +++ b/builtin/providers/google/service_scope.go @@ -11,6 +11,7 @@ func canonicalizeServiceScope(scope string) string { "datastore": "https://www.googleapis.com/auth/datastore", "logging-write": "https://www.googleapis.com/auth/logging.write", "monitoring": "https://www.googleapis.com/auth/monitoring", + "pubsub": "https://www.googleapis.com/auth/pubsub", "sql": "https://www.googleapis.com/auth/sqlservice", "sql-admin": "https://www.googleapis.com/auth/sqlservice.admin", "storage-full": "https://www.googleapis.com/auth/devstorage.full_control", @@ -22,9 +23,9 @@ func canonicalizeServiceScope(scope string) string { "userinfo-email": "https://www.googleapis.com/auth/userinfo.email", } - if matchedUrl, ok := scopeMap[scope]; ok { - return matchedUrl - } else { - return scope + if matchedURL, ok := scopeMap[scope]; ok { + return matchedURL } + + return scope } From dc96d7e61bd1c12897060cca1a94eec077887321 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Sat, 7 Nov 2015 12:32:22 -0700 Subject: [PATCH 53/95] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1399eca4f..a472b61bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,8 @@ BUG FIXES: * provider/openstack: Fix boot from volume [GH-3206] * provider/openstack: Fix crashing when image is no longer accessible [GH-2189] * provider/openstack: Better handling of network resource state changes [GH-3712] + * provider/openstack: Fix issue preventing security group rules from being removed [GH-3796] + * provider/openstack: Fix crashing when no security group is specified [GH-3801] ## 0.6.6 (October 23, 2015) From 02f512d4bd665c2983a22af8d51d368c509049c1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Nov 2015 16:53:42 -0800 Subject: [PATCH 54/95] config: new HCL API --- config/loader.go | 2 +- config/loader_hcl.go | 519 +++++++++++++++++++++---------------------- 2 files changed, 249 insertions(+), 272 deletions(-) diff --git a/config/loader.go b/config/loader.go index 5711ce8ef..c9a1295fe 100644 --- a/config/loader.go +++ b/config/loader.go @@ -25,7 +25,7 @@ func LoadJSON(raw json.RawMessage) (*Config, error) { // Start building the result hclConfig := &hclConfigurable{ - Object: obj, + Root: obj, } return hclConfig.Config() diff --git a/config/loader_hcl.go b/config/loader_hcl.go index f451a31d1..07b0eef84 100644 --- a/config/loader_hcl.go +++ b/config/loader_hcl.go @@ -5,15 +5,15 @@ import ( "io/ioutil" "github.com/hashicorp/hcl" - hclobj "github.com/hashicorp/hcl/hcl" + "github.com/hashicorp/hcl/hcl/ast" "github.com/mitchellh/mapstructure" ) // hclConfigurable is an implementation of configurable that knows // how to turn HCL configuration into a *Config object. type hclConfigurable struct { - File string - Object *hclobj.Object + File string + Root *ast.File } func (t *hclConfigurable) Config() (*Config, error) { @@ -36,7 +36,13 @@ func (t *hclConfigurable) Config() (*Config, error) { Variable map[string]*hclVariable } - if err := hcl.DecodeObject(&rawConfig, t.Object); err != nil { + // Top-level item should be the object list + list, ok := t.Root.Node.(*ast.ObjectList) + if !ok { + return nil, fmt.Errorf("error parsing: file doesn't contain a root object") + } + + if err := hcl.DecodeObject(&rawConfig, list); err != nil { return nil, err } @@ -73,7 +79,7 @@ func (t *hclConfigurable) Config() (*Config, error) { } // Get Atlas configuration - if atlas := t.Object.Get("atlas", false); atlas != nil { + if atlas := list.Filter("atlas"); len(atlas.Items) > 0 { var err error config.Atlas, err = loadAtlasHcl(atlas) if err != nil { @@ -82,7 +88,7 @@ func (t *hclConfigurable) Config() (*Config, error) { } // Build the modules - if modules := t.Object.Get("module", false); modules != nil { + if modules := list.Filter("module"); len(modules.Items) > 0 { var err error config.Modules, err = loadModulesHcl(modules) if err != nil { @@ -91,7 +97,7 @@ func (t *hclConfigurable) Config() (*Config, error) { } // Build the provider configs - if providers := t.Object.Get("provider", false); providers != nil { + if providers := list.Filter("provider"); len(providers.Items) > 0 { var err error config.ProviderConfigs, err = loadProvidersHcl(providers) if err != nil { @@ -100,7 +106,7 @@ func (t *hclConfigurable) Config() (*Config, error) { } // Build the resources - if resources := t.Object.Get("resource", false); resources != nil { + if resources := list.Filter("resource"); len(resources.Items) > 0 { var err error config.Resources, err = loadResourcesHcl(resources) if err != nil { @@ -109,7 +115,7 @@ func (t *hclConfigurable) Config() (*Config, error) { } // Build the outputs - if outputs := t.Object.Get("output", false); outputs != nil { + if outputs := list.Filter("output"); len(outputs.Items) > 0 { var err error config.Outputs, err = loadOutputsHcl(outputs) if err != nil { @@ -118,8 +124,13 @@ func (t *hclConfigurable) Config() (*Config, error) { } // Check for invalid keys - for _, elem := range t.Object.Elem(true) { - k := elem.Key + for _, item := range list.Items { + if len(item.Keys) == 0 { + // Not sure how this would happen, but let's avoid a panic + continue + } + + k := item.Keys[0].Token.Value().(string) if _, ok := validKeys[k]; ok { continue } @@ -133,8 +144,6 @@ func (t *hclConfigurable) Config() (*Config, error) { // loadFileHcl is a fileLoaderFunc that knows how to read HCL // files and turn them into hclConfigurables. func loadFileHcl(root string) (configurable, []string, error) { - var obj *hclobj.Object = nil - // Read the HCL file and prepare for parsing d, err := ioutil.ReadFile(root) if err != nil { @@ -143,7 +152,7 @@ func loadFileHcl(root string) (configurable, []string, error) { } // Parse it - obj, err = hcl.Parse(string(d)) + hclRoot, err := hcl.Parse(string(d)) if err != nil { return nil, nil, fmt.Errorf( "Error parsing %s: %s", root, err) @@ -151,8 +160,8 @@ func loadFileHcl(root string) (configurable, []string, error) { // Start building the result result := &hclConfigurable{ - File: root, - Object: obj, + File: root, + Root: hclRoot, } // Dive in, find the imports. This is disabled for now since @@ -200,9 +209,16 @@ func loadFileHcl(root string) (configurable, []string, error) { // Given a handle to a HCL object, this transforms it into the Atlas // configuration. -func loadAtlasHcl(obj *hclobj.Object) (*AtlasConfig, error) { +func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) { + if len(list.Items) > 1 { + return nil, fmt.Errorf("only one 'atlas' block allowed") + } + + // Get our one item + item := list.Items[0] + var config AtlasConfig - if err := hcl.DecodeObject(&config, obj); err != nil { + if err := hcl.DecodeObject(&config, item.Val); err != nil { return nil, fmt.Errorf( "Error reading atlas config: %s", err) @@ -217,18 +233,10 @@ func loadAtlasHcl(obj *hclobj.Object) (*AtlasConfig, error) { // The resulting modules may not be unique, but each module // represents exactly one module definition in the HCL configuration. // We leave it up to another pass to merge them together. -func loadModulesHcl(os *hclobj.Object) ([]*Module, error) { - var allNames []*hclobj.Object - - // See loadResourcesHcl for why this exists. Don't touch this. - for _, o1 := range os.Elem(false) { - // Iterate the inner to get the list of types - for _, o2 := range o1.Elem(true) { - // Iterate all of this type to get _all_ the types - for _, o3 := range o2.Elem(false) { - allNames = append(allNames, o3) - } - } +func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) { + list = list.Children() + if len(list.Items) == 0 { + return nil, nil } // Where all the results will go @@ -236,11 +244,18 @@ func loadModulesHcl(os *hclobj.Object) ([]*Module, error) { // Now go over all the types and their children in order to get // all of the actual resources. - for _, obj := range allNames { - k := obj.Key + for _, item := range list.Items { + k := item.Keys[0].Token.Value().(string) + + var listVal *ast.ObjectList + if ot, ok := item.Val.(*ast.ObjectType); ok { + listVal = ot.List + } else { + return nil, fmt.Errorf("module '%s': should be an object", k) + } var config map[string]interface{} - if err := hcl.DecodeObject(&config, obj); err != nil { + if err := hcl.DecodeObject(&config, item.Val); err != nil { return nil, fmt.Errorf( "Error reading config for %s: %s", k, @@ -260,8 +275,8 @@ func loadModulesHcl(os *hclobj.Object) ([]*Module, error) { // If we have a count, then figure it out var source string - if o := obj.Get("source", false); o != nil { - err = hcl.DecodeObject(&source, o) + if o := listVal.Filter("source"); len(o.Items) > 0 { + err = hcl.DecodeObject(&source, o.Items[0].Val) if err != nil { return nil, fmt.Errorf( "Error parsing source for %s: %s", @@ -282,27 +297,19 @@ func loadModulesHcl(os *hclobj.Object) ([]*Module, error) { // LoadOutputsHcl recurses into the given HCL object and turns // it into a mapping of outputs. -func loadOutputsHcl(os *hclobj.Object) ([]*Output, error) { - objects := make(map[string]*hclobj.Object) - - // Iterate over all the "output" blocks and get the keys along with - // their raw configuration objects. We'll parse those later. - for _, o1 := range os.Elem(false) { - for _, o2 := range o1.Elem(true) { - objects[o2.Key] = o2 - } - } - - if len(objects) == 0 { +func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) { + list = list.Children() + if len(list.Items) == 0 { return nil, nil } // Go through each object and turn it into an actual result. - result := make([]*Output, 0, len(objects)) - for n, o := range objects { - var config map[string]interface{} + result := make([]*Output, 0, len(list.Items)) + for _, item := range list.Items { + n := item.Keys[0].Token.Value().(string) - if err := hcl.DecodeObject(&config, o); err != nil { + var config map[string]interface{} + if err := hcl.DecodeObject(&config, item.Val); err != nil { return nil, err } @@ -325,27 +332,26 @@ func loadOutputsHcl(os *hclobj.Object) ([]*Output, error) { // LoadProvidersHcl recurses into the given HCL object and turns // it into a mapping of provider configs. -func loadProvidersHcl(os *hclobj.Object) ([]*ProviderConfig, error) { - var objects []*hclobj.Object - - // Iterate over all the "provider" blocks and get the keys along with - // their raw configuration objects. We'll parse those later. - for _, o1 := range os.Elem(false) { - for _, o2 := range o1.Elem(true) { - objects = append(objects, o2) - } - } - - if len(objects) == 0 { +func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { + list = list.Children() + if len(list.Items) == 0 { return nil, nil } // Go through each object and turn it into an actual result. - result := make([]*ProviderConfig, 0, len(objects)) - for _, o := range objects { - var config map[string]interface{} + result := make([]*ProviderConfig, 0, len(list.Items)) + for _, item := range list.Items { + n := item.Keys[0].Token.Value().(string) - if err := hcl.DecodeObject(&config, o); err != nil { + var listVal *ast.ObjectList + if ot, ok := item.Val.(*ast.ObjectType); ok { + listVal = ot.List + } else { + return nil, fmt.Errorf("module '%s': should be an object", n) + } + + var config map[string]interface{} + if err := hcl.DecodeObject(&config, item.Val); err != nil { return nil, err } @@ -355,24 +361,24 @@ func loadProvidersHcl(os *hclobj.Object) ([]*ProviderConfig, error) { if err != nil { return nil, fmt.Errorf( "Error reading config for provider config %s: %s", - o.Key, + n, err) } // If we have an alias field, then add those in var alias string - if a := o.Get("alias", false); a != nil { - err := hcl.DecodeObject(&alias, a) + if a := listVal.Filter("alias"); len(a.Items) > 0 { + err := hcl.DecodeObject(&alias, a.Items[0].Val) if err != nil { return nil, fmt.Errorf( "Error reading alias for provider[%s]: %s", - o.Key, + n, err) } } result = append(result, &ProviderConfig{ - Name: o.Key, + Name: n, Alias: alias, RawConfig: rawConfig, }) @@ -387,27 +393,10 @@ func loadProvidersHcl(os *hclobj.Object) ([]*ProviderConfig, error) { // The resulting resources may not be unique, but each resource // represents exactly one resource definition in the HCL configuration. // We leave it up to another pass to merge them together. -func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) { - var allTypes []*hclobj.Object - - // HCL object iteration is really nasty. Below is likely to make - // no sense to anyone approaching this code. Luckily, it is very heavily - // tested. If working on a bug fix or feature, we recommend writing a - // test first then doing whatever you want to the code below. If you - // break it, the tests will catch it. Likewise, if you change this, - // MAKE SURE you write a test for your change, because its fairly impossible - // to reason about this mess. - // - // Functionally, what the code does below is get the libucl.Objects - // for all the TYPES, such as "aws_security_group". - for _, o1 := range os.Elem(false) { - // Iterate the inner to get the list of types - for _, o2 := range o1.Elem(true) { - // Iterate all of this type to get _all_ the types - for _, o3 := range o2.Elem(false) { - allTypes = append(allTypes, o3) - } - } +func loadResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { + list = list.Children() + if len(list.Items) == 0 { + return nil, nil } // Where all the results will go @@ -415,191 +404,179 @@ func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) { // Now go over all the types and their children in order to get // all of the actual resources. - for _, t := range allTypes { - for _, obj := range t.Elem(true) { - k := obj.Key - - var config map[string]interface{} - if err := hcl.DecodeObject(&config, obj); err != nil { - return nil, fmt.Errorf( - "Error reading config for %s[%s]: %s", - t.Key, - k, - err) - } - - // Remove the fields we handle specially - delete(config, "connection") - delete(config, "count") - delete(config, "depends_on") - delete(config, "provisioner") - delete(config, "provider") - delete(config, "lifecycle") - - rawConfig, err := NewRawConfig(config) - if err != nil { - return nil, fmt.Errorf( - "Error reading config for %s[%s]: %s", - t.Key, - k, - err) - } - - // If we have a count, then figure it out - var count string = "1" - if o := obj.Get("count", false); o != nil { - err = hcl.DecodeObject(&count, o) - if err != nil { - return nil, fmt.Errorf( - "Error parsing count for %s[%s]: %s", - t.Key, - k, - err) - } - } - countConfig, err := NewRawConfig(map[string]interface{}{ - "count": count, - }) - if err != nil { - return nil, err - } - countConfig.Key = "count" - - // If we have depends fields, then add those in - var dependsOn []string - if o := obj.Get("depends_on", false); o != nil { - err := hcl.DecodeObject(&dependsOn, o) - if err != nil { - return nil, fmt.Errorf( - "Error reading depends_on for %s[%s]: %s", - t.Key, - k, - err) - } - } - - // If we have connection info, then parse those out - var connInfo map[string]interface{} - if o := obj.Get("connection", false); o != nil { - err := hcl.DecodeObject(&connInfo, o) - if err != nil { - return nil, fmt.Errorf( - "Error reading connection info for %s[%s]: %s", - t.Key, - k, - err) - } - } - - // If we have provisioners, then parse those out - var provisioners []*Provisioner - if os := obj.Get("provisioner", false); os != nil { - var err error - provisioners, err = loadProvisionersHcl(os, connInfo) - if err != nil { - return nil, fmt.Errorf( - "Error reading provisioners for %s[%s]: %s", - t.Key, - k, - err) - } - } - - // If we have a provider, then parse it out - var provider string - if o := obj.Get("provider", false); o != nil { - err := hcl.DecodeObject(&provider, o) - if err != nil { - return nil, fmt.Errorf( - "Error reading provider for %s[%s]: %s", - t.Key, - k, - err) - } - } - - // Check if the resource should be re-created before - // destroying the existing instance - var lifecycle ResourceLifecycle - if o := obj.Get("lifecycle", false); o != nil { - var raw map[string]interface{} - if err = hcl.DecodeObject(&raw, o); err != nil { - return nil, fmt.Errorf( - "Error parsing lifecycle for %s[%s]: %s", - t.Key, - k, - err) - } - - if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil { - return nil, fmt.Errorf( - "Error parsing lifecycle for %s[%s]: %s", - t.Key, - k, - err) - } - } - - result = append(result, &Resource{ - Name: k, - Type: t.Key, - RawCount: countConfig, - RawConfig: rawConfig, - Provisioners: provisioners, - Provider: provider, - DependsOn: dependsOn, - Lifecycle: lifecycle, - }) + for _, item := range list.Items { + if len(item.Keys) != 2 { + // TODO: bad error message + return nil, fmt.Errorf("resource needs exactly 2 names") } + + t := item.Keys[0].Token.Value().(string) + k := item.Keys[1].Token.Value().(string) + + var listVal *ast.ObjectList + if ot, ok := item.Val.(*ast.ObjectType); ok { + listVal = ot.List + } else { + return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k) + } + + var config map[string]interface{} + if err := hcl.DecodeObject(&config, item.Val); err != nil { + return nil, fmt.Errorf( + "Error reading config for %s[%s]: %s", + t, + k, + err) + } + + // Remove the fields we handle specially + delete(config, "connection") + delete(config, "count") + delete(config, "depends_on") + delete(config, "provisioner") + delete(config, "provider") + delete(config, "lifecycle") + + rawConfig, err := NewRawConfig(config) + if err != nil { + return nil, fmt.Errorf( + "Error reading config for %s[%s]: %s", + t, + k, + err) + } + + // If we have a count, then figure it out + var count string = "1" + if o := listVal.Filter("count"); len(o.Items) > 0 { + err = hcl.DecodeObject(&count, o.Items[0].Val) + if err != nil { + return nil, fmt.Errorf( + "Error parsing count for %s[%s]: %s", + t, + k, + err) + } + } + countConfig, err := NewRawConfig(map[string]interface{}{ + "count": count, + }) + if err != nil { + return nil, err + } + countConfig.Key = "count" + + // If we have depends fields, then add those in + var dependsOn []string + if o := listVal.Filter("depends_on"); len(o.Items) > 0 { + err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) + if err != nil { + return nil, fmt.Errorf( + "Error reading depends_on for %s[%s]: %s", + t, + k, + err) + } + } + + // If we have connection info, then parse those out + var connInfo map[string]interface{} + if o := listVal.Filter("connection"); len(o.Items) > 0 { + err := hcl.DecodeObject(&connInfo, o.Items[0].Val) + if err != nil { + return nil, fmt.Errorf( + "Error reading connection info for %s[%s]: %s", + t, + k, + err) + } + } + + // If we have provisioners, then parse those out + var provisioners []*Provisioner + if os := listVal.Filter("provisioner"); len(os.Items) > 0 { + var err error + provisioners, err = loadProvisionersHcl(os, connInfo) + if err != nil { + return nil, fmt.Errorf( + "Error reading provisioners for %s[%s]: %s", + t, + k, + err) + } + } + + // If we have a provider, then parse it out + var provider string + if o := listVal.Filter("provider"); len(o.Items) > 0 { + err := hcl.DecodeObject(&provider, o.Items[0].Val) + if err != nil { + return nil, fmt.Errorf( + "Error reading provider for %s[%s]: %s", + t, + k, + err) + } + } + + // Check if the resource should be re-created before + // destroying the existing instance + var lifecycle ResourceLifecycle + if o := listVal.Filter("lifecycle"); len(o.Items) > 0 { + var raw map[string]interface{} + if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil { + return nil, fmt.Errorf( + "Error parsing lifecycle for %s[%s]: %s", + t, + k, + err) + } + + if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil { + return nil, fmt.Errorf( + "Error parsing lifecycle for %s[%s]: %s", + t, + k, + err) + } + } + + result = append(result, &Resource{ + Name: k, + Type: t, + RawCount: countConfig, + RawConfig: rawConfig, + Provisioners: provisioners, + Provider: provider, + DependsOn: dependsOn, + Lifecycle: lifecycle, + }) } return result, nil } -func loadProvisionersHcl(os *hclobj.Object, connInfo map[string]interface{}) ([]*Provisioner, error) { - pos := make([]*hclobj.Object, 0, int(os.Len())) - - // Accumulate all the actual provisioner configuration objects. We - // have to iterate twice here: - // - // 1. The first iteration is of the list of `provisioner` blocks. - // 2. The second iteration is of the dictionary within the - // provisioner which will have only one element which is the - // type of provisioner to use along with tis config. - // - // In JSON it looks kind of like this: - // - // [ - // { - // "shell": { - // ... - // } - // } - // ] - // - for _, o1 := range os.Elem(false) { - for _, o2 := range o1.Elem(true) { - - switch o1.Type { - case hclobj.ValueTypeList: - for _, o3 := range o2.Elem(true) { - pos = append(pos, o3) - } - case hclobj.ValueTypeObject: - pos = append(pos, o2) - } - } - } - - // Short-circuit if there are no items - if len(pos) == 0 { +func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) { + println(fmt.Sprintf("%#v", list.Items[0])) + list = list.Children() + if len(list.Items) == 0 { return nil, nil } - result := make([]*Provisioner, 0, len(pos)) - for _, po := range pos { + // Go through each object and turn it into an actual result. + result := make([]*Provisioner, 0, len(list.Items)) + for _, item := range list.Items { + n := item.Keys[0].Token.Value().(string) + + var listVal *ast.ObjectList + if ot, ok := item.Val.(*ast.ObjectType); ok { + listVal = ot.List + } else { + return nil, fmt.Errorf("provisioner '%s': should be an object", n) + } + var config map[string]interface{} - if err := hcl.DecodeObject(&config, po); err != nil { + if err := hcl.DecodeObject(&config, item.Val); err != nil { return nil, err } @@ -614,8 +591,8 @@ func loadProvisionersHcl(os *hclobj.Object, connInfo map[string]interface{}) ([] // Check if we have a provisioner-level connection // block that overrides the resource-level var subConnInfo map[string]interface{} - if o := po.Get("connection", false); o != nil { - err := hcl.DecodeObject(&subConnInfo, o) + if o := listVal.Filter("connection"); len(o.Items) > 0 { + err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val) if err != nil { return nil, err } @@ -640,7 +617,7 @@ func loadProvisionersHcl(os *hclobj.Object, connInfo map[string]interface{}) ([] } result = append(result, &Provisioner{ - Type: po.Key, + Type: n, RawConfig: rawConfig, ConnInfo: connRaw, }) From 13c5fdb1542424210dbb955e341b9f92b50cbed3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Nov 2015 16:55:07 -0800 Subject: [PATCH 55/95] config: remove debug line --- config/loader_hcl.go | 1 - 1 file changed, 1 deletion(-) diff --git a/config/loader_hcl.go b/config/loader_hcl.go index 07b0eef84..c62ca3731 100644 --- a/config/loader_hcl.go +++ b/config/loader_hcl.go @@ -557,7 +557,6 @@ func loadResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { } func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) { - println(fmt.Sprintf("%#v", list.Items[0])) list = list.Children() if len(list.Items) == 0 { return nil, nil From 6ecec7fe833e11e09c16b994eb24746fa2e20b77 Mon Sep 17 00:00:00 2001 From: Matt Morrison Date: Sun, 8 Nov 2015 19:34:56 +1300 Subject: [PATCH 56/95] Add coalesce func --- config/interpolate_funcs.go | 25 +++++++++++++++++ config/interpolate_funcs_test.go | 27 +++++++++++++++++++ .../docs/configuration/interpolation.html.md | 3 +++ 3 files changed, 55 insertions(+) diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index e98ade2f0..5538763c0 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -25,6 +25,7 @@ func init() { "cidrhost": interpolationFuncCidrHost(), "cidrnetmask": interpolationFuncCidrNetmask(), "cidrsubnet": interpolationFuncCidrSubnet(), + "coalesce": interpolationFuncCoalesce(), "compact": interpolationFuncCompact(), "concat": interpolationFuncConcat(), "element": interpolationFuncElement(), @@ -145,6 +146,30 @@ func interpolationFuncCidrSubnet() ast.Function { } } +// interpolationFuncCoalesce implements the "coalesce" function that +// returns the first non null / empty string from the provided input +func interpolationFuncCoalesce() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeString}, + ReturnType: ast.TypeString, + Variadic: true, + VariadicType: ast.TypeString, + Callback: func(args []interface{}) (interface{}, error) { + if len(args) < 2 { + return nil, fmt.Errorf("must provide at least two arguments") + } + for _, arg := range args { + argument := arg.(string) + + if argument != "" { + return argument, nil + } + } + return "", nil + }, + } +} + // interpolationFuncConcat implements the "concat" function that // concatenates multiple strings. This isn't actually necessary anymore // since our language supports string concat natively, but for backwards diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index bbfdd484a..3aeb50db1 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -147,6 +147,33 @@ func TestInterpolateFuncCidrSubnet(t *testing.T) { }) } +func TestInterpolateFuncCoalesce(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + { + `${coalesce("first", "second", "third")}`, + "first", + false, + }, + { + `${coalesce("", "second", "third")}`, + "second", + false, + }, + { + `${coalesce("", "", "")}`, + "", + false, + }, + { + `${coalesce("foo")}`, + nil, + true, + }, + }, + }) +} + func TestInterpolateFuncDeprecatedConcat(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ diff --git a/website/source/docs/configuration/interpolation.html.md b/website/source/docs/configuration/interpolation.html.md index 049c71825..21efbd83e 100644 --- a/website/source/docs/configuration/interpolation.html.md +++ b/website/source/docs/configuration/interpolation.html.md @@ -95,6 +95,9 @@ The supported built-in functions are: CIDR notation (like ``10.0.0.0/8``) and extends its prefix to include an additional subnet number. For example, ``cidrsubnet("10.0.0.0/8", 8, 2)`` returns ``10.2.0.0/16``. + + * `coalesce(string1, string2, ...)` - Returns the first non-empty value from + the given arguments. At least two arguments must be provided. * `compact(list)` - Removes empty string elements from a list. This can be useful in some cases, for example when passing joined lists as module From 7e676a672f44e5e81ece3b2d81816aaf711c4147 Mon Sep 17 00:00:00 2001 From: Kirill Shirinkin Date: Sun, 8 Nov 2015 20:43:34 +0100 Subject: [PATCH 57/95] provider/openstack: extend documentation of Neutron::FloatingIP --- .../openstack/r/networking_floatingip_v2.html.markdown | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/source/docs/providers/openstack/r/networking_floatingip_v2.html.markdown b/website/source/docs/providers/openstack/r/networking_floatingip_v2.html.markdown index 9389eafeb..fb1c57cbc 100644 --- a/website/source/docs/providers/openstack/r/networking_floatingip_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/networking_floatingip_v2.html.markdown @@ -35,6 +35,9 @@ The following arguments are supported: * `pool` - (Required) The name of the pool from which to obtain the floating IP. Changing this creates a new floating IP. +* `port_id` - ID of an existing port with at least one IP address to associate with +this floating IP. + ## Attributes Reference The following attributes are exported: @@ -42,3 +45,4 @@ The following attributes are exported: * `region` - See Argument Reference above. * `pool` - See Argument Reference above. * `address` - The actual floating IP address itself. +* `port_id` - ID of associated port. From 87e384dc97829f3278311833ac1e7933fcd18cec Mon Sep 17 00:00:00 2001 From: Jef Statham Date: Mon, 9 Nov 2015 09:56:38 -0500 Subject: [PATCH 58/95] Fix spelling mistake in aws saml example Example resource name is `aws_iam_saml_provider` instead of `aws_saml_provider`. --- .../source/docs/providers/aws/r/iam_saml_provider.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/providers/aws/r/iam_saml_provider.html.markdown b/website/source/docs/providers/aws/r/iam_saml_provider.html.markdown index adba6d350..a27d45b64 100644 --- a/website/source/docs/providers/aws/r/iam_saml_provider.html.markdown +++ b/website/source/docs/providers/aws/r/iam_saml_provider.html.markdown @@ -13,7 +13,7 @@ Provides an IAM SAML provider. ## Example Usage ``` -resource "aws_saml_provider" "default" { +resource "aws_iam_saml_provider" "default" { name = "myprovider" saml_metadata_document = "${file("saml-metadata.xml")}" } From 13e862673f31ce2c27b7a704946b23bb9c317a5c Mon Sep 17 00:00:00 2001 From: Jef Statham Date: Mon, 9 Nov 2015 10:52:09 -0500 Subject: [PATCH 59/95] Fix spelling mistake in aws iam saml title --- .../source/docs/providers/aws/r/iam_saml_provider.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/providers/aws/r/iam_saml_provider.html.markdown b/website/source/docs/providers/aws/r/iam_saml_provider.html.markdown index a27d45b64..49fe6ec73 100644 --- a/website/source/docs/providers/aws/r/iam_saml_provider.html.markdown +++ b/website/source/docs/providers/aws/r/iam_saml_provider.html.markdown @@ -1,6 +1,6 @@ --- layout: "aws" -page_title: "AWS: aws_saml_provider" +page_title: "AWS: aws_iam_saml_provider" sidebar_current: "docs-aws-resource-iam-saml-provider" description: |- Provides an IAM SAML provider. From a80f6fd9795e731c72f4be3c596ccd28ebdcc088 Mon Sep 17 00:00:00 2001 From: clint shryock Date: Mon, 9 Nov 2015 10:18:53 -0600 Subject: [PATCH 60/95] Always deploy from stable website branch --- scripts/website_push.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/website_push.sh b/scripts/website_push.sh index fa58fd694..53ed59777 100755 --- a/scripts/website_push.sh +++ b/scripts/website_push.sh @@ -1,5 +1,8 @@ #!/bin/bash +# Switch to the stable-website branch +git checkout stable-website + # Set the tmpdir if [ -z "$TMPDIR" ]; then TMPDIR="/tmp" From f4c03ec2a6b8636a3ca74d4dc75470ffaa019693 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 9 Nov 2015 11:38:51 -0500 Subject: [PATCH 61/95] Reflect new comment format in stringer.go As of November 8th 2015, (4b07c5ce8a), the word "Code" is prepended to the comments in Go source files generated by the stringer utility. --- command/counthookaction_string.go | 2 +- config/lang/ast/type_string.go | 2 +- helper/schema/getsource_string.go | 2 +- helper/schema/valuetype_string.go | 2 +- terraform/graphnodeconfigtype_string.go | 2 +- terraform/instancetype_string.go | 2 +- terraform/walkoperation_string.go | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/command/counthookaction_string.go b/command/counthookaction_string.go index 8b90dc50b..c0c40d0de 100644 --- a/command/counthookaction_string.go +++ b/command/counthookaction_string.go @@ -1,4 +1,4 @@ -// generated by stringer -type=countHookAction hook_count_action.go; DO NOT EDIT +// Code generated by "stringer -type=countHookAction hook_count_action.go"; DO NOT EDIT package command diff --git a/config/lang/ast/type_string.go b/config/lang/ast/type_string.go index d9b5a2df4..5410e011e 100644 --- a/config/lang/ast/type_string.go +++ b/config/lang/ast/type_string.go @@ -1,4 +1,4 @@ -// generated by stringer -type=Type; DO NOT EDIT +// Code generated by "stringer -type=Type"; DO NOT EDIT package ast diff --git a/helper/schema/getsource_string.go b/helper/schema/getsource_string.go index 039bb561a..790dbff91 100644 --- a/helper/schema/getsource_string.go +++ b/helper/schema/getsource_string.go @@ -1,4 +1,4 @@ -// generated by stringer -type=getSource resource_data_get_source.go; DO NOT EDIT +// Code generated by "stringer -type=getSource resource_data_get_source.go"; DO NOT EDIT package schema diff --git a/helper/schema/valuetype_string.go b/helper/schema/valuetype_string.go index 42442a46b..08f008450 100644 --- a/helper/schema/valuetype_string.go +++ b/helper/schema/valuetype_string.go @@ -1,4 +1,4 @@ -// generated by stringer -type=ValueType valuetype.go; DO NOT EDIT +// Code generated by "stringer -type=ValueType valuetype.go"; DO NOT EDIT package schema diff --git a/terraform/graphnodeconfigtype_string.go b/terraform/graphnodeconfigtype_string.go index d8c1724f4..9ea0acbeb 100644 --- a/terraform/graphnodeconfigtype_string.go +++ b/terraform/graphnodeconfigtype_string.go @@ -1,4 +1,4 @@ -// generated by stringer -type=GraphNodeConfigType graph_config_node_type.go; DO NOT EDIT +// Code generated by "stringer -type=GraphNodeConfigType graph_config_node_type.go"; DO NOT EDIT package terraform diff --git a/terraform/instancetype_string.go b/terraform/instancetype_string.go index 3114bc157..f65414b34 100644 --- a/terraform/instancetype_string.go +++ b/terraform/instancetype_string.go @@ -1,4 +1,4 @@ -// generated by stringer -type=InstanceType instancetype.go; DO NOT EDIT +// Code generated by "stringer -type=InstanceType instancetype.go"; DO NOT EDIT package terraform diff --git a/terraform/walkoperation_string.go b/terraform/walkoperation_string.go index 1ce3661c4..0811fc874 100644 --- a/terraform/walkoperation_string.go +++ b/terraform/walkoperation_string.go @@ -1,4 +1,4 @@ -// generated by stringer -type=walkOperation graph_walk_operation.go; DO NOT EDIT +// Code generated by "stringer -type=walkOperation graph_walk_operation.go"; DO NOT EDIT package terraform From 796fdeef53d4653d4af983bc2ce879edf1dac4c0 Mon Sep 17 00:00:00 2001 From: "Michael H. Oshita" Date: Tue, 10 Nov 2015 01:42:59 +0900 Subject: [PATCH 62/95] Update rds_cluster.html.markdown I needed `db_subnet_group_name` in the rds_cluster resource as well when creating on a non-default VPC. https://github.com/hashicorp/terraform/pull/2935#issuecomment-133481106 --- website/source/docs/providers/aws/r/rds_cluster.html.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/website/source/docs/providers/aws/r/rds_cluster.html.markdown b/website/source/docs/providers/aws/r/rds_cluster.html.markdown index fb1f0dac8..c60e6ef29 100644 --- a/website/source/docs/providers/aws/r/rds_cluster.html.markdown +++ b/website/source/docs/providers/aws/r/rds_cluster.html.markdown @@ -63,6 +63,7 @@ Default: A 30-minute window selected at random from an 8-hour block of time per * `apply_immediately` - (Optional) Specifies whether any cluster modifications are applied immediately, or during the next maintenance window. Default is `false`. See [Amazon RDS Documentation for more information.](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html) +* `db_subnet_group_name` - (Optional) A DB subnet group to associate with this DB instance. ## Attributes Reference From 29636dc51d26110608ba3895803a5b60de938f52 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Mon, 9 Nov 2015 17:31:40 +0000 Subject: [PATCH 63/95] provider/openstack: Revert Security Group Rule Fix This commit reverts the patch from #3796. It has been discovered that multiple rules are being reported out of order when the configuration is applied multiple times. I feel this is a larger issue than the bug this patch originally fixed, so until I can resolve it, I am reverting the patch. --- .../resource_openstack_compute_secgroup_v2.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index 202c4d979..3cc0cbf0c 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -131,10 +131,10 @@ func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) err d.Set("description", sg.Description) rtm := rulesToMap(sg.Rules) for _, v := range rtm { - if v["from_group_id"] == d.Get("name") { - v["self"] = true + if v["group"] == d.Get("name") { + v["self"] = "1" } else { - v["self"] = false + v["self"] = "0" } } log.Printf("[DEBUG] rulesToMap(sg.Rules): %+v", rtm) @@ -283,12 +283,12 @@ func rulesToMap(sgrs []secgroups.Rule) []map[string]interface{} { sgrMap := make([]map[string]interface{}, len(sgrs)) for i, sgr := range sgrs { sgrMap[i] = map[string]interface{}{ - "id": sgr.ID, - "from_port": sgr.FromPort, - "to_port": sgr.ToPort, - "ip_protocol": sgr.IPProtocol, - "cidr": sgr.IPRange.CIDR, - "from_group_id": sgr.Group.Name, + "id": sgr.ID, + "from_port": sgr.FromPort, + "to_port": sgr.ToPort, + "ip_protocol": sgr.IPProtocol, + "cidr": sgr.IPRange.CIDR, + "group": sgr.Group.Name, } } return sgrMap From 3f06a7c56780fcf539d9ebd8d2313b4c37c22cbf Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Mon, 9 Nov 2015 10:58:06 -0700 Subject: [PATCH 64/95] Update CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f010a1042..554c1666a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,7 +61,6 @@ BUG FIXES: * provider/openstack: Fix boot from volume [GH-3206] * provider/openstack: Fix crashing when image is no longer accessible [GH-2189] * provider/openstack: Better handling of network resource state changes [GH-3712] - * provider/openstack: Fix issue preventing security group rules from being removed [GH-3796] * provider/openstack: Fix crashing when no security group is specified [GH-3801] ## 0.6.6 (October 23, 2015) From cf5cfdbff0da060c9f0e16164fba2f3d87f78023 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 9 Nov 2015 17:07:31 +0100 Subject: [PATCH 65/95] provider/aws: Set previously missing ARN in iam_saml_provider - fixes https://github.com/hashicorp/terraform/issues/3820 --- builtin/providers/aws/resource_aws_iam_saml_provider.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/providers/aws/resource_aws_iam_saml_provider.go b/builtin/providers/aws/resource_aws_iam_saml_provider.go index 6a166d711..3ca575efe 100644 --- a/builtin/providers/aws/resource_aws_iam_saml_provider.go +++ b/builtin/providers/aws/resource_aws_iam_saml_provider.go @@ -68,6 +68,7 @@ func resourceAwsIamSamlProviderRead(d *schema.ResourceData, meta interface{}) er } validUntil := out.ValidUntil.Format(time.RFC1123) + d.Set("arn", d.Id()) d.Set("valid_until", validUntil) d.Set("saml_metadata_document", *out.SAMLMetadataDocument) From 2a0334125c0db514bc1fb5aa1cb5df9845342f5e Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 9 Nov 2015 15:15:25 -0500 Subject: [PATCH 66/95] Add test attempting to reproduce #2598 This attempts to reproduce the issue described in #2598 whereby outputs added after an apply are not reflected in the output. As per the issue the outputs are described using the JSON syntax. --- terraform/context_apply_test.go | 49 +++++++++++++++++++ terraform/terraform_test.go | 16 ++++++ .../apply-output-add-after/main.tf | 6 +++ .../apply-output-add-after/outputs.tf.json | 10 ++++ .../apply-output-add-before/main.tf | 6 +++ .../apply-output-add-before/outputs.tf.json | 7 +++ 6 files changed, 94 insertions(+) create mode 100644 terraform/test-fixtures/apply-output-add-after/main.tf create mode 100644 terraform/test-fixtures/apply-output-add-after/outputs.tf.json create mode 100644 terraform/test-fixtures/apply-output-add-before/main.tf create mode 100644 terraform/test-fixtures/apply-output-add-before/outputs.tf.json diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 1fd069db0..4060dd3e3 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -2851,6 +2851,55 @@ func TestContext2Apply_outputInvalid(t *testing.T) { } } +func TestContext2Apply_outputAdd(t *testing.T) { + m1 := testModule(t, "apply-output-add-before") + p1 := testProvider("aws") + p1.ApplyFn = testApplyFn + p1.DiffFn = testDiffFn + ctx1 := testContext2(t, &ContextOpts{ + Module: m1, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p1), + }, + }) + + if _, err := ctx1.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state1, err := ctx1.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + m2 := testModule(t, "apply-output-add-after") + p2 := testProvider("aws") + p2.ApplyFn = testApplyFn + p2.DiffFn = testDiffFn + ctx2 := testContext2(t, &ContextOpts{ + Module: m2, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p2), + }, + State: state1, + }) + + if _, err := ctx2.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state2, err := ctx2.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state2.String()) + expected := strings.TrimSpace(testTerraformApplyOutputAddStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + func TestContext2Apply_outputList(t *testing.T) { m := testModule(t, "apply-output-list") p := testProvider("aws") diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index d17726acb..3b1653f43 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -575,6 +575,22 @@ Outputs: foo_num = 2 ` +const testTerraformApplyOutputAddStr = ` +aws_instance.test.0: + ID = foo + foo = foo0 + type = aws_instance +aws_instance.test.1: + ID = foo + foo = foo1 + type = aws_instance + +Outputs: + +firstOutput = foo0 +secondOutput = foo1 +` + const testTerraformApplyOutputListStr = ` aws_instance.bar.0: ID = foo diff --git a/terraform/test-fixtures/apply-output-add-after/main.tf b/terraform/test-fixtures/apply-output-add-after/main.tf new file mode 100644 index 000000000..1c10eaafc --- /dev/null +++ b/terraform/test-fixtures/apply-output-add-after/main.tf @@ -0,0 +1,6 @@ +provider "aws" {} + +resource "aws_instance" "test" { + foo = "${format("foo%d", count.index)}" + count = 2 +} diff --git a/terraform/test-fixtures/apply-output-add-after/outputs.tf.json b/terraform/test-fixtures/apply-output-add-after/outputs.tf.json new file mode 100644 index 000000000..32e96b0ee --- /dev/null +++ b/terraform/test-fixtures/apply-output-add-after/outputs.tf.json @@ -0,0 +1,10 @@ +{ + "output": { + "firstOutput": { + "value": "${aws_instance.test.0.foo}" + }, + "secondOutput": { + "value": "${aws_instance.test.1.foo}" + } + } +} diff --git a/terraform/test-fixtures/apply-output-add-before/main.tf b/terraform/test-fixtures/apply-output-add-before/main.tf new file mode 100644 index 000000000..1c10eaafc --- /dev/null +++ b/terraform/test-fixtures/apply-output-add-before/main.tf @@ -0,0 +1,6 @@ +provider "aws" {} + +resource "aws_instance" "test" { + foo = "${format("foo%d", count.index)}" + count = 2 +} diff --git a/terraform/test-fixtures/apply-output-add-before/outputs.tf.json b/terraform/test-fixtures/apply-output-add-before/outputs.tf.json new file mode 100644 index 000000000..238668ef3 --- /dev/null +++ b/terraform/test-fixtures/apply-output-add-before/outputs.tf.json @@ -0,0 +1,7 @@ +{ + "output": { + "firstOutput": { + "value": "${aws_instance.test.0.foo}" + } + } +} From 2694022b4acc3bad411d03ca8bff0a05d9a595f4 Mon Sep 17 00:00:00 2001 From: clint shryock Date: Mon, 9 Nov 2015 14:33:20 -0600 Subject: [PATCH 67/95] Fix a panic that could occur if no ECS Cluster was found for a given cluster name --- builtin/providers/aws/resource_aws_ecs_cluster.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/builtin/providers/aws/resource_aws_ecs_cluster.go b/builtin/providers/aws/resource_aws_ecs_cluster.go index 7f5d0ea1e..f9e3a4abb 100644 --- a/builtin/providers/aws/resource_aws_ecs_cluster.go +++ b/builtin/providers/aws/resource_aws_ecs_cluster.go @@ -59,9 +59,16 @@ func resourceAwsEcsClusterRead(d *schema.ResourceData, meta interface{}) error { } log.Printf("[DEBUG] Received ECS clusters: %s", out.Clusters) - d.SetId(*out.Clusters[0].ClusterArn) - d.Set("name", *out.Clusters[0].ClusterName) + for _, c := range out.Clusters { + if *c.ClusterName == clusterName { + d.SetId(*c.ClusterArn) + d.Set("name", c.ClusterName) + return nil + } + } + log.Printf("[ERR] No matching ECS Cluster found for (%s)", d.Id()) + d.SetId("") return nil } From fc983c5505c8f332f7f199afa6fb48222c88f18a Mon Sep 17 00:00:00 2001 From: stack72 Date: Mon, 9 Nov 2015 22:26:55 +0000 Subject: [PATCH 68/95] Initial Create, Read and Delete work for the S3 part of the Kinesis Firehose resource --- builtin/providers/aws/config.go | 5 + builtin/providers/aws/provider.go | 203 ++++++++--------- .../resource_aws_kinesis_firehose_stream.go | 210 ++++++++++++++++++ 3 files changed, 317 insertions(+), 101 deletions(-) create mode 100644 builtin/providers/aws/resource_aws_kinesis_firehose_stream.go diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 3e835c106..bd7b08924 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -27,6 +27,7 @@ import ( "github.com/aws/aws-sdk-go/service/elasticache" elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice" "github.com/aws/aws-sdk-go/service/elb" + "github.com/aws/aws-sdk-go/service/firehose" "github.com/aws/aws-sdk-go/service/glacier" "github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/kinesis" @@ -74,6 +75,7 @@ type AWSClient struct { rdsconn *rds.RDS iamconn *iam.IAM kinesisconn *kinesis.Kinesis + firehoseconn *firehose.Firehose elasticacheconn *elasticache.ElastiCache lambdaconn *lambda.Lambda opsworksconn *opsworks.OpsWorks @@ -168,6 +170,9 @@ func (c *Config) Client() (interface{}, error) { errs = append(errs, authErr) } + log.Println("[INFO] Initializing Kinesis Firehose Connection") + client.firehoseconn = autoscaling.New(sess) + log.Println("[INFO] Initializing AutoScaling connection") client.autoscalingconn = autoscaling.New(sess) diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index b5392429a..dfa1565fc 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -163,107 +163,108 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "aws_ami": resourceAwsAmi(), - "aws_ami_copy": resourceAwsAmiCopy(), - "aws_ami_from_instance": resourceAwsAmiFromInstance(), - "aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(), - "aws_autoscaling_group": resourceAwsAutoscalingGroup(), - "aws_autoscaling_notification": resourceAwsAutoscalingNotification(), - "aws_autoscaling_policy": resourceAwsAutoscalingPolicy(), - "aws_cloudformation_stack": resourceAwsCloudFormationStack(), - "aws_cloudtrail": resourceAwsCloudTrail(), - "aws_cloudwatch_log_group": resourceAwsCloudWatchLogGroup(), - "aws_autoscaling_lifecycle_hook": resourceAwsAutoscalingLifecycleHook(), - "aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(), - "aws_codedeploy_app": resourceAwsCodeDeployApp(), - "aws_codedeploy_deployment_group": resourceAwsCodeDeployDeploymentGroup(), - "aws_codecommit_repository": resourceAwsCodeCommitRepository(), - "aws_customer_gateway": resourceAwsCustomerGateway(), - "aws_db_instance": resourceAwsDbInstance(), - "aws_db_parameter_group": resourceAwsDbParameterGroup(), - "aws_db_security_group": resourceAwsDbSecurityGroup(), - "aws_db_subnet_group": resourceAwsDbSubnetGroup(), - "aws_directory_service_directory": resourceAwsDirectoryServiceDirectory(), - "aws_dynamodb_table": resourceAwsDynamoDbTable(), - "aws_ebs_volume": resourceAwsEbsVolume(), - "aws_ecs_cluster": resourceAwsEcsCluster(), - "aws_ecs_service": resourceAwsEcsService(), - "aws_ecs_task_definition": resourceAwsEcsTaskDefinition(), - "aws_efs_file_system": resourceAwsEfsFileSystem(), - "aws_efs_mount_target": resourceAwsEfsMountTarget(), - "aws_eip": resourceAwsEip(), - "aws_elasticache_cluster": resourceAwsElasticacheCluster(), - "aws_elasticache_parameter_group": resourceAwsElasticacheParameterGroup(), - "aws_elasticache_security_group": resourceAwsElasticacheSecurityGroup(), - "aws_elasticache_subnet_group": resourceAwsElasticacheSubnetGroup(), - "aws_elasticsearch_domain": resourceAwsElasticSearchDomain(), - "aws_elb": resourceAwsElb(), - "aws_flow_log": resourceAwsFlowLog(), - "aws_glacier_vault": resourceAwsGlacierVault(), - "aws_iam_access_key": resourceAwsIamAccessKey(), - "aws_iam_group_policy": resourceAwsIamGroupPolicy(), - "aws_iam_group": resourceAwsIamGroup(), - "aws_iam_group_membership": resourceAwsIamGroupMembership(), - "aws_iam_instance_profile": resourceAwsIamInstanceProfile(), - "aws_iam_policy": resourceAwsIamPolicy(), - "aws_iam_policy_attachment": resourceAwsIamPolicyAttachment(), - "aws_iam_role_policy": resourceAwsIamRolePolicy(), - "aws_iam_role": resourceAwsIamRole(), - "aws_iam_saml_provider": resourceAwsIamSamlProvider(), - "aws_iam_server_certificate": resourceAwsIAMServerCertificate(), - "aws_iam_user_policy": resourceAwsIamUserPolicy(), - "aws_iam_user": resourceAwsIamUser(), - "aws_instance": resourceAwsInstance(), - "aws_internet_gateway": resourceAwsInternetGateway(), - "aws_key_pair": resourceAwsKeyPair(), - "aws_kinesis_stream": resourceAwsKinesisStream(), - "aws_lambda_function": resourceAwsLambdaFunction(), - "aws_launch_configuration": resourceAwsLaunchConfiguration(), - "aws_lb_cookie_stickiness_policy": resourceAwsLBCookieStickinessPolicy(), - "aws_main_route_table_association": resourceAwsMainRouteTableAssociation(), - "aws_network_acl": resourceAwsNetworkAcl(), - "aws_network_interface": resourceAwsNetworkInterface(), - "aws_opsworks_stack": resourceAwsOpsworksStack(), - "aws_opsworks_java_app_layer": resourceAwsOpsworksJavaAppLayer(), - "aws_opsworks_haproxy_layer": resourceAwsOpsworksHaproxyLayer(), - "aws_opsworks_static_web_layer": resourceAwsOpsworksStaticWebLayer(), - "aws_opsworks_php_app_layer": resourceAwsOpsworksPhpAppLayer(), - "aws_opsworks_rails_app_layer": resourceAwsOpsworksRailsAppLayer(), - "aws_opsworks_nodejs_app_layer": resourceAwsOpsworksNodejsAppLayer(), - "aws_opsworks_memcached_layer": resourceAwsOpsworksMemcachedLayer(), - "aws_opsworks_mysql_layer": resourceAwsOpsworksMysqlLayer(), - "aws_opsworks_ganglia_layer": resourceAwsOpsworksGangliaLayer(), - "aws_opsworks_custom_layer": resourceAwsOpsworksCustomLayer(), - "aws_placement_group": resourceAwsPlacementGroup(), - "aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(), - "aws_rds_cluster": resourceAwsRDSCluster(), - "aws_rds_cluster_instance": resourceAwsRDSClusterInstance(), - "aws_route53_delegation_set": resourceAwsRoute53DelegationSet(), - "aws_route53_record": resourceAwsRoute53Record(), - "aws_route53_zone_association": resourceAwsRoute53ZoneAssociation(), - "aws_route53_zone": resourceAwsRoute53Zone(), - "aws_route53_health_check": resourceAwsRoute53HealthCheck(), - "aws_route": resourceAwsRoute(), - "aws_route_table": resourceAwsRouteTable(), - "aws_route_table_association": resourceAwsRouteTableAssociation(), - "aws_s3_bucket": resourceAwsS3Bucket(), - "aws_s3_bucket_object": resourceAwsS3BucketObject(), - "aws_security_group": resourceAwsSecurityGroup(), - "aws_security_group_rule": resourceAwsSecurityGroupRule(), - "aws_spot_instance_request": resourceAwsSpotInstanceRequest(), - "aws_sqs_queue": resourceAwsSqsQueue(), - "aws_sns_topic": resourceAwsSnsTopic(), - "aws_sns_topic_subscription": resourceAwsSnsTopicSubscription(), - "aws_subnet": resourceAwsSubnet(), - "aws_volume_attachment": resourceAwsVolumeAttachment(), - "aws_vpc_dhcp_options_association": resourceAwsVpcDhcpOptionsAssociation(), - "aws_vpc_dhcp_options": resourceAwsVpcDhcpOptions(), - "aws_vpc_peering_connection": resourceAwsVpcPeeringConnection(), - "aws_vpc": resourceAwsVpc(), - "aws_vpc_endpoint": resourceAwsVpcEndpoint(), - "aws_vpn_connection": resourceAwsVpnConnection(), - "aws_vpn_connection_route": resourceAwsVpnConnectionRoute(), - "aws_vpn_gateway": resourceAwsVpnGateway(), + "aws_ami": resourceAwsAmi(), + "aws_ami_copy": resourceAwsAmiCopy(), + "aws_ami_from_instance": resourceAwsAmiFromInstance(), + "aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(), + "aws_autoscaling_group": resourceAwsAutoscalingGroup(), + "aws_autoscaling_notification": resourceAwsAutoscalingNotification(), + "aws_autoscaling_policy": resourceAwsAutoscalingPolicy(), + "aws_cloudformation_stack": resourceAwsCloudFormationStack(), + "aws_cloudtrail": resourceAwsCloudTrail(), + "aws_cloudwatch_log_group": resourceAwsCloudWatchLogGroup(), + "aws_autoscaling_lifecycle_hook": resourceAwsAutoscalingLifecycleHook(), + "aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(), + "aws_codedeploy_app": resourceAwsCodeDeployApp(), + "aws_codedeploy_deployment_group": resourceAwsCodeDeployDeploymentGroup(), + "aws_codecommit_repository": resourceAwsCodeCommitRepository(), + "aws_customer_gateway": resourceAwsCustomerGateway(), + "aws_db_instance": resourceAwsDbInstance(), + "aws_db_parameter_group": resourceAwsDbParameterGroup(), + "aws_db_security_group": resourceAwsDbSecurityGroup(), + "aws_db_subnet_group": resourceAwsDbSubnetGroup(), + "aws_directory_service_directory": resourceAwsDirectoryServiceDirectory(), + "aws_dynamodb_table": resourceAwsDynamoDbTable(), + "aws_ebs_volume": resourceAwsEbsVolume(), + "aws_ecs_cluster": resourceAwsEcsCluster(), + "aws_ecs_service": resourceAwsEcsService(), + "aws_ecs_task_definition": resourceAwsEcsTaskDefinition(), + "aws_efs_file_system": resourceAwsEfsFileSystem(), + "aws_efs_mount_target": resourceAwsEfsMountTarget(), + "aws_eip": resourceAwsEip(), + "aws_elasticache_cluster": resourceAwsElasticacheCluster(), + "aws_elasticache_parameter_group": resourceAwsElasticacheParameterGroup(), + "aws_elasticache_security_group": resourceAwsElasticacheSecurityGroup(), + "aws_elasticache_subnet_group": resourceAwsElasticacheSubnetGroup(), + "aws_elasticsearch_domain": resourceAwsElasticSearchDomain(), + "aws_elb": resourceAwsElb(), + "aws_flow_log": resourceAwsFlowLog(), + "aws_glacier_vault": resourceAwsGlacierVault(), + "aws_iam_access_key": resourceAwsIamAccessKey(), + "aws_iam_group_policy": resourceAwsIamGroupPolicy(), + "aws_iam_group": resourceAwsIamGroup(), + "aws_iam_group_membership": resourceAwsIamGroupMembership(), + "aws_iam_instance_profile": resourceAwsIamInstanceProfile(), + "aws_iam_policy": resourceAwsIamPolicy(), + "aws_iam_policy_attachment": resourceAwsIamPolicyAttachment(), + "aws_iam_role_policy": resourceAwsIamRolePolicy(), + "aws_iam_role": resourceAwsIamRole(), + "aws_iam_saml_provider": resourceAwsIamSamlProvider(), + "aws_iam_server_certificate": resourceAwsIAMServerCertificate(), + "aws_iam_user_policy": resourceAwsIamUserPolicy(), + "aws_iam_user": resourceAwsIamUser(), + "aws_instance": resourceAwsInstance(), + "aws_internet_gateway": resourceAwsInternetGateway(), + "aws_key_pair": resourceAwsKeyPair(), + "aws_kinesis_firehose_delivery_stream": resourceAwsKinesisFirehoseDeliveryStream(), + "aws_kinesis_stream": resourceAwsKinesisStream(), + "aws_lambda_function": resourceAwsLambdaFunction(), + "aws_launch_configuration": resourceAwsLaunchConfiguration(), + "aws_lb_cookie_stickiness_policy": resourceAwsLBCookieStickinessPolicy(), + "aws_main_route_table_association": resourceAwsMainRouteTableAssociation(), + "aws_network_acl": resourceAwsNetworkAcl(), + "aws_network_interface": resourceAwsNetworkInterface(), + "aws_opsworks_stack": resourceAwsOpsworksStack(), + "aws_opsworks_java_app_layer": resourceAwsOpsworksJavaAppLayer(), + "aws_opsworks_haproxy_layer": resourceAwsOpsworksHaproxyLayer(), + "aws_opsworks_static_web_layer": resourceAwsOpsworksStaticWebLayer(), + "aws_opsworks_php_app_layer": resourceAwsOpsworksPhpAppLayer(), + "aws_opsworks_rails_app_layer": resourceAwsOpsworksRailsAppLayer(), + "aws_opsworks_nodejs_app_layer": resourceAwsOpsworksNodejsAppLayer(), + "aws_opsworks_memcached_layer": resourceAwsOpsworksMemcachedLayer(), + "aws_opsworks_mysql_layer": resourceAwsOpsworksMysqlLayer(), + "aws_opsworks_ganglia_layer": resourceAwsOpsworksGangliaLayer(), + "aws_opsworks_custom_layer": resourceAwsOpsworksCustomLayer(), + "aws_placement_group": resourceAwsPlacementGroup(), + "aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(), + "aws_rds_cluster": resourceAwsRDSCluster(), + "aws_rds_cluster_instance": resourceAwsRDSClusterInstance(), + "aws_route53_delegation_set": resourceAwsRoute53DelegationSet(), + "aws_route53_record": resourceAwsRoute53Record(), + "aws_route53_zone_association": resourceAwsRoute53ZoneAssociation(), + "aws_route53_zone": resourceAwsRoute53Zone(), + "aws_route53_health_check": resourceAwsRoute53HealthCheck(), + "aws_route": resourceAwsRoute(), + "aws_route_table": resourceAwsRouteTable(), + "aws_route_table_association": resourceAwsRouteTableAssociation(), + "aws_s3_bucket": resourceAwsS3Bucket(), + "aws_s3_bucket_object": resourceAwsS3BucketObject(), + "aws_security_group": resourceAwsSecurityGroup(), + "aws_security_group_rule": resourceAwsSecurityGroupRule(), + "aws_spot_instance_request": resourceAwsSpotInstanceRequest(), + "aws_sqs_queue": resourceAwsSqsQueue(), + "aws_sns_topic": resourceAwsSnsTopic(), + "aws_sns_topic_subscription": resourceAwsSnsTopicSubscription(), + "aws_subnet": resourceAwsSubnet(), + "aws_volume_attachment": resourceAwsVolumeAttachment(), + "aws_vpc_dhcp_options_association": resourceAwsVpcDhcpOptionsAssociation(), + "aws_vpc_dhcp_options": resourceAwsVpcDhcpOptions(), + "aws_vpc_peering_connection": resourceAwsVpcPeeringConnection(), + "aws_vpc": resourceAwsVpc(), + "aws_vpc_endpoint": resourceAwsVpcEndpoint(), + "aws_vpn_connection": resourceAwsVpnConnection(), + "aws_vpn_connection_route": resourceAwsVpnConnectionRoute(), + "aws_vpn_gateway": resourceAwsVpnGateway(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/aws/resource_aws_kinesis_firehose_stream.go b/builtin/providers/aws/resource_aws_kinesis_firehose_stream.go new file mode 100644 index 000000000..320e078b2 --- /dev/null +++ b/builtin/providers/aws/resource_aws_kinesis_firehose_stream.go @@ -0,0 +1,210 @@ +package aws + +import ( + "fmt" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/firehose" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsKinesisFirehoseDeliveryStream() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsKinesisFirehoseDeliveryStreamCreate, + Read: resourceAwsKinesisFirehoseDeliveryStreamRead, + Update: resourceAwsKinesisFirehoseDeliveryStreamUpdate, + Delete: resourceAwsKinesisFirehoseDeliveryStreamDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "destination": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "role_arn": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "s3_bucket_arn": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "s3_prefix": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "s3_buffer_size": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 5, + }, + + "s3_buffer_interval": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 300, + }, + + "s3_data_compression": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "UNCOMPRESSED", + }, + + "arn": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceAwsKinesisFirehoseDeliveryStreamCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).firehoseconn + + sn := d.Get("name").(string) + input := &firehose.CreateDeliveryStreamInput{ + DeliveryStreamName: aws.String(sn), + } + + s3_config := &firehose.S3DestinationConfiguration{ + BucketARN: aws.String(d.Get("s3_bucket_arn").(string)), + RoleARN: aws.String(d.Get("role_arn").(string)), + BufferingHints: &firehose.BufferingHints{ + IntervalInSeconds: aws.Int64(int64(d.Get("s3_buffer_interval").(int))), + SizeInMBs: aws.Int64(int64(d.Get("s3_buffer_size").(int))), + }, + CompressionFormat: aws.String(d.Get("s3_data_compression").(string)), + } + if v, ok := d.GetOk("s3_prefix"); ok { + s3_config.Prefix = aws.String(v.(string)) + } + + input.S3DestinationConfiguration = s3_config + + _, err := conn.CreateDeliveryStream(input) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + return fmt.Errorf("[WARN] Error creating Kinesis Firehose Delivery Stream: \"%s\", code: \"%s\"", awsErr.Message(), awsErr.Code()) + } + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"CREATING"}, + Target: "ACTIVE", + Refresh: firehoseStreamStateRefreshFunc(conn, sn), + Timeout: 5 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + firehoseStream, err := stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for Kinesis Stream (%s) to become active: %s", + sn, err) + } + + s := firehoseStream.(*firehose.DeliveryStreamDescription) + d.SetId(*s.DeliveryStreamARN) + d.Set("arn", s.DeliveryStreamARN) + + return resourceAwsKinesisStreamUpdate(d, meta) +} + +func resourceAwsKinesisFirehoseDeliveryStreamUpdate(d *schema.ResourceData, meta interface{}) error { + // conn := meta.(*AWSClient).firehoseconn + + return resourceAwsKinesisFirehoseDeliveryStreamRead(d, meta) +} + +func resourceAwsKinesisFirehoseDeliveryStreamRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).firehoseconn + sn := d.Get("name").(string) + describeOpts := &firehose.DeliveryStreamDescription{ + DeliveryStreamName: aws.String(sn), + } + resp, err := conn.DescribeDeliveryStream(describeOpts) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + if awsErr.Code() == "ResourceNotFoundException" { + d.SetId("") + return nil + } + return fmt.Errorf("[WARN] Error reading Kinesis Firehose Delivery Stream: \"%s\", code: \"%s\"", awsErr.Message(), awsErr.Code()) + } + return err + } + + s := resp.DeliveryStreamDescription + d.Set("arn", *s.DeliveryStreamARN) + + return nil +} + +func resourceAwsKinesisFirehoseDeliveryStreamDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).firehoseconn + + sn := d.Get("name").(string) + _, err := conn.DeleteDeliveryStream(&firehose.DeleteDeliveryStreamInput{ + DeliveryStreamName: aws.String(sn), + }) + + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"DELETING"}, + Target: "DESTROYED", + Refresh: firehoseStreamStateRefreshFunc(conn, sn), + Timeout: 5 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for Delivery Stream (%s) to be destroyed: %s", + sn, err) + } + + d.SetId("") + return nil +} + +func firehoseStreamStateRefreshFunc(conn *firehose.Firehose, sn string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + describeOpts := &firehose.DescribeDeliveryStreamInput{ + DeliveryStreamName: aws.String(sn), + } + resp, err := conn.DescribeDeliveryStream(describeOpts) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + if awsErr.Code() == "ResourceNotFoundException" { + return 42, "DESTROYED", nil + } + return nil, awsErr.Code(), err + } + return nil, "failed", err + } + + return resp.DeliveryStreamDescription, *resp.DeliveryStreamDescription.DeliveryStreamStatus, nil + } +} From 64d982ac9ed2fe17a50967952bfce5e47211139d Mon Sep 17 00:00:00 2001 From: mcinteer Date: Tue, 10 Nov 2015 11:27:45 +1300 Subject: [PATCH 69/95] Change the docs as the token can be explicitly set This tripped me up today when I was trying to connect using MFA. I had a look at the source and found the token property, tested it out and low and behold it worked! Hopefully this saves someone else going through the same pain --- website/source/docs/providers/aws/index.html.markdown | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/source/docs/providers/aws/index.html.markdown b/website/source/docs/providers/aws/index.html.markdown index 05efd5700..7199111c2 100644 --- a/website/source/docs/providers/aws/index.html.markdown +++ b/website/source/docs/providers/aws/index.html.markdown @@ -59,5 +59,4 @@ The following arguments are supported in the `provider` block: * `kinesis_endpoint` - (Optional) Use this to override the default endpoint URL constructed from the `region`. It's typically used to connect to kinesalite. -In addition to the above parameters, the `AWS_SESSION_TOKEN` environmental -variable can be set to set an MFA token. +* `token` - (Optional) Use this to set an MFA token. It can also be sourced from the `AWS_SECURITY_TOKEN` environment variable. From 5dfa9ac8234457b3b80eabe6ad7aecb8decc883b Mon Sep 17 00:00:00 2001 From: stack72 Date: Mon, 9 Nov 2015 22:44:26 +0000 Subject: [PATCH 70/95] Adding the shell for the acceptance tests for the AWS Kinesis Firehose work --- ...e_aws_kinesis_firehose_delivery_stream.go} | 0 ...s_kinesis_firehose_delivery_stream_test.go | 106 ++++++++++++++++++ 2 files changed, 106 insertions(+) rename builtin/providers/aws/{resource_aws_kinesis_firehose_stream.go => resource_aws_kinesis_firehose_delivery_stream.go} (100%) create mode 100644 builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream_test.go diff --git a/builtin/providers/aws/resource_aws_kinesis_firehose_stream.go b/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream.go similarity index 100% rename from builtin/providers/aws/resource_aws_kinesis_firehose_stream.go rename to builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream.go diff --git a/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream_test.go b/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream_test.go new file mode 100644 index 000000000..1414f7ae6 --- /dev/null +++ b/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream_test.go @@ -0,0 +1,106 @@ +package aws + +import ( + "fmt" + "math/rand" + "strings" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/firehose" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSKinesisFirehoseDeliveryStream_basic(t *testing.T) { + var stream firehose.DeliveryStreamDescription + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckKinesisFirehoseDeliveryStreamDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccKinesisFirehoseDeliveryStreamConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckKinesisFirehoseDeliveryStreamExists("aws_kinesis_firehose_delivery_stream.test_stream", &stream), + testAccCheckAWSKinesisFirehoseDeliveryStreamAttributes(&stream), + ), + }, + }, + }) +} + +func testAccCheckKinesisFirehoseDeliveryStreamExists(n string, stream *firehose.DeliveryStreamDescription) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Kinesis Firehose ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).firehoseconn + describeOpts := &firehose.DescribeDeliveryStreamInput{ + DeliveryStreamName: aws.String(rs.Primary.Attributes["name"]), + } + resp, err := conn.DescribeDeliveryStream(describeOpts) + if err != nil { + return err + } + + *stream = *resp.DeliveryStreamDescription + + return nil + } +} + +func testAccCheckAWSKinesisFirehoseDeliveryStreamAttributes(stream *firehose.DeliveryStreamDescription) resource.TestCheckFunc { + return func(s *terraform.State) error { + if !strings.HasPrefix(*stream.DeliveryStreamName, "terraform-kinesis-firehose-test") { + return fmt.Errorf("Bad Stream name: %s", *stream.DeliveryStreamName) + } + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_kinesis_firehose_delivery_stream" { + continue + } + if *stream.DeliveryStreamARN != rs.Primary.Attributes["arn"] { + return fmt.Errorf("Bad Delivery Stream ARN\n\t expected: %s\n\tgot: %s\n", rs.Primary.Attributes["arn"], *stream.DeliveryStreamARN) + } + } + return nil + } +} + +func testAccCheckKinesisFirehoseDeliveryStreamDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_kinesis_firehose_delivery_stream" { + continue + } + conn := testAccProvider.Meta().(*AWSClient).firehoseconn + describeOpts := &firehose.DescribeDeliveryStreamInput{ + DeliveryStreamName: aws.String(rs.Primary.Attributes["name"]), + } + resp, err := conn.DescribeDeliveryStream(describeOpts) + if err == nil { + if resp.DeliveryStreamDescription != nil && *resp.DeliveryStreamDescription.DeliveryStreamStatus != "DELETING" { + return fmt.Errorf("Error: Delivery Stream still exists") + } + } + + return nil + + } + + return nil +} + +var testAccKinesisFirehoseDeliveryStreamConfig = fmt.Sprintf(` +resource "aws_kinesis_firehose_delivery_stream" "test_stream" { + name = "terraform-kinesis-firehose-test-%d" + +} +`, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) From 22876be830dbd0a4fee1ed268c92fe2db30790ff Mon Sep 17 00:00:00 2001 From: "Michael H. Oshita" Date: Mon, 9 Nov 2015 23:59:09 +0900 Subject: [PATCH 71/95] Document `license_model` as supported argument Required for Oracle SE1 --- website/source/docs/providers/aws/r/db_instance.html.markdown | 1 + 1 file changed, 1 insertion(+) 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 499e13ba4..15122d9dd 100644 --- a/website/source/docs/providers/aws/r/db_instance.html.markdown +++ b/website/source/docs/providers/aws/r/db_instance.html.markdown @@ -81,6 +81,7 @@ database, and to use this value as the source database. This correlates to the [Working with PostgreSQL and MySQL Read Replicas](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ReadRepl.html) for more information on using Replication. * `snapshot_identifier` - (Optional) Specifies whether or not to create this database from a snapshot. This correlates to the snapshot ID you'd find in the RDS console, e.g: rds:production-2015-06-26-06-05. +* `license_model` - (Optional, but required for some DB engines, i.e. Oracle SE1) License model information for this DB instance. ~> **NOTE:** Removing the `replicate_source_db` attribute from an existing RDS Replicate database managed by Terraform will promote the database to a fully From d14d891367b2e13cef77f5c986473fdf1af8ba02 Mon Sep 17 00:00:00 2001 From: stack72 Date: Tue, 10 Nov 2015 16:24:33 +0000 Subject: [PATCH 72/95] Finishing the first pass at Kinesis Firehose. I have only implemented the S3 configuration right now as Terraform doesn't include RedShift support --- builtin/providers/aws/config.go | 2 +- ...ce_aws_kinesis_firehose_delivery_stream.go | 77 ++++++++++++++- ...s_kinesis_firehose_delivery_stream_test.go | 97 +++++++++++++++++-- 3 files changed, 165 insertions(+), 11 deletions(-) diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index bd7b08924..d8a9ff862 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -171,7 +171,7 @@ func (c *Config) Client() (interface{}, error) { } log.Println("[INFO] Initializing Kinesis Firehose Connection") - client.firehoseconn = autoscaling.New(sess) + client.firehoseconn = firehose.New(sess) log.Println("[INFO] Initializing AutoScaling connection") client.autoscalingconn = autoscaling.New(sess) diff --git a/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream.go b/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream.go index 320e078b2..c1b973454 100644 --- a/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream.go +++ b/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "strings" "time" "github.com/aws/aws-sdk-go/aws" @@ -29,6 +30,10 @@ func resourceAwsKinesisFirehoseDeliveryStream() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, + StateFunc: func(v interface{}) string { + value := v.(string) + return strings.ToLower(value) + }, }, "role_arn": &schema.Schema{ @@ -69,6 +74,18 @@ func resourceAwsKinesisFirehoseDeliveryStream() *schema.Resource { Optional: true, Computed: true, }, + + "version_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "destination_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, }, } } @@ -76,6 +93,10 @@ func resourceAwsKinesisFirehoseDeliveryStream() *schema.Resource { func resourceAwsKinesisFirehoseDeliveryStreamCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).firehoseconn + if d.Get("destination").(string) != "s3" { + return fmt.Errorf("[ERROR] AWS Kinesis Firehose only supports S3 destinations for the first implementation") + } + sn := d.Get("name").(string) input := &firehose.CreateDeliveryStreamInput{ DeliveryStreamName: aws.String(sn), @@ -124,11 +145,56 @@ func resourceAwsKinesisFirehoseDeliveryStreamCreate(d *schema.ResourceData, meta d.SetId(*s.DeliveryStreamARN) d.Set("arn", s.DeliveryStreamARN) - return resourceAwsKinesisStreamUpdate(d, meta) + return resourceAwsKinesisFirehoseDeliveryStreamRead(d, meta) } func resourceAwsKinesisFirehoseDeliveryStreamUpdate(d *schema.ResourceData, meta interface{}) error { - // conn := meta.(*AWSClient).firehoseconn + conn := meta.(*AWSClient).firehoseconn + + if d.Get("destination").(string) != "s3" { + return fmt.Errorf("[ERROR] AWS Kinesis Firehose only supports S3 destinations for the first implementation") + } + + sn := d.Get("name").(string) + s3_config := &firehose.S3DestinationUpdate{} + + if d.HasChange("role_arn") { + s3_config.RoleARN = aws.String(d.Get("role_arn").(string)) + } + + if d.HasChange("s3_bucket_arn") { + s3_config.BucketARN = aws.String(d.Get("s3_bucket_arn").(string)) + } + + if d.HasChange("s3_prefix") { + s3_config.Prefix = aws.String(d.Get("s3_prefix").(string)) + } + + if d.HasChange("s3_data_compression") { + s3_config.CompressionFormat = aws.String(d.Get("s3_data_compression").(string)) + } + + if d.HasChange("s3_buffer_interval") || d.HasChange("s3_buffer_size") { + bufferingHints := &firehose.BufferingHints{ + IntervalInSeconds: aws.Int64(int64(d.Get("s3_buffer_interval").(int))), + SizeInMBs: aws.Int64(int64(d.Get("s3_buffer_size").(int))), + } + s3_config.BufferingHints = bufferingHints + } + + destOpts := &firehose.UpdateDestinationInput{ + DeliveryStreamName: aws.String(sn), + CurrentDeliveryStreamVersionId: aws.String(d.Get("version_id").(string)), + DestinationId: aws.String(d.Get("destination_id").(string)), + S3DestinationUpdate: s3_config, + } + + _, err := conn.UpdateDestination(destOpts) + if err != nil { + return fmt.Errorf( + "Error Updating Kinesis Firehose Delivery Stream: %s", + sn, err) + } return resourceAwsKinesisFirehoseDeliveryStreamRead(d, meta) } @@ -136,7 +202,7 @@ func resourceAwsKinesisFirehoseDeliveryStreamUpdate(d *schema.ResourceData, meta func resourceAwsKinesisFirehoseDeliveryStreamRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).firehoseconn sn := d.Get("name").(string) - describeOpts := &firehose.DeliveryStreamDescription{ + describeOpts := &firehose.DescribeDeliveryStreamInput{ DeliveryStreamName: aws.String(sn), } resp, err := conn.DescribeDeliveryStream(describeOpts) @@ -152,7 +218,12 @@ func resourceAwsKinesisFirehoseDeliveryStreamRead(d *schema.ResourceData, meta i } s := resp.DeliveryStreamDescription + d.Set("version_id", s.VersionId) d.Set("arn", *s.DeliveryStreamARN) + if len(s.Destinations) > 0 { + destination := s.Destinations[0] + d.Set("destination_id", *destination.DestinationId) + } return nil } diff --git a/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream_test.go b/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream_test.go index 1414f7ae6..611e196ce 100644 --- a/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream_test.go +++ b/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream_test.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "log" "math/rand" "strings" "testing" @@ -16,13 +17,16 @@ import ( func TestAccAWSKinesisFirehoseDeliveryStream_basic(t *testing.T) { var stream firehose.DeliveryStreamDescription + ri := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + config := fmt.Sprintf(testAccKinesisFirehoseDeliveryStreamConfig_basic, ri, ri) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckKinesisFirehoseDeliveryStreamDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccKinesisFirehoseDeliveryStreamConfig, + Config: config, Check: resource.ComposeTestCheckFunc( testAccCheckKinesisFirehoseDeliveryStreamExists("aws_kinesis_firehose_delivery_stream.test_stream", &stream), testAccCheckAWSKinesisFirehoseDeliveryStreamAttributes(&stream), @@ -32,9 +36,53 @@ func TestAccAWSKinesisFirehoseDeliveryStream_basic(t *testing.T) { }) } +func TestAccAWSKinesisFirehoseDeliveryStream_s3ConfigUpdates(t *testing.T) { + var stream firehose.DeliveryStreamDescription + + ri := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + preconfig := fmt.Sprintf(testAccKinesisFirehoseDeliveryStreamConfig_s3, ri, ri) + postConfig := fmt.Sprintf(testAccKinesisFirehoseDeliveryStreamConfig_s3Updates, ri, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckKinesisFirehoseDeliveryStreamDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: preconfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckKinesisFirehoseDeliveryStreamExists("aws_kinesis_firehose_delivery_stream.test_stream", &stream), + testAccCheckAWSKinesisFirehoseDeliveryStreamAttributes(&stream), + resource.TestCheckResourceAttr( + "aws_kinesis_firehose_delivery_stream.test_stream", "s3_buffer_size", "5"), + resource.TestCheckResourceAttr( + "aws_kinesis_firehose_delivery_stream.test_stream", "s3_buffer_interval", "300"), + resource.TestCheckResourceAttr( + "aws_kinesis_firehose_delivery_stream.test_stream", "s3_data_compression", "UNCOMPRESSED"), + ), + }, + + resource.TestStep{ + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckKinesisFirehoseDeliveryStreamExists("aws_kinesis_firehose_delivery_stream.test_stream", &stream), + testAccCheckAWSKinesisFirehoseDeliveryStreamAttributes(&stream), + resource.TestCheckResourceAttr( + "aws_kinesis_firehose_delivery_stream.test_stream", "s3_buffer_size", "10"), + resource.TestCheckResourceAttr( + "aws_kinesis_firehose_delivery_stream.test_stream", "s3_buffer_interval", "400"), + resource.TestCheckResourceAttr( + "aws_kinesis_firehose_delivery_stream.test_stream", "s3_data_compression", "GZIP"), + ), + }, + }, + }) +} + func testAccCheckKinesisFirehoseDeliveryStreamExists(n string, stream *firehose.DeliveryStreamDescription) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] + log.Printf("State: %#v", s.RootModule().Resources) if !ok { return fmt.Errorf("Not found: %s", n) } @@ -60,7 +108,7 @@ func testAccCheckKinesisFirehoseDeliveryStreamExists(n string, stream *firehose. func testAccCheckAWSKinesisFirehoseDeliveryStreamAttributes(stream *firehose.DeliveryStreamDescription) resource.TestCheckFunc { return func(s *terraform.State) error { - if !strings.HasPrefix(*stream.DeliveryStreamName, "terraform-kinesis-firehose-test") { + if !strings.HasPrefix(*stream.DeliveryStreamName, "terraform-kinesis-firehose") { return fmt.Errorf("Bad Stream name: %s", *stream.DeliveryStreamName) } for _, rs := range s.RootModule().Resources { @@ -98,9 +146,44 @@ func testAccCheckKinesisFirehoseDeliveryStreamDestroy(s *terraform.State) error return nil } -var testAccKinesisFirehoseDeliveryStreamConfig = fmt.Sprintf(` -resource "aws_kinesis_firehose_delivery_stream" "test_stream" { - name = "terraform-kinesis-firehose-test-%d" - +var testAccKinesisFirehoseDeliveryStreamConfig_basic = ` +resource "aws_s3_bucket" "bucket" { + bucket = "tf-test-bucket-%d" + acl = "private" } -`, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) + +resource "aws_kinesis_firehose_delivery_stream" "test_stream" { + name = "terraform-kinesis-firehose-basictest-%d" + destination = "s3" + role_arn = "arn:aws:iam::946579370547:role/firehose_delivery_role" + s3_bucket_arn = "${aws_s3_bucket.bucket.arn}" +}` + +var testAccKinesisFirehoseDeliveryStreamConfig_s3 = ` +resource "aws_s3_bucket" "bucket" { + bucket = "tf-test-bucket-%d" + acl = "private" +} + +resource "aws_kinesis_firehose_delivery_stream" "test_stream" { + name = "terraform-kinesis-firehose-s3test-%d" + destination = "s3" + role_arn = "arn:aws:iam::946579370547:role/firehose_delivery_role" + s3_bucket_arn = "${aws_s3_bucket.bucket.arn}" +}` + +var testAccKinesisFirehoseDeliveryStreamConfig_s3Updates = ` +resource "aws_s3_bucket" "bucket" { + bucket = "tf-test-bucket-01-%d" + acl = "private" +} + +resource "aws_kinesis_firehose_delivery_stream" "test_stream" { + name = "terraform-kinesis-firehose-s3test-%d" + destination = "s3" + role_arn = "arn:aws:iam::946579370547:role/firehose_delivery_role" + s3_bucket_arn = "${aws_s3_bucket.bucket.arn}" + s3_buffer_size = 10 + s3_buffer_interval = 400 + s3_data_compression = "GZIP" +}` From 8c68cb108021db980eff26a9a83956e34464470e Mon Sep 17 00:00:00 2001 From: stack72 Date: Tue, 10 Nov 2015 16:43:34 +0000 Subject: [PATCH 73/95] Adding the documentation for the Kinesis Firehose Delivery Stream --- ...sis_firehose_delivery_stream.html.markdown | 70 +++++++++++++++++++ website/source/layouts/aws.erb | 11 +++ 2 files changed, 81 insertions(+) create mode 100644 website/source/docs/providers/aws/r/kinesis_firehose_delivery_stream.html.markdown diff --git a/website/source/docs/providers/aws/r/kinesis_firehose_delivery_stream.html.markdown b/website/source/docs/providers/aws/r/kinesis_firehose_delivery_stream.html.markdown new file mode 100644 index 000000000..571798385 --- /dev/null +++ b/website/source/docs/providers/aws/r/kinesis_firehose_delivery_stream.html.markdown @@ -0,0 +1,70 @@ +--- +layout: "aws" +page_title: "AWS: aws_kinesis_firehose_delivery_stream" +sidebar_current: "docs-aws-resource-kinesis-firehose-delivery-stream" +description: |- + Provides a AWS Kinesis Firehose Delivery Stream +--- + +# aws\_kinesis\_stream + +Provides a Kinesis Firehose Delivery Stream resource. Amazon Kinesis Firehose is a fully managed, elastic service to easily deliver real-time data streams to destinations such as Amazon S3 and Amazon Redshift. + +For more details, see the [Amazon Kinesis Firehose Documentation][1]. + +## Example Usage + +``` +resource "aws_s3_bucket" "bucket" { + bucket = "tf-test-bucket" + acl = "private" +} + +esource "aws_iam_role" "firehose_role" { + name = "firehose_test_role" + assume_role_policy = < + > + Kinesis Firehose Resources + + + > Lambda Resources From 255486857e1c1574475f70df0f42802591771183 Mon Sep 17 00:00:00 2001 From: stack72 Date: Tue, 10 Nov 2015 16:45:46 +0000 Subject: [PATCH 74/95] Changing the Firehose documentation to note the regions is is available for and also that s3 is the only destination involved right now --- .../aws/r/kinesis_firehose_delivery_stream.html.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/source/docs/providers/aws/r/kinesis_firehose_delivery_stream.html.markdown b/website/source/docs/providers/aws/r/kinesis_firehose_delivery_stream.html.markdown index 571798385..1eec58951 100644 --- a/website/source/docs/providers/aws/r/kinesis_firehose_delivery_stream.html.markdown +++ b/website/source/docs/providers/aws/r/kinesis_firehose_delivery_stream.html.markdown @@ -47,6 +47,8 @@ resource "aws_kinesis_firehose_delivery_stream" "test_stream" { } ``` +~> **NOTE:** Kinesis Firehose is currently only support in us-east-1, us-west-2 and eu-west-1. This implementation of Kinesis Firehose only supports the s3 destination type + ## Argument Reference The following arguments are supported: From 1abb0b19bfc0f259eea25bf6c391987631e63ba5 Mon Sep 17 00:00:00 2001 From: stack72 Date: Tue, 3 Nov 2015 23:57:51 +0000 Subject: [PATCH 75/95] Changing the db_instance resource to mark the engine_version as Optional --- .../providers/aws/resource_aws_db_instance.go | 2 +- .../aws/resource_aws_db_instance_test.go | 41 +++++++++++++++++++ .../providers/aws/r/db_instance.html.markdown | 2 +- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index 37662b201..1bcde095d 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -54,7 +54,7 @@ func resourceAwsDbInstance() *schema.Resource { "engine_version": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, }, "storage_encrypted": &schema.Schema{ diff --git a/builtin/providers/aws/resource_aws_db_instance_test.go b/builtin/providers/aws/resource_aws_db_instance_test.go index e63be73a8..d58f33ed7 100644 --- a/builtin/providers/aws/resource_aws_db_instance_test.go +++ b/builtin/providers/aws/resource_aws_db_instance_test.go @@ -49,6 +49,25 @@ func TestAccAWSDBInstance_basic(t *testing.T) { }) } +func TestAccAWSDBInstance_withoutEngineVersion(t *testing.T) { + var v rds.DBInstance + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDBInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSDBInstanceConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists("aws_db_instance.bar", &v), + testAccCheckAWSDBInstanceAttributes(&v), + ), + }, + }, + }) +} + func TestAccAWSDBInstanceReplica(t *testing.T) { var s, r rds.DBInstance @@ -194,6 +213,28 @@ resource "aws_db_instance" "bar" { parameter_group_name = "default.mysql5.6" }`, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) +var testAccAWSDBInstanceConfig_withoutEngineVersion = fmt.Sprintf(` +resource "aws_db_instance" "bar" { + identifier = "foobarbaz-test-terraform-%d" + + allocated_storage = 10 + engine = "MySQL" + instance_class = "db.t1.micro" + name = "baz" + password = "barbarbarbar" + username = "foo" + + + # Maintenance Window is stored in lower case in the API, though not strictly + # documented. Terraform will downcase this to match (as opposed to throw a + # validation error). + maintenance_window = "Fri:09:00-Fri:09:30" + + backup_retention_period = 0 + + parameter_group_name = "default.mysql5.6" +}`, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) + func testAccReplicaInstanceConfig(val int) string { return fmt.Sprintf(` resource "aws_db_instance" "bar" { 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 499e13ba4..a5544c486 100644 --- a/website/source/docs/providers/aws/r/db_instance.html.markdown +++ b/website/source/docs/providers/aws/r/db_instance.html.markdown @@ -36,7 +36,7 @@ The following arguments are supported: * `allocated_storage` - (Required) The allocated storage in gigabytes. * `engine` - (Required) The database engine to use. -* `engine_version` - (Required) The engine version to use. +* `engine_version` - (Optional) The engine version to use. * `identifier` - (Required) The name of the RDS instance * `instance_class` - (Required) The instance type of the RDS instance. * `storage_type` - (Optional) One of "standard" (magnetic), "gp2" (general From e3a66d092882cfa2066ca4b216aa1f815a4e6f9d Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Mon, 9 Nov 2015 22:23:55 +0000 Subject: [PATCH 76/95] Making engine_version be computed in the db_instance provider --- builtin/providers/aws/resource_aws_db_instance.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index 1bcde095d..e941a359f 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -55,6 +55,7 @@ func resourceAwsDbInstance() *schema.Resource { "engine_version": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, }, "storage_encrypted": &schema.Schema{ From 6e21cd746e56ca2f52864cc0e7745daa655784d9 Mon Sep 17 00:00:00 2001 From: stack72 Date: Tue, 10 Nov 2015 17:35:12 +0000 Subject: [PATCH 77/95] Removing the AWS DBInstance Acceptance Test for withoutEngine as this is now part of the checkInstanceAttributes func --- .../aws/resource_aws_db_instance_test.go | 45 +------------------ 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/builtin/providers/aws/resource_aws_db_instance_test.go b/builtin/providers/aws/resource_aws_db_instance_test.go index d58f33ed7..a2c2f69ca 100644 --- a/builtin/providers/aws/resource_aws_db_instance_test.go +++ b/builtin/providers/aws/resource_aws_db_instance_test.go @@ -31,8 +31,6 @@ func TestAccAWSDBInstance_basic(t *testing.T) { "aws_db_instance.bar", "allocated_storage", "10"), resource.TestCheckResourceAttr( "aws_db_instance.bar", "engine", "mysql"), - resource.TestCheckResourceAttr( - "aws_db_instance.bar", "engine_version", "5.6.21"), resource.TestCheckResourceAttr( "aws_db_instance.bar", "license_model", "general-public-license"), resource.TestCheckResourceAttr( @@ -49,25 +47,6 @@ func TestAccAWSDBInstance_basic(t *testing.T) { }) } -func TestAccAWSDBInstance_withoutEngineVersion(t *testing.T) { - var v rds.DBInstance - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSDBInstanceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccAWSDBInstanceConfig, - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSDBInstanceExists("aws_db_instance.bar", &v), - testAccCheckAWSDBInstanceAttributes(&v), - ), - }, - }, - }) -} - func TestAccAWSDBInstanceReplica(t *testing.T) { var s, r rds.DBInstance @@ -130,7 +109,7 @@ func testAccCheckAWSDBInstanceAttributes(v *rds.DBInstance) resource.TestCheckFu return fmt.Errorf("bad engine: %#v", *v.Engine) } - if *v.EngineVersion != "5.6.21" { + if *v.EngineVersion == "" { return fmt.Errorf("bad engine_version: %#v", *v.EngineVersion) } @@ -213,28 +192,6 @@ resource "aws_db_instance" "bar" { parameter_group_name = "default.mysql5.6" }`, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) -var testAccAWSDBInstanceConfig_withoutEngineVersion = fmt.Sprintf(` -resource "aws_db_instance" "bar" { - identifier = "foobarbaz-test-terraform-%d" - - allocated_storage = 10 - engine = "MySQL" - instance_class = "db.t1.micro" - name = "baz" - password = "barbarbarbar" - username = "foo" - - - # Maintenance Window is stored in lower case in the API, though not strictly - # documented. Terraform will downcase this to match (as opposed to throw a - # validation error). - maintenance_window = "Fri:09:00-Fri:09:30" - - backup_retention_period = 0 - - parameter_group_name = "default.mysql5.6" -}`, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) - func testAccReplicaInstanceConfig(val int) string { return fmt.Sprintf(` resource "aws_db_instance" "bar" { From a49b162dd1782d35f69d895ac4fba7549b0304d3 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Tue, 10 Nov 2015 13:31:15 -0500 Subject: [PATCH 78/95] Prompt for input variables before context validate Also adds a regression test using Mock UI. Fixes #3767. --- command/plan.go | 8 +++++--- command/plan_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/command/plan.go b/command/plan.go index cd1aeaec6..8c5fda5cc 100644 --- a/command/plan.go +++ b/command/plan.go @@ -68,14 +68,16 @@ func (c *PlanCommand) Run(args []string) int { c.Ui.Error(err.Error()) return 1 } - if !validateContext(ctx, c.Ui) { - return 1 - } + if err := ctx.Input(c.InputMode()); err != nil { c.Ui.Error(fmt.Sprintf("Error configuring: %s", err)) return 1 } + if !validateContext(ctx, c.Ui) { + return 1 + } + if refresh { c.Ui.Output("Refreshing Terraform state prior to plan...\n") state, err := ctx.Refresh() diff --git a/command/plan_test.go b/command/plan_test.go index d49200a5f..d0d14bc56 100644 --- a/command/plan_test.go +++ b/command/plan_test.go @@ -1,6 +1,7 @@ package command import ( + "bytes" "io/ioutil" "os" "path/filepath" @@ -330,6 +331,30 @@ func TestPlan_vars(t *testing.T) { } } +func TestPlan_varsUnset(t *testing.T) { + // Disable test mode so input would be asked + test = false + defer func() { test = true }() + + defaultInputReader = bytes.NewBufferString("bar\n") + + p := testProvider() + ui := new(cli.MockUi) + c := &PlanCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + args := []string{ + testFixturePath("plan-vars"), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } +} + func TestPlan_varFile(t *testing.T) { varFilePath := testTempFile(t) if err := ioutil.WriteFile(varFilePath, []byte(planVarFile), 0644); err != nil { From bd35d2157ab500493d4f1eb464925c71217b031d Mon Sep 17 00:00:00 2001 From: James Nugent Date: Tue, 10 Nov 2015 14:11:48 -0500 Subject: [PATCH 79/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 554c1666a..8c0be5b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ BUG FIXES: * `terraform remote config`: update `--help` output [GH-3632] * core: modules on Git branches now update properly [GH-1568] + * core: Fix issue preventing input prompts for unset variables during plan [GH-3843] * provider/google: Timeout when deleting large instance_group_manager [GH-3591] * provider/aws: Fix issue with order of Termincation Policies in AutoScaling Groups. This will introduce plans on upgrade to this version, in order to correct the ordering [GH-2890] From e27df87b6899e4d4d577c193ac4a05a29a04d614 Mon Sep 17 00:00:00 2001 From: Clint Date: Tue, 10 Nov 2015 14:21:26 -0600 Subject: [PATCH 80/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c0be5b88..d3cbf5f25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ BUG FIXES: * provider/aws: Allow cluster name, not only ARN for `aws_ecs_service` [GH-3668] * 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 `iam_saml_provider` [GH-3827] * provider/aws: Tolerate ElastiCache clusters being deleted outside Terraform [GH-3767] * provider/azure: various bugfixes [GH-3695] * provider/digitalocean: fix issue preventing SSH fingerprints from working [GH-3633] From eadee6bd33e04a53c65792c36268905a7138f57c Mon Sep 17 00:00:00 2001 From: Patrick Gray Date: Tue, 10 Nov 2015 15:24:45 -0500 Subject: [PATCH 81/95] modify aws_iam_user_test to correctly check username and path for initial and changed username/path --- builtin/providers/aws/resource_aws_iam_user_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/builtin/providers/aws/resource_aws_iam_user_test.go b/builtin/providers/aws/resource_aws_iam_user_test.go index 4a7939658..d1ff3889e 100644 --- a/builtin/providers/aws/resource_aws_iam_user_test.go +++ b/builtin/providers/aws/resource_aws_iam_user_test.go @@ -23,14 +23,14 @@ func TestAccAWSUser_basic(t *testing.T) { Config: testAccAWSUserConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSUserExists("aws_iam_user.user", &conf), - testAccCheckAWSUserAttributes(&conf), + testAccCheckAWSUserAttributes(&conf, "test-user", "/"), ), }, resource.TestStep{ Config: testAccAWSUserConfig2, Check: resource.ComposeTestCheckFunc( testAccCheckAWSUserExists("aws_iam_user.user", &conf), - testAccCheckAWSUserAttributes(&conf), + testAccCheckAWSUserAttributes(&conf, "test-user2", "/path2/"), ), }, }, @@ -92,13 +92,13 @@ func testAccCheckAWSUserExists(n string, res *iam.GetUserOutput) resource.TestCh } } -func testAccCheckAWSUserAttributes(user *iam.GetUserOutput) resource.TestCheckFunc { +func testAccCheckAWSUserAttributes(user *iam.GetUserOutput, name string, path string) resource.TestCheckFunc { return func(s *terraform.State) error { - if *user.User.UserName != "test-user" { + if *user.User.UserName != name { return fmt.Errorf("Bad name: %s", *user.User.UserName) } - if *user.User.Path != "/" { + if *user.User.Path != path { return fmt.Errorf("Bad path: %s", *user.User.Path) } @@ -115,6 +115,6 @@ resource "aws_iam_user" "user" { const testAccAWSUserConfig2 = ` resource "aws_iam_user" "user" { name = "test-user2" - path = "/balls/" + path = "/path2/" } ` From 1c8d31248ce1a24f841c9cd1fd98dee7341a8bd7 Mon Sep 17 00:00:00 2001 From: Clint Date: Tue, 10 Nov 2015 14:43:38 -0600 Subject: [PATCH 82/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3cbf5f25..dc2715da9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ IMPROVEMENTS: * provider/aws: Add `kinesis_endpoint` for configuring Kinesis [GH-3255] * 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 snapshot window and retention limits for ElastiCache (Redis) [GH-3707] * provider/aws: Add configuration to enable copying RDS tags to final snapshot [GH-3529] * provider/aws: RDS Cluster additions (`backup_retention_period`, `preferred_backup_window`, `preferred_maintenance_window`) [GH-3757] From 77bad2c94604678570d65cdf5adf1c9b9a62ab0f Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Tue, 10 Nov 2015 20:59:01 +0000 Subject: [PATCH 83/95] Changes to Aws Kinesis Firehouse Docs Changing the AWS Kinesis Firehose docs as per @jen20's feedback --- .../aws/r/kinesis_firehose_delivery_stream.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/providers/aws/r/kinesis_firehose_delivery_stream.html.markdown b/website/source/docs/providers/aws/r/kinesis_firehose_delivery_stream.html.markdown index 1eec58951..61a6c62b5 100644 --- a/website/source/docs/providers/aws/r/kinesis_firehose_delivery_stream.html.markdown +++ b/website/source/docs/providers/aws/r/kinesis_firehose_delivery_stream.html.markdown @@ -47,7 +47,7 @@ resource "aws_kinesis_firehose_delivery_stream" "test_stream" { } ``` -~> **NOTE:** Kinesis Firehose is currently only support in us-east-1, us-west-2 and eu-west-1. This implementation of Kinesis Firehose only supports the s3 destination type +~> **NOTE:** Kinesis Firehose is currently only supported in us-east-1, us-west-2 and eu-west-1. This implementation of Kinesis Firehose only supports the s3 destination type as Terraform doesn't support Redshift yet. ## Argument Reference From 8d8c1f940976e6815dc2435dec7109b5aa06c2ee Mon Sep 17 00:00:00 2001 From: James Nugent Date: Tue, 10 Nov 2015 16:12:24 -0500 Subject: [PATCH 84/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc2715da9..3527b48ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ FEATURES: * **New resource: `aws_cloudtrail`** [GH-3094] * **New resource: `aws_route`** [GH-3548] * **New resource: `aws_codecommit_repository`** [GH-3274] + * **New resource: `aws_kinesis_firehose_delivery_stream`** [GH-3833] * **New provider: `tls`** - A utility provider for generating TLS keys/self-signed certificates for development and testing [GH-2778] * **New resource: `google_sql_database` and `google_sql_database_instance`** [GH-3617] * **New resource: `google_compute_global_address`** [GH-3701] From b48bd30227b273e3c977cfbd6b376de3aa8783e2 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Tue, 10 Nov 2015 16:39:23 -0500 Subject: [PATCH 85/95] provider/aws: Fix error format in Kinesis Firehose --- .../aws/resource_aws_kinesis_firehose_delivery_stream.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream.go b/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream.go index c1b973454..c39467ee4 100644 --- a/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream.go +++ b/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream.go @@ -192,7 +192,7 @@ func resourceAwsKinesisFirehoseDeliveryStreamUpdate(d *schema.ResourceData, meta _, err := conn.UpdateDestination(destOpts) if err != nil { return fmt.Errorf( - "Error Updating Kinesis Firehose Delivery Stream: %s", + "Error Updating Kinesis Firehose Delivery Stream: \"%s\"\n%s", sn, err) } From 7e4bb881b0397a9b092fd8c46fd943673ff3dbca Mon Sep 17 00:00:00 2001 From: Clint Date: Tue, 10 Nov 2015 15:48:57 -0600 Subject: [PATCH 86/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3527b48ca..5ef27e160 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ IMPROVEMENTS: * 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 snapshot window and retention limits for ElastiCache (Redis) [GH-3707] + * provider/aws: `engine_version` is now optional for DB Instance [GH-3744] * provider/aws: Add configuration to enable copying RDS tags to final snapshot [GH-3529] * provider/aws: RDS Cluster additions (`backup_retention_period`, `preferred_backup_window`, `preferred_maintenance_window`) [GH-3757] * provider/openstack: Use IPv4 as the defeault IP version for subnets [GH-3091] From 359c087a3a90a07eadfb247900f250717015f432 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Tue, 10 Nov 2015 17:11:39 -0500 Subject: [PATCH 87/95] Build using `make test` on Travis CI --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4a776dc19..c36571ca1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,7 @@ go: install: make updatedeps script: - - go test ./... - - make vet - #- go test -race ./... + - make test branches: only: From ad9ce721553841ce1c94bbd6208391364d66d3ba Mon Sep 17 00:00:00 2001 From: Clint Date: Tue, 10 Nov 2015 16:18:09 -0600 Subject: [PATCH 88/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ef27e160..15be945c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ IMPROVEMENTS: * 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 snapshot window and retention limits for ElastiCache (Redis) [GH-3707] + * provider/aws Implement username updates for `aws_iam_user` [GH-3227] * provider/aws: `engine_version` is now optional for DB Instance [GH-3744] * provider/aws: Add configuration to enable copying RDS tags to final snapshot [GH-3529] * provider/aws: RDS Cluster additions (`backup_retention_period`, `preferred_backup_window`, `preferred_maintenance_window`) [GH-3757] From 914bc56ad592daf4f9820a9529a010ca228d8bd7 Mon Sep 17 00:00:00 2001 From: Clint Date: Tue, 10 Nov 2015 16:18:23 -0600 Subject: [PATCH 89/95] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15be945c2..87910b2a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ IMPROVEMENTS: * 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 snapshot window and retention limits for ElastiCache (Redis) [GH-3707] - * provider/aws Implement username updates for `aws_iam_user` [GH-3227] + * provider/aws: Add username updates for `aws_iam_user` [GH-3227] * provider/aws: `engine_version` is now optional for DB Instance [GH-3744] * provider/aws: Add configuration to enable copying RDS tags to final snapshot [GH-3529] * provider/aws: RDS Cluster additions (`backup_retention_period`, `preferred_backup_window`, `preferred_maintenance_window`) [GH-3757] From b16b634e2b321e8fb4670ad9a944919d2afcc924 Mon Sep 17 00:00:00 2001 From: clint shryock Date: Tue, 10 Nov 2015 16:19:15 -0600 Subject: [PATCH 90/95] add some logging around create/update requests for IAM user --- builtin/providers/aws/resource_aws_iam_user.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/builtin/providers/aws/resource_aws_iam_user.go b/builtin/providers/aws/resource_aws_iam_user.go index daff0d97c..d058047d0 100644 --- a/builtin/providers/aws/resource_aws_iam_user.go +++ b/builtin/providers/aws/resource_aws_iam_user.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" @@ -58,6 +59,7 @@ func resourceAwsIamUserCreate(d *schema.ResourceData, meta interface{}) error { UserName: aws.String(name), } + log.Println("[DEBUG] Create IAM User request:", request) createResp, err := iamconn.CreateUser(request) if err != nil { return fmt.Errorf("Error creating IAM User %s: %s", name, err) @@ -75,6 +77,7 @@ func resourceAwsIamUserRead(d *schema.ResourceData, meta interface{}) error { getResp, err := iamconn.GetUser(request) if err != nil { if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" { // XXX test me + log.Printf("[WARN] No IAM user by name (%s) found", d.Id()) d.SetId("") return nil } @@ -104,16 +107,19 @@ func resourceAwsIamUserUpdate(d *schema.ResourceData, meta interface{}) error { if d.HasChange("name") || d.HasChange("path") { iamconn := meta.(*AWSClient).iamconn on, nn := d.GetChange("name") - op, np := d.GetChange("path") - fmt.Println(on, nn, op, np) + _, np := d.GetChange("path") + request := &iam.UpdateUserInput{ UserName: aws.String(on.(string)), NewUserName: aws.String(nn.(string)), NewPath: aws.String(np.(string)), } + + log.Println("[DEBUG] Update IAM User request:", request) _, err := iamconn.UpdateUser(request) if err != nil { if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" { + log.Printf("[WARN] No IAM user by name (%s) found", d.Id()) d.SetId("") return nil } @@ -130,6 +136,7 @@ func resourceAwsIamUserDelete(d *schema.ResourceData, meta interface{}) error { UserName: aws.String(d.Id()), } + log.Println("[DEBUG] Delete IAM User request:", request) if _, err := iamconn.DeleteUser(request); err != nil { return fmt.Errorf("Error deleting IAM User %s: %s", d.Id(), err) } From 7152674f9b9f1dc20fa9f655d363d42719988d48 Mon Sep 17 00:00:00 2001 From: clint shryock Date: Tue, 10 Nov 2015 16:27:41 -0600 Subject: [PATCH 91/95] upgrade a warning to error --- builtin/providers/aws/resource_aws_elb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_elb.go b/builtin/providers/aws/resource_aws_elb.go index 6be03b112..91b4882cb 100644 --- a/builtin/providers/aws/resource_aws_elb.go +++ b/builtin/providers/aws/resource_aws_elb.go @@ -314,7 +314,7 @@ func resourceAwsElbRead(d *schema.ResourceData, meta interface{}) error { } sgId, err := sourceSGIdByName(meta, *lb.SourceSecurityGroup.GroupName, elbVpc) if err != nil { - log.Printf("[WARN] Error looking up ELB Security Group ID: %s", err) + return fmt.Errorf("[WARN] Error looking up ELB Security Group ID: %s", err) } else { d.Set("source_security_group_id", sgId) } From ad356cd24f24814cdb5b338d318c2c5de56fb628 Mon Sep 17 00:00:00 2001 From: Clint Date: Tue, 10 Nov 2015 16:39:39 -0600 Subject: [PATCH 92/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87910b2a8..c302e28bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,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: 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] * provider/aws: `engine_version` is now optional for DB Instance [GH-3744] From de4d35c2353147a59cb04baf9092ab220e18984f Mon Sep 17 00:00:00 2001 From: Clint Date: Tue, 10 Nov 2015 16:42:21 -0600 Subject: [PATCH 93/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c302e28bc..7721a411a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ BUG FIXES: * provider/aws: Fix policy encoding issue with SNS Topics [GH-3700] * provider/aws: Correctly export ARN in `iam_saml_provider` [GH-3827] * 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/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] From 22f0f5f7ff19996064c51f31480f2d1c9bcae648 Mon Sep 17 00:00:00 2001 From: Clint Date: Tue, 10 Nov 2015 16:44:11 -0600 Subject: [PATCH 94/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7721a411a..c3155bd1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ BUG FIXES: * provider/aws: Correctly export ARN in `iam_saml_provider` [GH-3827] * 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] * 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] From d1d8aca483113d7f84a2fde148ac68f847cd1a46 Mon Sep 17 00:00:00 2001 From: Clint Date: Tue, 10 Nov 2015 16:46:20 -0600 Subject: [PATCH 95/95] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3155bd1e..987cfc36d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ BUG FIXES: * 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] + * provider/aws: Fix issue with SNS topic policy if omitted [GH-3777] * 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]