Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add user_data_replace_on_change field to aws_instance #23604

Merged
3 changes: 3 additions & 0 deletions .changelog/23604.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_instance: Add `user_data_replace_on_change` attribute
```
13 changes: 13 additions & 0 deletions internal/service/ec2/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,11 @@ func ResourceInstance() *schema.Resource {
return
},
},
"user_data_replace_on_change": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"volume_tags": tftags.TagsSchema(),
"vpc_security_group_ids": {
Type: schema.TypeSet,
Expand Down Expand Up @@ -695,6 +700,14 @@ func ResourceInstance() *schema.Resource {
customdiff.ComputedIf("launch_template.0.name", func(_ context.Context, diff *schema.ResourceDiff, meta interface{}) bool {
return diff.HasChange("launch_template.0.id")
}),
customdiff.ForceNewIf("user_data", func(_ context.Context, diff *schema.ResourceDiff, meta interface{}) bool {
replace := diff.Get("user_data_replace_on_change")
return replace.(bool)
}),
customdiff.ForceNewIf("user_data_base64", func(_ context.Context, diff *schema.ResourceDiff, meta interface{}) bool {
replace := diff.Get("user_data_replace_on_change")
return replace.(bool)
}),
),
}
}
Expand Down
126 changes: 126 additions & 0 deletions internal/service/ec2/instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3749,6 +3749,87 @@ func TestAccEC2Instance_UserData_unspecifiedToEmptyString(t *testing.T) {
})
}

func TestAccEC2Instance_UserData_replaceOnChangeFlag(t *testing.T) {
var v ec2.Instance
resourceName := "aws_instance.test"
rName := fmt.Sprintf("tf-testacc-instance-%s", sdkacctest.RandString(12))

var instanceId string

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID),
Providers: acctest.Providers,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
{
Config: testAccInstanceConfig_UserData_Specified_With_Replace_Flag_On(rName, "TestData1"),
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(resourceName, &v),
testResourceIdHasChanged(resourceName, &instanceId),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
// Switching should force a recreate
{
Config: testAccInstanceConfig_UserData_Specified_With_Replace_Flag_On(rName, "TestData2"),
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(resourceName, &v),
testResourceIdHasChanged(resourceName, &instanceId),
),
ExpectNonEmptyPlan: true,
PlanOnly: true,
},
},
})
}

func TestAccEC2Instance_UserDataBase64_replaceOnChangeFlag(t *testing.T) {
var v ec2.Instance
resourceName := "aws_instance.test"
rName := fmt.Sprintf("tf-testacc-instance-%s", sdkacctest.RandString(12))

var instanceId string

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID),
Providers: acctest.Providers,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
{
Config: testAccInstanceConfig_UserData64_Specified_With_Replace_Flag_On(rName, "3dc39dda39be1205215e776bad998da361a5955d"),
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(resourceName, &v),
testResourceIdHasChanged(resourceName, &instanceId),
),

},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
// Switching should force a recreate
{
Config: testAccInstanceConfig_UserData64_Specified_With_Replace_Flag_On(rName, "3dc39dda39be1205215e776bad998da361a5955e"),
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(resourceName, &v),
testResourceIdHasChanged(resourceName, &instanceId),
),
ExpectNonEmptyPlan: true,
PlanOnly: true,
},
},
})
}



func TestAccEC2Instance_hibernation(t *testing.T) {
var instance1, instance2 ec2.Instance
resourceName := "aws_instance.test"
Expand Down Expand Up @@ -4142,6 +4223,21 @@ func testAccCheckInstanceExistsWithProvider(n string, i *ec2.Instance, providerF
}
}

func testResourceIdHasChanged (resourceName string, instanceId *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
instance := s.RootModule().Resources[resourceName]
if(*instanceId == "") {
*instanceId = instance.Primary.ID
return nil
}
if(*instanceId != instance.Primary.ID){
return nil
} else {
return fmt.Errorf("A new instance should have been created")
}
}
}

func testAccCheckStopInstance(instance *ec2.Instance) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn
Expand Down Expand Up @@ -6501,6 +6597,36 @@ resource "aws_instance" "test" {
`)
}

func testAccInstanceConfig_UserData_Specified_With_Replace_Flag_On(rName string, userData string) string {
return acctest.ConfigCompose(
acctest.ConfigLatestAmazonLinuxHvmEbsAmi(),
testAccInstanceVPCConfig(rName, false),
fmt.Sprintf(`
resource "aws_instance" "test" {
ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id
instance_type = "t2.micro"
subnet_id = aws_subnet.test.id
user_data = "%[1]q"
user_data_replace_on_change = true
}
`, userData))
}

func testAccInstanceConfig_UserData64_Specified_With_Replace_Flag_On(rName string, userData string) string {
return acctest.ConfigCompose(
acctest.ConfigLatestAmazonLinuxHvmEbsAmi(),
testAccInstanceVPCConfig(rName, false),
fmt.Sprintf(`
resource "aws_instance" "test" {
ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id
instance_type = "t2.micro"
subnet_id = aws_subnet.test.id
user_data_base64 = base64encode(%[1]q)
user_data_replace_on_change = true
}
`, userData))
}

// testAccInstanceVPCConfig returns the configuration for tests that create
// 1) a VPC without IPv6 support
// 2) a subnet in the VPC that optionally assigns public IP addresses to ENIs
Expand Down
5 changes: 3 additions & 2 deletions website/docs/r/instance.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@ The following arguments are supported:
* `subnet_id` - (Optional) VPC Subnet ID to launch in.
* `tags` - (Optional) A map of tags to assign to the resource. Note that these tags apply to the instance and not block storage devices. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.
* `tenancy` - (Optional) Tenancy of the instance (if the instance is running in a VPC). An instance with a tenancy of dedicated runs on single-tenant hardware. The host tenancy is not supported for the import-instance command.
* `user_data` - (Optional) User data to provide when launching the instance. Do not pass gzip-compressed data via this argument; see `user_data_base64` instead. Updates to this field will trigger a stop/start of the EC2 instance.
* `user_data_base64` - (Optional) Can be used instead of `user_data` to pass base64-encoded binary data directly. Use this instead of `user_data` whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruption. Updates to this field will trigger a stop/start of the EC2 instance.
* `user_data` - (Optional) User data to provide when launching the instance. Do not pass gzip-compressed data via this argument; see `user_data_base64` instead. Updates to this field will trigger a stop/start of the EC2 instance by default. If the `user_data_replace_on_change` is set then updates to this field will trigger a destroy and recreate.
* `user_data_base64` - (Optional) Can be used instead of `user_data` to pass base64-encoded binary data directly. Use this instead of `user_data` whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruption. Updates to this field will trigger a stop/start of the EC2 instance by default. If the `user_data_replace_on_change` is set then updates to this field will trigger a destroy and recreate.
* `user_data_replace_on_change` - (Optional) When used in combination with `user_data` or `user_data_base64` will trigger a destroy and recreate when set to `true`. Defaults to `false` if not set.
* `volume_tags` - (Optional) A map of tags to assign, at instance-creation time, to root and EBS volumes.

~> **NOTE:** Do not use `volume_tags` if you plan to manage block device tags outside the `aws_instance` configuration, such as using `tags` in an [`aws_ebs_volume`](/docs/providers/aws/r/ebs_volume.html) resource attached via [`aws_volume_attachment`](/docs/providers/aws/r/volume_attachment.html). Doing so will result in resource cycling and inconsistent behavior.
Expand Down