Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/45486.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_imagebuilder_image: Add `deletion_settings` configuration block to enable managed deletion of images and associated AWS resources
```
98 changes: 90 additions & 8 deletions internal/service/imagebuilder/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,20 @@ func resourceImage() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"deletion_settings": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"execution_role": {
Type: schema.TypeString,
Required: true,
ValidateFunc: verify.ValidARN,
},
},
},
},
"workflow": {
Type: schema.TypeSet,
Computed: true,
Expand Down Expand Up @@ -387,6 +401,9 @@ func resourceImageRead(ctx context.Context, d *schema.ResourceData, meta any) di
}
d.Set("platform", image.Platform)
d.Set(names.AttrVersion, image.Version)
if v, ok := d.GetOk("deletion_settings"); ok {
d.Set("deletion_settings", v)
}
if image.Workflows != nil {
if err := d.Set("workflow", flattenWorkflowConfigurations(image.Workflows)); err != nil {
return sdkdiag.AppendErrorf(diags, "setting workflow: %s", err)
Expand All @@ -413,21 +430,86 @@ func resourceImageDelete(ctx context.Context, d *schema.ResourceData, meta any)
conn := meta.(*conns.AWSClient).ImageBuilderClient(ctx)

log.Printf("[DEBUG] Deleting Image Builder Image: %s", d.Id())
_, err := conn.DeleteImage(ctx, &imagebuilder.DeleteImageInput{
ImageBuildVersionArn: aws.String(d.Id()),
})

if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) {
return diags
}
if v, ok := d.GetOk("deletion_settings"); ok && len(v.([]any)) > 0 && v.([]any)[0] != nil {
deletionSettings := v.([]any)[0].(map[string]any)

if err != nil {
return sdkdiag.AppendErrorf(diags, "deleting Image Builder Image (%s): %s", d.Id(), err)
input := &imagebuilder.StartResourceStateUpdateInput{
ResourceArn: aws.String(d.Id()),
State: &awstypes.ResourceState{
Status: awstypes.ResourceStatusDeleted,
},
}

if executionRole, ok := deletionSettings["execution_role"].(string); ok && executionRole != "" {
input.ExecutionRole = aws.String(executionRole)
}

image, err := findImageByARN(ctx, conn, d.Id())
if err != nil {
return sdkdiag.AppendErrorf(diags, "reading Image Builder Image (%s) for deletion: %s", d.Id(), err)
}

if image.Type == awstypes.ImageTypeDocker {
input.IncludeResources = &awstypes.ResourceStateUpdateIncludeResources{
Containers: true,
}
} else {
input.IncludeResources = &awstypes.ResourceStateUpdateIncludeResources{
Amis: true,
Snapshots: true,
}
}

output, err := conn.StartResourceStateUpdate(ctx, input)
if err != nil {
return sdkdiag.AppendErrorf(diags, "starting resource state update for Image Builder Image (%s): %s", d.Id(), err)
}

if err := waitLifecycleExecution(ctx, conn, aws.ToString(output.LifecycleExecutionId)); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for Image Builder Image (%s) deletion: %s", d.Id(), err)
}
} else {
_, err := conn.DeleteImage(ctx, &imagebuilder.DeleteImageInput{
ImageBuildVersionArn: aws.String(d.Id()),
})

if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) {
return diags
}

if err != nil {
return sdkdiag.AppendErrorf(diags, "deleting Image Builder Image (%s): %s", d.Id(), err)
}
}

return diags
}

func waitLifecycleExecution(ctx context.Context, conn *imagebuilder.Client, id string) error {
for {
output, err := conn.GetLifecycleExecution(ctx, &imagebuilder.GetLifecycleExecutionInput{
LifecycleExecutionId: aws.String(id),
})
if err != nil {
return err
}

status := string(output.LifecycleExecution.State.Status)
if status == "SUCCESS" {
return nil
}
if status == "FAILED" {
return errors.New(aws.ToString(output.LifecycleExecution.State.Reason))
}
if status == "CANCELLED" {
return errors.New("lifecycle execution was cancelled")
}

time.Sleep(10 * time.Second)
}
}

func findImageByARN(ctx context.Context, conn *imagebuilder.Client, arn string) (*awstypes.Image, error) {
input := &imagebuilder.GetImageInput{
ImageBuildVersionArn: aws.String(arn),
Expand Down
82 changes: 82 additions & 0 deletions internal/service/imagebuilder/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,28 @@ func TestAccImageBuilderImage_workflows(t *testing.T) {
})
}

func TestAccImageBuilderImage_deletionSettings(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_imagebuilder_image.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.ImageBuilderServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckImageDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccImageConfig_deletionSettings(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckImageExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, "deletion_settings.#", "1"),
),
},
},
})
}

func testAccCheckImageDestroy(ctx context.Context) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).ImageBuilderClient(ctx)
Expand Down Expand Up @@ -1069,3 +1091,63 @@ resource "aws_imagebuilder_image" "test" {
}
`)
}

func testAccImageConfig_deletionSettings(rName string) string {
return acctest.ConfigCompose(
testAccImageBaseConfig(rName),
fmt.Sprintf(`
resource "aws_iam_role" "deletion" {
name = "%[1]s-deletion"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "imagebuilder.${data.aws_partition.current.dns_suffix}"
}
}]
})
}

resource "aws_iam_role_policy" "deletion" {
name = "%[1]s-deletion"
role = aws_iam_role.deletion.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"imagebuilder:GetImage",
"imagebuilder:GetLifecycleExecution",
"imagebuilder:DeleteImage",
"ec2:DescribeImages",
"ec2:DescribeImageAttribute",
"ec2:DescribeSnapshots",
"ec2:DeregisterImage",
"ec2:DeleteSnapshot",
"ecr:BatchDeleteImage",
"ecr:DescribeImages",
"ecr:DescribeRepositories"
]
Resource = "*"
}]
})
}

resource "aws_imagebuilder_image" "test" {
image_recipe_arn = aws_imagebuilder_image_recipe.test.arn
infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn

deletion_settings {
execution_role = aws_iam_role.deletion.arn
}

depends_on = [aws_iam_role_policy.deletion]

tags = {
Name = %[1]q
}
}
`, rName))
}
16 changes: 16 additions & 0 deletions website/docs/r/imagebuilder_image.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ The following arguments are optional:

* `region` - (Optional) Region where this resource will be [managed](https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints). Defaults to the Region set in the [provider configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#aws-configuration-reference).
* `container_recipe_arn` - (Optional) - Amazon Resource Name (ARN) of the container recipe.
* `deletion_settings` - (Optional) Configuration block with deletion settings. Detailed below.
* `distribution_configuration_arn` - (Optional) Amazon Resource Name (ARN) of the Image Builder Distribution Configuration.
* `enhanced_image_metadata_enabled` - (Optional) Whether additional information about the image being created is collected. Defaults to `true`.
* `execution_role` - (Optional) Amazon Resource Name (ARN) of the service-linked role to be used by Image Builder to [execute workflows](https://docs.aws.amazon.com/imagebuilder/latest/userguide/manage-image-workflows.html).
Expand Down Expand Up @@ -83,6 +84,21 @@ The following arguments are required:
* `name` - (Required) The name of the Workflow parameter.
* `value` - (Required) The value of the Workflow parameter.

### deletion_settings

When configured, enables managed deletion of the image and all associated resources using Image Builder's lifecycle management. When not configured, uses standard deletion which only removes the image record.

For AMI-based images, managed deletion removes:
* AMIs
* EBS snapshots

For container images, managed deletion removes:
* Container images from ECR repositories

The following arguments are required:

* `execution_role` - (Required) Amazon Resource Name (ARN) of the IAM role used to delete the image and associated resources.

## Attribute Reference

This resource exports the following attributes in addition to the arguments above:
Expand Down
Loading