From 8b01af5e59b0455b77f2fa73ba4d357abb13c1ab Mon Sep 17 00:00:00 2001 From: David Meyer Date: Wed, 8 Nov 2017 12:10:57 -0600 Subject: [PATCH 1/4] r/instance d/instance: Get (Windows) password data. --- aws/data_source_aws_instance.go | 21 ++++- aws/resource_aws_instance.go | 55 ++++++++++++ aws/resource_aws_instance_test.go | 120 ++++++++++++++++++++++++++ website/docs/d/instance.html.markdown | 4 + website/docs/r/instance.html.markdown | 4 + 5 files changed, 203 insertions(+), 1 deletion(-) diff --git a/aws/data_source_aws_instance.go b/aws/data_source_aws_instance.go index 0033a09c90e..d41b24a0f47 100644 --- a/aws/data_source_aws_instance.go +++ b/aws/data_source_aws_instance.go @@ -46,6 +46,15 @@ func dataSourceAwsInstance() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "get_password_data": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "password_data": { + Type: schema.TypeString, + Computed: true, + }, "public_dns": { Type: schema.TypeString, Computed: true, @@ -266,7 +275,17 @@ func dataSourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { } log.Printf("[DEBUG] aws_instance - Single Instance ID found: %s", *instance.InstanceId) - return instanceDescriptionAttributes(d, instance, conn) + if err := instanceDescriptionAttributes(d, instance, conn); err != nil { + return err + } + + if d.Get("get_password_data").(bool) { + if err := readPasswordData(d, instance, conn); err != nil { + return err + } + } + + return nil } // Populate instance attribute fields with the returned instance diff --git a/aws/resource_aws_instance.go b/aws/resource_aws_instance.go index dc847ff2f3e..7b5e003f061 100644 --- a/aws/resource_aws_instance.go +++ b/aws/resource_aws_instance.go @@ -78,6 +78,17 @@ func resourceAwsInstance() *schema.Resource { Computed: true, }, + "get_password_data": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "password_data": { + Type: schema.TypeString, + Computed: true, + }, + "subnet_id": { Type: schema.TypeString, Optional: true, @@ -759,6 +770,10 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { } } + if err := readPasswordData(d, instance, conn); err != nil { + return err + } + return nil } @@ -1528,6 +1543,46 @@ func readSecurityGroups(d *schema.ResourceData, instance *ec2.Instance, conn *ec return nil } +func readPasswordData(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2) error { + if !d.Get("get_password_data").(bool) { + if err := d.Set("password_data", nil); err != nil { + return err + } + + return nil + } + + log.Printf("[INFO] Reading password data for instance %s", d.Id()) + + timeout := time.Now().Add(15 * time.Minute) + for { + resp, err := conn.GetPasswordData(&ec2.GetPasswordDataInput{ + InstanceId: instance.InstanceId, + }) + + if err != nil { + return err + } + + if resp.PasswordData != nil && *resp.PasswordData != "" { + passwordData := strings.TrimSpace(*resp.PasswordData) + if err := d.Set("password_data", passwordData); err != nil { + return err + } + + log.Printf("[INFO] Password data read for instance %s", d.Id()) + return nil + } + + if time.Now().After(timeout) { + return fmt.Errorf("Timeout exceeded waiting for password data to become available for instance %s", d.Id()) + } + + log.Printf("[TRACE] Password data is blank for instance %s, will retry in 5s...", d.Id()) + time.Sleep(5 * time.Second) + } +} + type awsInstanceOpts struct { BlockDeviceMappings []*ec2.BlockDeviceMapping DisableAPITermination *bool diff --git a/aws/resource_aws_instance_test.go b/aws/resource_aws_instance_test.go index 85d7cc2b4a5..603f2d6e3bb 100644 --- a/aws/resource_aws_instance_test.go +++ b/aws/resource_aws_instance_test.go @@ -1288,6 +1288,108 @@ func TestAccAWSInstance_associatePublic_overridePrivate(t *testing.T) { }) } +func TestAccAWSInstance_getPasswordData_true(t *testing.T) { + var before ec2.Instance + resName := "aws_instance.foo" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceConfig_getPasswordData(true), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resName, &before), + resource.TestCheckResourceAttr(resName, "get_password_data", "true"), + resource.TestCheckResourceAttrSet(resName, "password_data"), + ), + }, + }, + }) +} + +func TestAccAWSInstance_getPasswordData_false(t *testing.T) { + var before ec2.Instance + resName := "aws_instance.foo" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceConfig_getPasswordData(false), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resName, &before), + resource.TestCheckResourceAttr(resName, "get_password_data", "false"), + resource.TestCheckResourceAttr(resName, "password_data", ""), + ), + }, + }, + }) +} + +func TestAccAWSInstance_getPasswordData_falseToTrue(t *testing.T) { + var before, after ec2.Instance + resName := "aws_instance.foo" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceConfig_getPasswordData(false), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resName, &before), + resource.TestCheckResourceAttr(resName, "get_password_data", "false"), + resource.TestCheckResourceAttr(resName, "password_data", ""), + ), + }, + { + Config: testAccInstanceConfig_getPasswordData(true), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resName, &after), + testAccCheckInstanceNotRecreated(t, &before, &after), + resource.TestCheckResourceAttr(resName, "get_password_data", "true"), + resource.TestCheckResourceAttrSet(resName, "password_data"), + ), + }, + }, + }) +} + +func TestAccAWSInstance_getPasswordData_trueToFalse(t *testing.T) { + var before, after ec2.Instance + resName := "aws_instance.foo" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceConfig_getPasswordData(true), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resName, &before), + resource.TestCheckResourceAttr(resName, "get_password_data", "true"), + resource.TestCheckResourceAttrSet(resName, "password_data"), + ), + }, + { + Config: testAccInstanceConfig_getPasswordData(false), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resName, &after), + testAccCheckInstanceNotRecreated(t, &before, &after), + resource.TestCheckResourceAttr(resName, "get_password_data", "false"), + resource.TestCheckResourceAttr(resName, "password_data", ""), + ), + }, + }, + }) +} + func testAccCheckInstanceNotRecreated(t *testing.T, before, after *ec2.Instance) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -2719,3 +2821,21 @@ resource "aws_instance" "foo" { } }`, rInt, rInt) } + +func testAccInstanceConfig_getPasswordData(val bool) string { + return fmt.Sprintf(` + resource "aws_key_pair" "foo" { + key_name = "tf-acc-winpasswordtest" + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAq6U3HQYC4g8WzU147gZZ7CKQH8TgYn3chZGRPxaGmHW1RUwsyEs0nmombmIhwxudhJ4ehjqXsDLoQpd6+c7BuLgTMvbv8LgE9LX53vnljFe1dsObsr/fYLvpU9LTlo8HgHAqO5ibNdrAUvV31ronzCZhms/Gyfdaue88Fd0/YnsZVGeOZPayRkdOHSpqme2CBrpa8myBeL1CWl0LkDG4+YCURjbaelfyZlIApLYKy3FcCan9XQFKaL32MJZwCgzfOvWIMtYcU8QtXMgnA3/I3gXk8YDUJv5P4lj0s/PJXuTM8DygVAUtebNwPuinS7wwonm5FXcWMuVGsVpG5K7FGQ== tf-acc-winpasswordtest" + } + + resource "aws_instance" "foo" { + # us-west-2 (oregon) Microsoft Windows Server 2016 Core on Windows Server 2016 Base 10 - 2017.10.13 + ami = "ami-7730f60f" + instance_type = "t2.medium" + key_name = "${aws_key_pair.foo.key_name}" + + get_password_data = %t + } + `, val) +} diff --git a/website/docs/d/instance.html.markdown b/website/docs/d/instance.html.markdown index ac2ad0e171b..60e40235fab 100644 --- a/website/docs/d/instance.html.markdown +++ b/website/docs/d/instance.html.markdown @@ -40,6 +40,8 @@ exactly match a pair on the desired Instance. several valid keys, for a full reference, check out [describe-instances in the AWS CLI reference][1]. +* `get_password_data` - (Optional) If true, wait for password data to become available and retrieve it. The password data is exported to the `password_data` attribute. + ~> **NOTE:** At least one of `filter`, `instance_tags`, or `instance_id` must be specified. ~> **NOTE:** If anything other than a single match is returned by the search, @@ -75,6 +77,8 @@ interpolation. * `key_name` - The key name of the Instance. * `monitoring` - Whether detailed monitoring is enabled or disabled for the Instance (Boolean). * `network_interface_id` - The ID of the network interface that was created with the Instance. +* `password_data` - Base-64 encoded encrypted password data for the instance. + This attribute is only exported if `get_password_data` is true. * `placement_group` - The placement group of the Instance. * `private_dns` - The private DNS name assigned to the Instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames diff --git a/website/docs/r/instance.html.markdown b/website/docs/r/instance.html.markdown index fc1bef32acb..8d1e4264175 100644 --- a/website/docs/r/instance.html.markdown +++ b/website/docs/r/instance.html.markdown @@ -64,6 +64,7 @@ instance. Amazon defaults this to `stop` for EBS-backed instances and instances. See [Shutdown Behavior](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingInstanceInitiatedShutdownBehavior) for more information. * `instance_type` - (Required) The type of instance to start * `key_name` - (Optional) The key name to use for the instance. +* `get_password_data` - (Optional) If true, wait for password data to become available and retrieve it. The password data is exported to the `password_data` attribute. * `monitoring` - (Optional) If true, the launched EC2 instance will have detailed monitoring enabled. (Available since v0.6.0) * `security_groups` - (Optional) A list of security group names to associate with. If you are creating Instances in a VPC, use `vpc_security_group_ids` instead. @@ -220,6 +221,9 @@ The following attributes are exported: * `availability_zone` - The availability zone of the instance. * `placement_group` - The placement group of the instance. * `key_name` - The key name of the instance +* `password_data` - Base-64 encoded encrypted password data for the instance. + This attribute is only exported if `get_password_data` is true. + Note that this encrypted value will be stored in the state file, as with all exported attributes. * `public_dns` - The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC * `public_ip` - The public IP address assigned to the instance, if applicable. **NOTE**: If you are using an [`aws_eip`](/docs/providers/aws/r/eip.html) with your instance, you should refer to the EIP's address directly and not use `public_ip`, as this field will change after the EIP is attached. From fe12cd75dfd26eb0e20f708ed0c277ca351c9f40 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Wed, 8 Nov 2017 13:02:34 -0600 Subject: [PATCH 2/4] d/instance: Acceptance tests for getting password data. --- aws/data_source_aws_instance_test.go | 100 +++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/aws/data_source_aws_instance_test.go b/aws/data_source_aws_instance_test.go index 9a5dec37b5b..b82d6656c95 100644 --- a/aws/data_source_aws_instance_test.go +++ b/aws/data_source_aws_instance_test.go @@ -216,6 +216,84 @@ func TestAccAWSInstanceDataSource_VPCSecurityGroups(t *testing.T) { }) } +func TestAccAWSInstanceDataSource_getPasswordData_true(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_getPasswordData(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.aws_instance.foo", "get_password_data", "true"), + resource.TestCheckResourceAttrSet("data.aws_instance.foo", "password_data"), + ), + }, + }, + }) +} + +func TestAccAWSInstanceDataSource_getPasswordData_false(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_getPasswordData(false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.aws_instance.foo", "get_password_data", "false"), + resource.TestCheckNoResourceAttr("data.aws_instance.foo", "password_data"), + ), + }, + }, + }) +} + +func TestAccAWSInstanceDataSource_getPasswordData_trueToFalse(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_getPasswordData(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.aws_instance.foo", "get_password_data", "true"), + resource.TestCheckResourceAttrSet("data.aws_instance.foo", "password_data"), + ), + }, + { + Config: testAccInstanceDataSourceConfig_getPasswordData(false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.aws_instance.foo", "get_password_data", "false"), + resource.TestCheckNoResourceAttr("data.aws_instance.foo", "password_data"), + ), + }, + }, + }) +} + +func TestAccAWSInstanceDataSource_getPasswordData_falseToTrue(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_getPasswordData(false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.aws_instance.foo", "get_password_data", "false"), + resource.TestCheckNoResourceAttr("data.aws_instance.foo", "password_data"), + ), + }, + { + Config: testAccInstanceDataSourceConfig_getPasswordData(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.aws_instance.foo", "get_password_data", "true"), + resource.TestCheckResourceAttrSet("data.aws_instance.foo", "password_data"), + ), + }, + }, + }) +} + // Lookup based on InstanceID const testAccInstanceDataSourceConfig = ` resource "aws_instance" "web" { @@ -502,3 +580,25 @@ data "aws_instance" "foo" { instance_id = "${aws_instance.foo_instance.id}" } ` + +func testAccInstanceDataSourceConfig_getPasswordData(val bool) string { + return fmt.Sprintf(` + resource "aws_key_pair" "foo" { + key_name = "tf-acc-winpasswordtest" + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAq6U3HQYC4g8WzU147gZZ7CKQH8TgYn3chZGRPxaGmHW1RUwsyEs0nmombmIhwxudhJ4ehjqXsDLoQpd6+c7BuLgTMvbv8LgE9LX53vnljFe1dsObsr/fYLvpU9LTlo8HgHAqO5ibNdrAUvV31ronzCZhms/Gyfdaue88Fd0/YnsZVGeOZPayRkdOHSpqme2CBrpa8myBeL1CWl0LkDG4+YCURjbaelfyZlIApLYKy3FcCan9XQFKaL32MJZwCgzfOvWIMtYcU8QtXMgnA3/I3gXk8YDUJv5P4lj0s/PJXuTM8DygVAUtebNwPuinS7wwonm5FXcWMuVGsVpG5K7FGQ== tf-acc-winpasswordtest" + } + + resource "aws_instance" "foo" { + # us-west-2 (oregon) Microsoft Windows Server 2016 Core on Windows Server 2016 Base 10 - 2017.10.13 + ami = "ami-7730f60f" + instance_type = "t2.medium" + key_name = "${aws_key_pair.foo.key_name}" + } + + data "aws_instance" "foo" { + instance_id = "${aws_instance.foo.id}" + + get_password_data = %t + } + `, val) +} From 9124635dec48e2fd42dc118285ac4b78a3a6f34e Mon Sep 17 00:00:00 2001 From: David Meyer Date: Wed, 7 Mar 2018 22:09:34 -0600 Subject: [PATCH 3/4] PR feedback tweaks. --- aws/data_source_aws_instance.go | 4 +- aws/data_source_aws_instance_test.go | 68 ++++++++++-------------- aws/resource_aws_instance.go | 53 +++++++++---------- aws/resource_aws_instance_test.go | 76 +++++++++------------------ website/docs/d/instance.html.markdown | 4 +- website/docs/r/instance.html.markdown | 4 +- 6 files changed, 86 insertions(+), 123 deletions(-) diff --git a/aws/data_source_aws_instance.go b/aws/data_source_aws_instance.go index d41b24a0f47..ad005455763 100644 --- a/aws/data_source_aws_instance.go +++ b/aws/data_source_aws_instance.go @@ -280,9 +280,11 @@ func dataSourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { } if d.Get("get_password_data").(bool) { - if err := readPasswordData(d, instance, conn); err != nil { + passwordData, err := getAwsEc2InstancePasswordData(*instance.InstanceId, conn) + if err != nil { return err } + d.Set("password_data", passwordData) } return nil diff --git a/aws/data_source_aws_instance_test.go b/aws/data_source_aws_instance_test.go index b82d6656c95..296ed520ed8 100644 --- a/aws/data_source_aws_instance_test.go +++ b/aws/data_source_aws_instance_test.go @@ -216,52 +216,22 @@ func TestAccAWSInstanceDataSource_VPCSecurityGroups(t *testing.T) { }) } -func TestAccAWSInstanceDataSource_getPasswordData_true(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: testAccInstanceDataSourceConfig_getPasswordData(true), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("data.aws_instance.foo", "get_password_data", "true"), - resource.TestCheckResourceAttrSet("data.aws_instance.foo", "password_data"), - ), - }, - }, - }) -} - -func TestAccAWSInstanceDataSource_getPasswordData_false(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: testAccInstanceDataSourceConfig_getPasswordData(false), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("data.aws_instance.foo", "get_password_data", "false"), - resource.TestCheckNoResourceAttr("data.aws_instance.foo", "password_data"), - ), - }, - }, - }) -} - func TestAccAWSInstanceDataSource_getPasswordData_trueToFalse(t *testing.T) { + rInt := acctest.RandInt() + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccInstanceDataSourceConfig_getPasswordData(true), + Config: testAccInstanceDataSourceConfig_getPasswordData(true, rInt), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.aws_instance.foo", "get_password_data", "true"), resource.TestCheckResourceAttrSet("data.aws_instance.foo", "password_data"), ), }, { - Config: testAccInstanceDataSourceConfig_getPasswordData(false), + Config: testAccInstanceDataSourceConfig_getPasswordData(false, rInt), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.aws_instance.foo", "get_password_data", "false"), resource.TestCheckNoResourceAttr("data.aws_instance.foo", "password_data"), @@ -272,19 +242,21 @@ func TestAccAWSInstanceDataSource_getPasswordData_trueToFalse(t *testing.T) { } func TestAccAWSInstanceDataSource_getPasswordData_falseToTrue(t *testing.T) { + rInt := acctest.RandInt() + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccInstanceDataSourceConfig_getPasswordData(false), + Config: testAccInstanceDataSourceConfig_getPasswordData(false, rInt), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.aws_instance.foo", "get_password_data", "false"), resource.TestCheckNoResourceAttr("data.aws_instance.foo", "password_data"), ), }, { - Config: testAccInstanceDataSourceConfig_getPasswordData(true), + Config: testAccInstanceDataSourceConfig_getPasswordData(true, rInt), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.aws_instance.foo", "get_password_data", "true"), resource.TestCheckResourceAttrSet("data.aws_instance.foo", "password_data"), @@ -581,16 +553,30 @@ data "aws_instance" "foo" { } ` -func testAccInstanceDataSourceConfig_getPasswordData(val bool) string { +func testAccInstanceDataSourceConfig_getPasswordData(val bool, rInt int) string { return fmt.Sprintf(` + # Find latest Microsoft Windows Server 2016 Core image (Amazon deletes old ones) + data "aws_ami" "win2016core" { + most_recent = true + + filter { + name = "owner-alias" + values = ["amazon"] + } + + filter { + name = "name" + values = ["Windows_Server-2016-English-Core-Base-*"] + } + } + resource "aws_key_pair" "foo" { - key_name = "tf-acc-winpasswordtest" + key_name = "tf-acctest-%d" public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAq6U3HQYC4g8WzU147gZZ7CKQH8TgYn3chZGRPxaGmHW1RUwsyEs0nmombmIhwxudhJ4ehjqXsDLoQpd6+c7BuLgTMvbv8LgE9LX53vnljFe1dsObsr/fYLvpU9LTlo8HgHAqO5ibNdrAUvV31ronzCZhms/Gyfdaue88Fd0/YnsZVGeOZPayRkdOHSpqme2CBrpa8myBeL1CWl0LkDG4+YCURjbaelfyZlIApLYKy3FcCan9XQFKaL32MJZwCgzfOvWIMtYcU8QtXMgnA3/I3gXk8YDUJv5P4lj0s/PJXuTM8DygVAUtebNwPuinS7wwonm5FXcWMuVGsVpG5K7FGQ== tf-acc-winpasswordtest" } resource "aws_instance" "foo" { - # us-west-2 (oregon) Microsoft Windows Server 2016 Core on Windows Server 2016 Base 10 - 2017.10.13 - ami = "ami-7730f60f" + ami = "${data.aws_ami.win2016core.id}" instance_type = "t2.medium" key_name = "${aws_key_pair.foo.key_name}" } @@ -600,5 +586,5 @@ func testAccInstanceDataSourceConfig_getPasswordData(val bool) string { get_password_data = %t } - `, val) + `, rInt, val) } diff --git a/aws/resource_aws_instance.go b/aws/resource_aws_instance.go index 7b5e003f061..dc1cf01624c 100644 --- a/aws/resource_aws_instance.go +++ b/aws/resource_aws_instance.go @@ -770,8 +770,14 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { } } - if err := readPasswordData(d, instance, conn); err != nil { - return err + if d.Get("get_password_data").(bool) { + passwordData, err := getAwsEc2InstancePasswordData(*instance.InstanceId, conn) + if err != nil { + return err + } + d.Set("password_data", passwordData) + } else { + d.Set("password_data", nil) } return nil @@ -1543,44 +1549,35 @@ func readSecurityGroups(d *schema.ResourceData, instance *ec2.Instance, conn *ec return nil } -func readPasswordData(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2) error { - if !d.Get("get_password_data").(bool) { - if err := d.Set("password_data", nil); err != nil { - return err - } +func getAwsEc2InstancePasswordData(instanceID string, conn *ec2.EC2) (string, error) { + log.Printf("[INFO] Reading password data for instance %s", instanceID) - return nil - } + var passwordData string - log.Printf("[INFO] Reading password data for instance %s", d.Id()) - - timeout := time.Now().Add(15 * time.Minute) - for { + err := resource.Retry(15*time.Minute, func() *resource.RetryError { resp, err := conn.GetPasswordData(&ec2.GetPasswordDataInput{ - InstanceId: instance.InstanceId, + InstanceId: aws.String(instanceID), }) if err != nil { - return err + return resource.NonRetryableError(err) } - if resp.PasswordData != nil && *resp.PasswordData != "" { - passwordData := strings.TrimSpace(*resp.PasswordData) - if err := d.Set("password_data", passwordData); err != nil { - return err - } - - log.Printf("[INFO] Password data read for instance %s", d.Id()) - return nil + if resp.PasswordData == nil || *resp.PasswordData == "" { + return resource.RetryableError(fmt.Errorf("Password data is blank for instance ID: %s", instanceID)) } - if time.Now().After(timeout) { - return fmt.Errorf("Timeout exceeded waiting for password data to become available for instance %s", d.Id()) - } + passwordData = strings.TrimSpace(*resp.PasswordData) - log.Printf("[TRACE] Password data is blank for instance %s, will retry in 5s...", d.Id()) - time.Sleep(5 * time.Second) + log.Printf("[INFO] Password data read for instance %s", instanceID) + return nil + }) + + if err != nil { + return "", err } + + return passwordData, nil } type awsInstanceOpts struct { diff --git a/aws/resource_aws_instance_test.go b/aws/resource_aws_instance_test.go index 603f2d6e3bb..1b354453f63 100644 --- a/aws/resource_aws_instance_test.go +++ b/aws/resource_aws_instance_test.go @@ -1288,51 +1288,10 @@ func TestAccAWSInstance_associatePublic_overridePrivate(t *testing.T) { }) } -func TestAccAWSInstance_getPasswordData_true(t *testing.T) { - var before ec2.Instance - resName := "aws_instance.foo" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckInstanceDestroy, - Steps: []resource.TestStep{ - { - Config: testAccInstanceConfig_getPasswordData(true), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceExists(resName, &before), - resource.TestCheckResourceAttr(resName, "get_password_data", "true"), - resource.TestCheckResourceAttrSet(resName, "password_data"), - ), - }, - }, - }) -} - -func TestAccAWSInstance_getPasswordData_false(t *testing.T) { - var before ec2.Instance - resName := "aws_instance.foo" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckInstanceDestroy, - Steps: []resource.TestStep{ - { - Config: testAccInstanceConfig_getPasswordData(false), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceExists(resName, &before), - resource.TestCheckResourceAttr(resName, "get_password_data", "false"), - resource.TestCheckResourceAttr(resName, "password_data", ""), - ), - }, - }, - }) -} - func TestAccAWSInstance_getPasswordData_falseToTrue(t *testing.T) { var before, after ec2.Instance resName := "aws_instance.foo" + rInt := acctest.RandInt() resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -1340,7 +1299,7 @@ func TestAccAWSInstance_getPasswordData_falseToTrue(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_getPasswordData(false), + Config: testAccInstanceConfig_getPasswordData(false, rInt), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resName, &before), resource.TestCheckResourceAttr(resName, "get_password_data", "false"), @@ -1348,7 +1307,7 @@ func TestAccAWSInstance_getPasswordData_falseToTrue(t *testing.T) { ), }, { - Config: testAccInstanceConfig_getPasswordData(true), + Config: testAccInstanceConfig_getPasswordData(true, rInt), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resName, &after), testAccCheckInstanceNotRecreated(t, &before, &after), @@ -1363,6 +1322,7 @@ func TestAccAWSInstance_getPasswordData_falseToTrue(t *testing.T) { func TestAccAWSInstance_getPasswordData_trueToFalse(t *testing.T) { var before, after ec2.Instance resName := "aws_instance.foo" + rInt := acctest.RandInt() resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -1370,7 +1330,7 @@ func TestAccAWSInstance_getPasswordData_trueToFalse(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_getPasswordData(true), + Config: testAccInstanceConfig_getPasswordData(true, rInt), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resName, &before), resource.TestCheckResourceAttr(resName, "get_password_data", "true"), @@ -1378,7 +1338,7 @@ func TestAccAWSInstance_getPasswordData_trueToFalse(t *testing.T) { ), }, { - Config: testAccInstanceConfig_getPasswordData(false), + Config: testAccInstanceConfig_getPasswordData(false, rInt), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resName, &after), testAccCheckInstanceNotRecreated(t, &before, &after), @@ -2822,20 +2782,34 @@ resource "aws_instance" "foo" { }`, rInt, rInt) } -func testAccInstanceConfig_getPasswordData(val bool) string { +func testAccInstanceConfig_getPasswordData(val bool, rInt int) string { return fmt.Sprintf(` + # Find latest Microsoft Windows Server 2016 Core image (Amazon deletes old ones) + data "aws_ami" "win2016core" { + most_recent = true + + filter { + name = "owner-alias" + values = ["amazon"] + } + + filter { + name = "name" + values = ["Windows_Server-2016-English-Core-Base-*"] + } + } + resource "aws_key_pair" "foo" { - key_name = "tf-acc-winpasswordtest" + key_name = "tf-acctest-%d" public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAq6U3HQYC4g8WzU147gZZ7CKQH8TgYn3chZGRPxaGmHW1RUwsyEs0nmombmIhwxudhJ4ehjqXsDLoQpd6+c7BuLgTMvbv8LgE9LX53vnljFe1dsObsr/fYLvpU9LTlo8HgHAqO5ibNdrAUvV31ronzCZhms/Gyfdaue88Fd0/YnsZVGeOZPayRkdOHSpqme2CBrpa8myBeL1CWl0LkDG4+YCURjbaelfyZlIApLYKy3FcCan9XQFKaL32MJZwCgzfOvWIMtYcU8QtXMgnA3/I3gXk8YDUJv5P4lj0s/PJXuTM8DygVAUtebNwPuinS7wwonm5FXcWMuVGsVpG5K7FGQ== tf-acc-winpasswordtest" } resource "aws_instance" "foo" { - # us-west-2 (oregon) Microsoft Windows Server 2016 Core on Windows Server 2016 Base 10 - 2017.10.13 - ami = "ami-7730f60f" + ami = "${data.aws_ami.win2016core.id}" instance_type = "t2.medium" key_name = "${aws_key_pair.foo.key_name}" get_password_data = %t } - `, val) + `, rInt, val) } diff --git a/website/docs/d/instance.html.markdown b/website/docs/d/instance.html.markdown index 60e40235fab..28065d8b0cf 100644 --- a/website/docs/d/instance.html.markdown +++ b/website/docs/d/instance.html.markdown @@ -40,7 +40,7 @@ exactly match a pair on the desired Instance. several valid keys, for a full reference, check out [describe-instances in the AWS CLI reference][1]. -* `get_password_data` - (Optional) If true, wait for password data to become available and retrieve it. The password data is exported to the `password_data` attribute. +* `get_password_data` - (Optional) If true, wait for password data to become available and retrieve it. Useful for getting the administrator password for instances running Microsoft Windows. The password data is exported to the `password_data` attribute. See [GetPasswordData](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_GetPasswordData.html) for more information. ~> **NOTE:** At least one of `filter`, `instance_tags`, or `instance_id` must be specified. @@ -78,7 +78,9 @@ interpolation. * `monitoring` - Whether detailed monitoring is enabled or disabled for the Instance (Boolean). * `network_interface_id` - The ID of the network interface that was created with the Instance. * `password_data` - Base-64 encoded encrypted password data for the instance. + Useful for getting the administrator password for instances running Microsoft Windows. This attribute is only exported if `get_password_data` is true. + See [GetPasswordData](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_GetPasswordData.html) for more information. * `placement_group` - The placement group of the Instance. * `private_dns` - The private DNS name assigned to the Instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames diff --git a/website/docs/r/instance.html.markdown b/website/docs/r/instance.html.markdown index 8d1e4264175..4725d765ca0 100644 --- a/website/docs/r/instance.html.markdown +++ b/website/docs/r/instance.html.markdown @@ -64,7 +64,7 @@ instance. Amazon defaults this to `stop` for EBS-backed instances and instances. See [Shutdown Behavior](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingInstanceInitiatedShutdownBehavior) for more information. * `instance_type` - (Required) The type of instance to start * `key_name` - (Optional) The key name to use for the instance. -* `get_password_data` - (Optional) If true, wait for password data to become available and retrieve it. The password data is exported to the `password_data` attribute. +* `get_password_data` - (Optional) If true, wait for password data to become available and retrieve it. Useful for getting the administrator password for instances running Microsoft Windows. The password data is exported to the `password_data` attribute. See [GetPasswordData](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_GetPasswordData.html) for more information. * `monitoring` - (Optional) If true, the launched EC2 instance will have detailed monitoring enabled. (Available since v0.6.0) * `security_groups` - (Optional) A list of security group names to associate with. If you are creating Instances in a VPC, use `vpc_security_group_ids` instead. @@ -222,8 +222,10 @@ The following attributes are exported: * `placement_group` - The placement group of the instance. * `key_name` - The key name of the instance * `password_data` - Base-64 encoded encrypted password data for the instance. + Useful for getting the administrator password for instances running Microsoft Windows. This attribute is only exported if `get_password_data` is true. Note that this encrypted value will be stored in the state file, as with all exported attributes. + See [GetPasswordData](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_GetPasswordData.html) for more information. * `public_dns` - The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC * `public_ip` - The public IP address assigned to the instance, if applicable. **NOTE**: If you are using an [`aws_eip`](/docs/providers/aws/r/eip.html) with your instance, you should refer to the EIP's address directly and not use `public_ip`, as this field will change after the EIP is attached. From 9d94d530b1fbca194ac898127342fc76aacd5910 Mon Sep 17 00:00:00 2001 From: David Meyer Date: Tue, 13 Mar 2018 11:43:18 -0500 Subject: [PATCH 4/4] Fix instance import. --- aws/resource_aws_instance.go | 1 + 1 file changed, 1 insertion(+) diff --git a/aws/resource_aws_instance.go b/aws/resource_aws_instance.go index dc1cf01624c..826519f2253 100644 --- a/aws/resource_aws_instance.go +++ b/aws/resource_aws_instance.go @@ -777,6 +777,7 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { } d.Set("password_data", passwordData) } else { + d.Set("get_password_data", false) d.Set("password_data", nil) }