From 071b0cd4becf64fa3267afde728130c6aa51b9d7 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Tue, 1 Feb 2022 22:07:49 +0100 Subject: [PATCH 01/14] Implement aws_imagebuilder_container_recipe minimal version --- internal/provider/provider.go | 1 + .../service/imagebuilder/container_recipe.go | 212 ++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 internal/service/imagebuilder/container_recipe.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index ff75d0d1c218..2928d2b10969 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1389,6 +1389,7 @@ func Provider() *schema.Provider { "aws_iam_user_ssh_key": iam.ResourceUserSSHKey(), "aws_imagebuilder_component": imagebuilder.ResourceComponent(), + "aws_imagebuilder_container_recipe": imagebuilder.ResourceContainerRecipe(), "aws_imagebuilder_distribution_configuration": imagebuilder.ResourceDistributionConfiguration(), "aws_imagebuilder_image": imagebuilder.ResourceImage(), "aws_imagebuilder_image_pipeline": imagebuilder.ResourceImagePipeline(), diff --git a/internal/service/imagebuilder/container_recipe.go b/internal/service/imagebuilder/container_recipe.go new file mode 100644 index 000000000000..54f5c76803ca --- /dev/null +++ b/internal/service/imagebuilder/container_recipe.go @@ -0,0 +1,212 @@ +package imagebuilder + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/imagebuilder" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceContainerRecipe() *schema.Resource { + return &schema.Resource{ + Create: resourceContainerRecipeCreate, + Read: resourceContainerRecipeRead, + Delete: resourceContainerRecipeDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "component": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MinItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "component_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "parameter": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 256), + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + "container_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"DOCKER"}, false), + }, + "dockerfile_template_data": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 16000), + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 128), + }, + "parent_image": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + "target_repository": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "repository_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + "service": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"ECR"}, false), + }, + }, + }, + }, + "version": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceContainerRecipeCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).ImageBuilderConn + + input := &imagebuilder.CreateContainerRecipeInput{ + ClientToken: aws.String(resource.UniqueId()), + } + + if v, ok := d.GetOk("component"); ok && len(v.([]interface{})) > 0 { + input.Components = expandComponentConfigurations(v.([]interface{})) + } + + if v, ok := d.GetOk("container_type"); ok { + input.ContainerType = aws.String(v.(string)) + } + + if v, ok := d.GetOk("dockerfile_template_data"); ok { + input.DockerfileTemplateData = aws.String(v.(string)) + } + + if v, ok := d.GetOk("name"); ok { + input.Name = aws.String(v.(string)) + } + + if v, ok := d.GetOk("parent_image"); ok { + input.ParentImage = aws.String(v.(string)) + } + + if v, ok := d.GetOk("target_repository"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.TargetRepository = expandTargetContainerRepository(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("version"); ok { + input.SemanticVersion = aws.String(v.(string)) + } + + output, err := conn.CreateContainerRecipe(input) + + if err != nil { + return fmt.Errorf("error creating Image Builder Container Recipe: %w", err) + } + + if output == nil { + return fmt.Errorf("error creating Image Builder Container Recipe: empty response") + } + + d.SetId(aws.StringValue(output.ContainerRecipeArn)) + + return resourceContainerRecipeRead(d, meta) +} + +func resourceContainerRecipeRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).ImageBuilderConn + + input := &imagebuilder.GetContainerRecipeInput{ + ContainerRecipeArn: aws.String(d.Id()), + } + + output, err := conn.GetContainerRecipe(input) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, imagebuilder.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] Image Builder Container Recipe (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + containerRecipe := output.ContainerRecipe + + d.Set("arn", containerRecipe.Arn) + d.Set("component", flattenComponentConfigurations(containerRecipe.Components)) + d.Set("container_type", containerRecipe.ContainerType) + d.Set("dockerfile_template_data", containerRecipe.DockerfileTemplateData) + d.Set("name", containerRecipe.Name) + d.Set("parent_image", containerRecipe.ParentImage) + d.Set("target_repository", []interface{}{flattenTargetContainerRepository(containerRecipe.TargetRepository)}) + d.Set("version", containerRecipe.Version) + + return nil +} + +func resourceContainerRecipeDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).ImageBuilderConn + + input := &imagebuilder.DeleteContainerRecipeInput{ + ContainerRecipeArn: aws.String(d.Id()), + } + + _, err := conn.DeleteContainerRecipe(input) + + if tfawserr.ErrCodeEquals(err, imagebuilder.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Image Builder Container Recipe (%s): %w", d.Id(), err) + } + + return nil +} From 2ee023bd2635f11db3359d190a8ad7fcd9fd492c Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sat, 5 Feb 2022 18:10:43 +0100 Subject: [PATCH 02/14] Implement basic and disappear tests --- .../imagebuilder/container_recipe_test.go | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 internal/service/imagebuilder/container_recipe_test.go diff --git a/internal/service/imagebuilder/container_recipe_test.go b/internal/service/imagebuilder/container_recipe_test.go new file mode 100644 index 000000000000..b128dface2db --- /dev/null +++ b/internal/service/imagebuilder/container_recipe_test.go @@ -0,0 +1,168 @@ +package imagebuilder_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/imagebuilder" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfimagebuilder "github.com/hashicorp/terraform-provider-aws/internal/service/imagebuilder" +) + +func TestAccImageBuilderContainerRecipe_basic(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeNameConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "imagebuilder", regexp.MustCompile(fmt.Sprintf("container-recipe/%s/1.0.0", rName))), + resource.TestCheckResourceAttr(resourceName, "component.#", "1"), + acctest.CheckResourceAttrRegionalARNAccountID(resourceName, "component.0.component_arn", "imagebuilder", "aws", "component/update-linux/x.x.x"), + resource.TestCheckResourceAttr(resourceName, "container_type", "DOCKER"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + acctest.CheckResourceAttrRegionalARNAccountID(resourceName, "parent_image", "imagebuilder", "aws", "image/amazon-linux-x86-2/x.x.x"), + resource.TestCheckResourceAttr(resourceName, "target_repository.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "target_repository.0.repository_name", "aws_ecr_repository.test", "name"), + resource.TestCheckResourceAttr(resourceName, "target_repository.0.service", "ECR"), + resource.TestCheckResourceAttr(resourceName, "version", "1.0.0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_disappears(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeNameConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfimagebuilder.ResourceContainerRecipe(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckContainerRecipeDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).ImageBuilderConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_imagebuilder_container_recipe" { + continue + } + + input := &imagebuilder.GetContainerRecipeInput{ + ContainerRecipeArn: aws.String(rs.Primary.ID), + } + + output, err := conn.GetContainerRecipe(input) + + if tfawserr.ErrCodeEquals(err, imagebuilder.ErrCodeResourceNotFoundException) { + continue + } + + if err != nil { + return fmt.Errorf("error getting Image Builder Container Recipe (%s): %w", rs.Primary.ID, err) + } + + if output != nil { + return fmt.Errorf("Image Builder Container Recipe (%s) still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckContainerRecipeExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource not found: %s", resourceName) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ImageBuilderConn + + input := &imagebuilder.GetContainerRecipeInput{ + ContainerRecipeArn: aws.String(rs.Primary.ID), + } + + _, err := conn.GetContainerRecipe(input) + + if err != nil { + return fmt.Errorf("error getting Image Builder Container Recipe (%s): %w", rs.Primary.ID, err) + } + + return nil + } +} + +func testAccContainerRecipeBaseConfig(rName string) string { + return fmt.Sprintf(` +data "aws_region" "current" {} + +data "aws_partition" "current" {} + +resource "aws_ecr_repository" "test" { + name = %[1]q +} + +`, rName) +} + +func testAccContainerRecipeNameConfig(rName string) string { + return acctest.ConfigCompose( + testAccContainerRecipeBaseConfig(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_container_recipe" "test" { + container_type = "DOCKER" + name = %[1]q + parent_image = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:image/amazon-linux-x86-2/x.x.x" + version = "1.0.0" + + component { + component_arn = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:component/update-linux/x.x.x" + } + + dockerfile_template_data = < Date: Sat, 5 Feb 2022 22:56:07 +0100 Subject: [PATCH 03/14] Implement remaining arguments --- .../service/imagebuilder/container_recipe.go | 196 +++ .../imagebuilder/container_recipe_test.go | 1228 ++++++++++++++++- 2 files changed, 1382 insertions(+), 42 deletions(-) diff --git a/internal/service/imagebuilder/container_recipe.go b/internal/service/imagebuilder/container_recipe.go index 54f5c76803ca..54d2afde960f 100644 --- a/internal/service/imagebuilder/container_recipe.go +++ b/internal/service/imagebuilder/container_recipe.go @@ -3,6 +3,7 @@ package imagebuilder import ( "fmt" "log" + "regexp" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/imagebuilder" @@ -65,12 +66,140 @@ func ResourceContainerRecipe() *schema.Resource { ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"DOCKER"}, false), }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, "dockerfile_template_data": { Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 16000), }, + "dockerfile_template_uri": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"dockerfile_template_data", "dockerfile_template_uri"}, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^s3://`), "must begin with s3://"), + }, + "instance_configuration": { + Type: schema.TypeList, + Optional: true, + Computed: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "block_device_mapping": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "device_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + "ebs": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "delete_on_termination": { + // Use TypeString to allow an "unspecified" value, + // since TypeBool only has true/false with false default. + // The conversion from bare true/false values in + // configurations to TypeString value is currently safe. + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: verify.SuppressEquivalentTypeStringBoolean, + ValidateFunc: verify.ValidTypeStringNullableBoolean, + }, + "encrypted": { + // Use TypeString to allow an "unspecified" value, + // since TypeBool only has true/false with false default. + // The conversion from bare true/false values in + // configurations to TypeString value is currently safe. + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: verify.SuppressEquivalentTypeStringBoolean, + ValidateFunc: verify.ValidTypeStringNullableBoolean, + }, + "iops": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(100, 64000), + }, + "kms_key_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + "snapshot_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + "volume_size": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 16000), + }, + "volume_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(imagebuilder.EbsVolumeType_Values(), false), + }, + }, + }, + }, + "no_device": { + // Use TypeBool to allow an "unspecified" value of false, + // since the API uses an empty string ("") as true and + // this is not compatible with TypeString's zero value. + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "virtual_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + }, + }, + }, + "image": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + }, + }, + }, + "kms_key_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, "name": { Type: schema.TypeString, Required: true, @@ -83,6 +212,7 @@ func ResourceContainerRecipe() *schema.Resource { ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 1024), }, + // tags "target_repository": { Type: schema.TypeList, Required: true, @@ -108,6 +238,12 @@ func ResourceContainerRecipe() *schema.Resource { Required: true, ForceNew: true, }, + "working_directory": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, }, } } @@ -127,10 +263,26 @@ func resourceContainerRecipeCreate(d *schema.ResourceData, meta interface{}) err input.ContainerType = aws.String(v.(string)) } + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + if v, ok := d.GetOk("dockerfile_template_data"); ok { input.DockerfileTemplateData = aws.String(v.(string)) } + if v, ok := d.GetOk("dockerfile_template_uri"); ok { + input.DockerfileTemplateUri = aws.String(v.(string)) + } + + if v, ok := d.GetOk("instance_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.InstanceConfiguration = expandInstanceConfiguration(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("kms_key_id"); ok { + input.KmsKeyId = aws.String(v.(string)) + } + if v, ok := d.GetOk("name"); ok { input.Name = aws.String(v.(string)) } @@ -147,6 +299,10 @@ func resourceContainerRecipeCreate(d *schema.ResourceData, meta interface{}) err input.SemanticVersion = aws.String(v.(string)) } + if v, ok := d.GetOk("working_directory"); ok { + input.WorkingDirectory = aws.String(v.(string)) + } + output, err := conn.CreateContainerRecipe(input) if err != nil { @@ -182,11 +338,15 @@ func resourceContainerRecipeRead(d *schema.ResourceData, meta interface{}) error d.Set("arn", containerRecipe.Arn) d.Set("component", flattenComponentConfigurations(containerRecipe.Components)) d.Set("container_type", containerRecipe.ContainerType) + d.Set("description", containerRecipe.Description) d.Set("dockerfile_template_data", containerRecipe.DockerfileTemplateData) + d.Set("instance_configuration", []interface{}{flattenInstanceConfiguration(containerRecipe.InstanceConfiguration)}) + d.Set("kms_key_id", containerRecipe.KmsKeyId) d.Set("name", containerRecipe.Name) d.Set("parent_image", containerRecipe.ParentImage) d.Set("target_repository", []interface{}{flattenTargetContainerRepository(containerRecipe.TargetRepository)}) d.Set("version", containerRecipe.Version) + d.Set("working_directory", containerRecipe.WorkingDirectory) return nil } @@ -210,3 +370,39 @@ func resourceContainerRecipeDelete(d *schema.ResourceData, meta interface{}) err return nil } + +func expandInstanceConfiguration(tfMap map[string]interface{}) *imagebuilder.InstanceConfiguration { + if tfMap == nil { + return nil + } + + apiObject := &imagebuilder.InstanceConfiguration{} + + if v, ok := tfMap["block_device_mapping"].(*schema.Set); ok && v.Len() > 0 { + apiObject.BlockDeviceMappings = expandInstanceBlockDeviceMappings(v.List()) + } + + if v, ok := tfMap["image"].(string); ok && v != "" { + apiObject.Image = aws.String(v) + } + + return apiObject +} + +func flattenInstanceConfiguration(apiObject *imagebuilder.InstanceConfiguration) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.BlockDeviceMappings; v != nil { + tfMap["block_device_mapping"] = flattenInstanceBlockDeviceMappings(v) + } + + if v := apiObject.Image; v != nil { + tfMap["image"] = aws.StringValue(v) + } + + return tfMap +} diff --git a/internal/service/imagebuilder/container_recipe_test.go b/internal/service/imagebuilder/container_recipe_test.go index b128dface2db..98fa77c336e2 100644 --- a/internal/service/imagebuilder/container_recipe_test.go +++ b/internal/service/imagebuilder/container_recipe_test.go @@ -33,13 +33,20 @@ func TestAccImageBuilderContainerRecipe_basic(t *testing.T) { acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "imagebuilder", regexp.MustCompile(fmt.Sprintf("container-recipe/%s/1.0.0", rName))), resource.TestCheckResourceAttr(resourceName, "component.#", "1"), acctest.CheckResourceAttrRegionalARNAccountID(resourceName, "component.0.component_arn", "imagebuilder", "aws", "component/update-linux/x.x.x"), + resource.TestCheckResourceAttr(resourceName, "component.0.parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "container_type", "DOCKER"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttrSet(resourceName, "dockerfile_template_data"), + // resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + // resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kms_key_id", ""), resource.TestCheckResourceAttr(resourceName, "name", rName), acctest.CheckResourceAttrRegionalARNAccountID(resourceName, "parent_image", "imagebuilder", "aws", "image/amazon-linux-x86-2/x.x.x"), resource.TestCheckResourceAttr(resourceName, "target_repository.#", "1"), resource.TestCheckResourceAttrPair(resourceName, "target_repository.0.repository_name", "aws_ecr_repository.test", "name"), resource.TestCheckResourceAttr(resourceName, "target_repository.0.service", "ECR"), resource.TestCheckResourceAttr(resourceName, "version", "1.0.0"), + resource.TestCheckResourceAttr(resourceName, "working_directory", ""), ), }, { @@ -73,85 +80,1220 @@ func TestAccImageBuilderContainerRecipe_disappears(t *testing.T) { }) } +func TestAccImageBuilderContainerRecipe_component(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeComponentConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "component.#", "2"), + acctest.CheckResourceAttrRegionalARNAccountID(resourceName, "component.0.component_arn", "imagebuilder", "aws", "component/update-linux/x.x.x"), + acctest.CheckResourceAttrRegionalARNAccountID(resourceName, "component.1.component_arn", "imagebuilder", "aws", "component/aws-cli-version-2-linux/x.x.x"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_componentParameter(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeComponentParameterConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "component.#", "1"), + resource.TestCheckResourceAttr(resourceName, "component.0.parameter.#", "2"), + resource.TestCheckResourceAttr(resourceName, "component.0.parameter.0.name", "Parameter1"), + resource.TestCheckResourceAttr(resourceName, "component.0.parameter.0.value", "Value1"), + resource.TestCheckResourceAttr(resourceName, "component.0.parameter.1.name", "Parameter2"), + resource.TestCheckResourceAttr(resourceName, "component.0.parameter.1.value", "Value2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_description(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeDescriptionConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", "description"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_dockerfileTemplateURI(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerDockerfileTemplateURIConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "dockerfile_template_data"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"dockerfile_template_uri"}, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_InstanceConfiguration_BlockDeviceMapping_deviceName(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeInstanceConfigurationBlockDeviceMappingDeviceNameConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.device_name", "/dev/xvda"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_InstanceConfiguration_BlockDeviceMappingEBS_deleteOnTermination(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeInstanceConfigurationBlockDeviceMappingEBSDeleteOnTerminationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.0.delete_on_termination", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_InstanceConfiguration_BlockDeviceMappingEBS_encrypted(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeInstanceConfigurationBlockDeviceMappingEBSEncryptedConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.0.encrypted", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_InstanceConfiguration_BlockDeviceMappingEBS_iops(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeInstanceConfigurationBlockDeviceMappingEBSIOPSConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.0.iops", "100"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_InstanceConfiguration_BlockDeviceMappingEBS_kmsKeyID(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + kmsKeyResourceName := "aws_kms_key.test" + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeInstanceConfigurationBlockDeviceMappingEBSKMSKeyIDConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.0.kms_key_id", kmsKeyResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_InstanceConfiguration_BlockDeviceMappingEBS_snapshotID(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + ebsSnapshotResourceName := "aws_ebs_snapshot.test" + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeInstanceConfigurationBlockDeviceMappingEBSSnapshotIDConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.0.snapshot_id", ebsSnapshotResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_InstanceConfiguration_BlockDeviceMappingEBS_volumeSize(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeInstanceConfigurationBlockDeviceMappingEBSVolumeSizeConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.0.volume_size", "20"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_InstanceConfiguration_BlockDeviceMappingEBS_volumeType(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeInstanceConfigurationBlockDeviceMappingEBSVolumeTypeConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.ebs.0.volume_type", imagebuilder.EbsVolumeTypeGp2), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_InstanceConfiguration_BlockDeviceMapping_noDevice(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeInstanceConfigurationBlockDeviceMappingNoDeviceConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.no_device", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_InstanceConfiguration_BlockDeviceMapping_virtualName(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeInstanceConfigurationBlockDeviceMappingVirtualNameConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.0.virtual_name", "ephemeral0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_InstanceConfiguration_Image(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + imageDataSourceName := "data.aws_ami.test" + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeInstanceConfigurationImageConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "instance_configuration.0.image", imageDataSourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_kmsKeyID(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + kmsKeyResourceName := "aws_kms_key.test" + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeKmsKeyIDConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "kms_key_id", kmsKeyResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccImageBuilderContainerRecipe_workingDirectory(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeWorkingDirectoryConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "working_directory", "/tmp"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckContainerRecipeDestroy(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).ImageBuilderConn - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_imagebuilder_container_recipe" { - continue - } + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_imagebuilder_container_recipe" { + continue + } + + input := &imagebuilder.GetContainerRecipeInput{ + ContainerRecipeArn: aws.String(rs.Primary.ID), + } + + output, err := conn.GetContainerRecipe(input) + + if tfawserr.ErrCodeEquals(err, imagebuilder.ErrCodeResourceNotFoundException) { + continue + } + + if err != nil { + return fmt.Errorf("error getting Image Builder Container Recipe (%s): %w", rs.Primary.ID, err) + } + + if output != nil { + return fmt.Errorf("Image Builder Container Recipe (%s) still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckContainerRecipeExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource not found: %s", resourceName) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ImageBuilderConn + + input := &imagebuilder.GetContainerRecipeInput{ + ContainerRecipeArn: aws.String(rs.Primary.ID), + } + + _, err := conn.GetContainerRecipe(input) + + if err != nil { + return fmt.Errorf("error getting Image Builder Container Recipe (%s): %w", rs.Primary.ID, err) + } + + return nil + } +} + +func testAccContainerRecipeBaseConfig(rName string) string { + return fmt.Sprintf(` +data "aws_region" "current" {} + +data "aws_partition" "current" {} + +resource "aws_ecr_repository" "test" { + name = %[1]q +} + +`, rName) +} + +func testAccContainerRecipeNameConfig(rName string) string { + return acctest.ConfigCompose( + testAccContainerRecipeBaseConfig(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_container_recipe" "test" { + name = %[1]q + container_type = "DOCKER" + parent_image = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:image/amazon-linux-x86-2/x.x.x" + version = "1.0.0" + + component { + component_arn = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:component/update-linux/x.x.x" + } + + dockerfile_template_data = < Date: Sat, 5 Feb 2022 23:10:53 +0100 Subject: [PATCH 04/14] Fix terrafmt errors --- .../service/imagebuilder/container_recipe.go | 3 + .../imagebuilder/container_recipe_test.go | 269 +++++++++--------- 2 files changed, 138 insertions(+), 134 deletions(-) diff --git a/internal/service/imagebuilder/container_recipe.go b/internal/service/imagebuilder/container_recipe.go index 54d2afde960f..23755505c846 100644 --- a/internal/service/imagebuilder/container_recipe.go +++ b/internal/service/imagebuilder/container_recipe.go @@ -38,11 +38,13 @@ func ResourceContainerRecipe() *schema.Resource { "component_arn": { Type: schema.TypeString, Required: true, + ForceNew: true, ValidateFunc: verify.ValidARN, }, "parameter": { Type: schema.TypeSet, Optional: true, + ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -77,6 +79,7 @@ func ResourceContainerRecipe() *schema.Resource { Optional: true, Computed: true, ForceNew: true, + ExactlyOneOf: []string{"dockerfile_template_data", "dockerfile_template_uri"}, ValidateFunc: validation.StringLenBetween(1, 16000), }, "dockerfile_template_uri": { diff --git a/internal/service/imagebuilder/container_recipe_test.go b/internal/service/imagebuilder/container_recipe_test.go index 98fa77c336e2..1a4e6789c4fd 100644 --- a/internal/service/imagebuilder/container_recipe_test.go +++ b/internal/service/imagebuilder/container_recipe_test.go @@ -37,6 +37,7 @@ func TestAccImageBuilderContainerRecipe_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "container_type", "DOCKER"), resource.TestCheckResourceAttr(resourceName, "description", ""), resource.TestCheckResourceAttrSet(resourceName, "dockerfile_template_data"), + // template uri // resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), // resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.#", "0"), resource.TestCheckResourceAttr(resourceName, "kms_key_id", ""), @@ -633,10 +634,10 @@ func testAccContainerRecipeNameConfig(rName string) string { testAccContainerRecipeBaseConfig(rName), fmt.Sprintf(` resource "aws_imagebuilder_container_recipe" "test" { - name = %[1]q + name = %[1]q container_type = "DOCKER" - parent_image = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:image/amazon-linux-x86-2/x.x.x" - version = "1.0.0" + parent_image = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:image/amazon-linux-x86-2/x.x.x" + version = "1.0.0" component { component_arn = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:component/update-linux/x.x.x" @@ -661,10 +662,10 @@ func testAccContainerRecipeComponentConfig(rName string) string { testAccContainerRecipeBaseConfig(rName), fmt.Sprintf(` resource "aws_imagebuilder_container_recipe" "test" { - name = %[1]q + name = %[1]q container_type = "DOCKER" - parent_image = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:image/amazon-linux-x86-2/x.x.x" - version = "1.0.0" + parent_image = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:image/amazon-linux-x86-2/x.x.x" + version = "1.0.0" component { component_arn = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:component/update-linux/x.x.x" @@ -717,24 +718,24 @@ EOF } resource "aws_imagebuilder_container_recipe" "test" { - name = %[1]q + name = %[1]q container_type = "DOCKER" - parent_image = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:image/amazon-linux-x86-2/x.x.x" - version = "1.0.0" + parent_image = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:image/amazon-linux-x86-2/x.x.x" + version = "1.0.0" component { component_arn = aws_imagebuilder_component.test.arn - parameter { - name = "Parameter1" - value = "Value1" - } + parameter { + name = "Parameter1" + value = "Value1" + } - parameter { - name = "Parameter2" - value = "Value2" - } + parameter { + name = "Parameter2" + value = "Value2" + } } dockerfile_template_data = < Date: Sun, 6 Feb 2022 18:00:34 +0100 Subject: [PATCH 05/14] Add support for tags --- .../service/imagebuilder/container_recipe.go | 56 ++++++++- .../imagebuilder/container_recipe_test.go | 116 +++++++++++++++++- 2 files changed, 166 insertions(+), 6 deletions(-) diff --git a/internal/service/imagebuilder/container_recipe.go b/internal/service/imagebuilder/container_recipe.go index 23755505c846..57694149c722 100644 --- a/internal/service/imagebuilder/container_recipe.go +++ b/internal/service/imagebuilder/container_recipe.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) @@ -19,6 +20,7 @@ func ResourceContainerRecipe() *schema.Resource { return &schema.Resource{ Create: resourceContainerRecipeCreate, Read: resourceContainerRecipeRead, + Update: resourceContainerRecipeUpdate, Delete: resourceContainerRecipeDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, @@ -92,7 +94,6 @@ func ResourceContainerRecipe() *schema.Resource { "instance_configuration": { Type: schema.TypeList, Optional: true, - Computed: true, ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ @@ -215,7 +216,8 @@ func ResourceContainerRecipe() *schema.Resource { ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 1024), }, - // tags + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), "target_repository": { Type: schema.TypeList, Required: true, @@ -248,11 +250,14 @@ func ResourceContainerRecipe() *schema.Resource { ValidateFunc: validation.StringLenBetween(1, 1024), }, }, + CustomizeDiff: verify.SetTagsDiff, } } func resourceContainerRecipeCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).ImageBuilderConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) input := &imagebuilder.CreateContainerRecipeInput{ ClientToken: aws.String(resource.UniqueId()), @@ -294,6 +299,10 @@ func resourceContainerRecipeCreate(d *schema.ResourceData, meta interface{}) err input.ParentImage = aws.String(v.(string)) } + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + if v, ok := d.GetOk("target_repository"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { input.TargetRepository = expandTargetContainerRepository(v.([]interface{})[0].(map[string]interface{})) } @@ -323,6 +332,8 @@ func resourceContainerRecipeCreate(d *schema.ResourceData, meta interface{}) err func resourceContainerRecipeRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).ImageBuilderConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig input := &imagebuilder.GetContainerRecipeInput{ ContainerRecipeArn: aws.String(d.Id()), @@ -336,6 +347,14 @@ func resourceContainerRecipeRead(d *schema.ResourceData, meta interface{}) error return nil } + if err != nil { + return fmt.Errorf("error creating Image Builder Container Recipe: %w", err) + } + + if output == nil { + return fmt.Errorf("error creating Image Builder Container Recipe: empty response") + } + containerRecipe := output.ContainerRecipe d.Set("arn", containerRecipe.Arn) @@ -343,10 +362,27 @@ func resourceContainerRecipeRead(d *schema.ResourceData, meta interface{}) error d.Set("container_type", containerRecipe.ContainerType) d.Set("description", containerRecipe.Description) d.Set("dockerfile_template_data", containerRecipe.DockerfileTemplateData) - d.Set("instance_configuration", []interface{}{flattenInstanceConfiguration(containerRecipe.InstanceConfiguration)}) + + if containerRecipe.InstanceConfiguration != nil { + d.Set("instance_configuration", []interface{}{flattenInstanceConfiguration(containerRecipe.InstanceConfiguration)}) + } else { + d.Set("instance_configuration", nil) + } + d.Set("kms_key_id", containerRecipe.KmsKeyId) d.Set("name", containerRecipe.Name) d.Set("parent_image", containerRecipe.ParentImage) + + tags := KeyValueTags(containerRecipe.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return fmt.Errorf("error setting tags_all: %w", err) + } + d.Set("target_repository", []interface{}{flattenTargetContainerRepository(containerRecipe.TargetRepository)}) d.Set("version", containerRecipe.Version) d.Set("working_directory", containerRecipe.WorkingDirectory) @@ -354,6 +390,20 @@ func resourceContainerRecipeRead(d *schema.ResourceData, meta interface{}) error return nil } +func resourceContainerRecipeUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).ImageBuilderConn + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Id(), o, n); err != nil { + return fmt.Errorf("error updating tags for Image Builder Container Recipe (%s): %w", d.Id(), err) + } + } + + return resourceContainerRecipeRead(d, meta) +} + func resourceContainerRecipeDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).ImageBuilderConn diff --git a/internal/service/imagebuilder/container_recipe_test.go b/internal/service/imagebuilder/container_recipe_test.go index 1a4e6789c4fd..d284af07ca5e 100644 --- a/internal/service/imagebuilder/container_recipe_test.go +++ b/internal/service/imagebuilder/container_recipe_test.go @@ -37,12 +37,11 @@ func TestAccImageBuilderContainerRecipe_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "container_type", "DOCKER"), resource.TestCheckResourceAttr(resourceName, "description", ""), resource.TestCheckResourceAttrSet(resourceName, "dockerfile_template_data"), - // template uri - // resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), - // resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.block_device_mapping.#", "0"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "0"), resource.TestCheckResourceAttr(resourceName, "kms_key_id", ""), resource.TestCheckResourceAttr(resourceName, "name", rName), acctest.CheckResourceAttrRegionalARNAccountID(resourceName, "parent_image", "imagebuilder", "aws", "image/amazon-linux-x86-2/x.x.x"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "target_repository.#", "1"), resource.TestCheckResourceAttrPair(resourceName, "target_repository.0.repository_name", "aws_ecr_repository.test", "name"), resource.TestCheckResourceAttr(resourceName, "target_repository.0.service", "ECR"), @@ -537,6 +536,50 @@ func TestAccImageBuilderContainerRecipe_kmsKeyID(t *testing.T) { }) } +func TestAccImageBuilderContainerRecipe_tags(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_imagebuilder_container_recipe.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, imagebuilder.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckContainerRecipeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerRecipeTags1Config(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContainerRecipeTags2Config(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccContainerRecipeTags1Config(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerRecipeExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + func TestAccImageBuilderContainerRecipe_workingDirectory(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_imagebuilder_container_recipe.test" @@ -1281,6 +1324,73 @@ EOF `, rName)) } +func testAccContainerRecipeTags1Config(rName string, tagKey1 string, tagValue1 string) string { + return acctest.ConfigCompose( + testAccContainerRecipeBaseConfig(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_container_recipe" "test" { + name = %[1]q + + container_type = "DOCKER" + parent_image = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:image/amazon-linux-x86-2/x.x.x" + version = "1.0.0" + + component { + component_arn = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:component/update-linux/x.x.x" + } + + dockerfile_template_data = < Date: Sun, 6 Feb 2022 18:06:59 +0100 Subject: [PATCH 06/14] Add force_destroy to s3_bucket resource --- internal/service/imagebuilder/container_recipe_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/service/imagebuilder/container_recipe_test.go b/internal/service/imagebuilder/container_recipe_test.go index d284af07ca5e..cba6010da0ac 100644 --- a/internal/service/imagebuilder/container_recipe_test.go +++ b/internal/service/imagebuilder/container_recipe_test.go @@ -830,7 +830,8 @@ func testAccContainerDockerfileTemplateURIConfig(rName string) string { testAccContainerRecipeBaseConfig(rName), fmt.Sprintf(` resource "aws_s3_bucket" "test" { - bucket = %[1]q + bucket = %[1]q + force_destroy = true } resource "aws_s3_object" "test" { From 31edf63623d0e21a9a662dafaa09939ab2c9ab69 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 6 Feb 2022 18:52:56 +0100 Subject: [PATCH 07/14] Update import --- internal/service/imagebuilder/container_recipe.go | 2 +- internal/service/imagebuilder/container_recipe_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/imagebuilder/container_recipe.go b/internal/service/imagebuilder/container_recipe.go index 57694149c722..a74cd030eb4a 100644 --- a/internal/service/imagebuilder/container_recipe.go +++ b/internal/service/imagebuilder/container_recipe.go @@ -7,7 +7,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/imagebuilder" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" diff --git a/internal/service/imagebuilder/container_recipe_test.go b/internal/service/imagebuilder/container_recipe_test.go index cba6010da0ac..581429635a9a 100644 --- a/internal/service/imagebuilder/container_recipe_test.go +++ b/internal/service/imagebuilder/container_recipe_test.go @@ -7,7 +7,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/imagebuilder" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" From c158de0ff9123b5658bbc6353bdfe1d6d0734aaf Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 6 Feb 2022 19:44:00 +0100 Subject: [PATCH 08/14] Add docs --- ...magebuilder_container_recipe.html.markdown | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 website/docs/r/imagebuilder_container_recipe.html.markdown diff --git a/website/docs/r/imagebuilder_container_recipe.html.markdown b/website/docs/r/imagebuilder_container_recipe.html.markdown new file mode 100644 index 000000000000..29297f30d487 --- /dev/null +++ b/website/docs/r/imagebuilder_container_recipe.html.markdown @@ -0,0 +1,134 @@ +--- +subcategory: "Image Builder" +layout: "aws" +page_title: "AWS: aws_imagebuilder_container_recipe" +description: |- + Manage an Image Builder Container Recipe +--- + +# Resource: aws_imagebuilder_container_recipe + +Manages an Image Builder Container Recipe. + +## Example Usage + +```terraform +resource "aws_imagebuilder_container_recipe" "example" { + name = "example" + version = "1.0.0" + + container_type = "DOCKER" + parent_image = "arn:aws:imagebuilder:eu-central-1:aws:image/amazon-linux-x86-latest/x.x.x" + + target_repository { + repository_name = aws_ecr_repository.example.name + service = "ECR" + } + + component { + component_arn = aws_imagebuilder_component.example.arn + + parameter { + name = "Parameter1" + value = "Value1" + } + + parameter { + name = "Parameter2" + value = "Value2" + } + } + + dockerfile_template_data = < Date: Sun, 6 Feb 2022 19:44:42 +0100 Subject: [PATCH 09/14] Add changelog --- .changelog/22965.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/22965.txt diff --git a/.changelog/22965.txt b/.changelog/22965.txt new file mode 100644 index 000000000000..aa9d3e049a14 --- /dev/null +++ b/.changelog/22965.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_imagebuilder_container_recipe +``` From b777beff245d69795df5c8aa27cb081f3841197e Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 6 Feb 2022 19:49:50 +0100 Subject: [PATCH 10/14] Remove redundant blank line --- website/docs/r/imagebuilder_container_recipe.html.markdown | 1 - 1 file changed, 1 deletion(-) diff --git a/website/docs/r/imagebuilder_container_recipe.html.markdown b/website/docs/r/imagebuilder_container_recipe.html.markdown index 29297f30d487..d15e50d8100b 100644 --- a/website/docs/r/imagebuilder_container_recipe.html.markdown +++ b/website/docs/r/imagebuilder_container_recipe.html.markdown @@ -94,7 +94,6 @@ The following arguments are required: The following arguments are optional: * `block_device_mapping` - (Optional) Configuration block(s) with block device mappings for the the image recipe. Detailed below. - * `image` - (Optional) The AMI ID to use as the base image for a container build and test instance. If not specified, Image Builder will use the appropriate ECS-optimized AMI as a base image. ### block_device_mapping From f0ab94040fe2b7a7b72ee9c6f790ecb41f5e8cde Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 6 Feb 2022 20:06:13 +0100 Subject: [PATCH 11/14] Add sweeper --- internal/service/imagebuilder/sweep.go | 59 ++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/internal/service/imagebuilder/sweep.go b/internal/service/imagebuilder/sweep.go index 0d82524346b8..5b7c0da1ed5f 100644 --- a/internal/service/imagebuilder/sweep.go +++ b/internal/service/imagebuilder/sweep.go @@ -36,6 +36,11 @@ func init() { F: sweepImageRecipes, }) + resource.AddTestSweepers("aws_imagebuilder_container_recipe", &resource.Sweeper{ + Name: "aws_imagebuilder_container_recipe", + F: sweepContainerRecipes, + }) + resource.AddTestSweepers("aws_imagebuilder_image", &resource.Sweeper{ Name: "aws_imagebuilder_image", F: sweepImages, @@ -285,6 +290,60 @@ func sweepImageRecipes(region string) error { return sweeperErrs.ErrorOrNil() } +func sweepContainerRecipes(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.(*conns.AWSClient).ImageBuilderConn + + var sweeperErrs *multierror.Error + + input := &imagebuilder.ListContainerRecipesInput{ + Owner: aws.String(imagebuilder.OwnershipSelf), + } + + err = conn.ListContainerRecipesPages(input, func(page *imagebuilder.ListContainerRecipesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, containerRecipeSummary := range page.ContainerRecipeSummaryList { + if containerRecipeSummary == nil { + continue + } + + arn := aws.StringValue(containerRecipeSummary.Arn) + + r := ResourceContainerRecipe() + d := r.Data(nil) + d.SetId(arn) + + err := r.Delete(d, client) + + if err != nil { + sweeperErr := fmt.Errorf("error deleting Image Builder Container Recipe (%s): %w", arn, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping Image Builder Container Recipe sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Image Builder Container Recipes: %w", err)) + } + + return sweeperErrs.ErrorOrNil() +} + func sweepImages(region string) error { client, err := sweep.SharedRegionalSweepClient(region) From bb5e7387797e4bb80234f8da8e6b93b138ea6ef7 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 6 Feb 2022 20:06:20 +0100 Subject: [PATCH 12/14] Fix docs --- website/docs/r/imagebuilder_container_recipe.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/imagebuilder_container_recipe.html.markdown b/website/docs/r/imagebuilder_container_recipe.html.markdown index d15e50d8100b..667d96c34349 100644 --- a/website/docs/r/imagebuilder_container_recipe.html.markdown +++ b/website/docs/r/imagebuilder_container_recipe.html.markdown @@ -129,5 +129,5 @@ In addition to all arguments above, the following attributes are exported: `aws_imagebuilder_container_recipe` resources can be imported by using the Amazon Resource Name (ARN), e.g., ``` -$ terraform import aws_imagebuilder_image_recipe.example arn:aws:imagebuilder:us-east-1:123456789012:container-recipe/example/1.0.0 +$ terraform import aws_imagebuilder_container_recipe.example arn:aws:imagebuilder:us-east-1:123456789012:container-recipe/example/1.0.0 ``` From e288aa971a0add9560fb7cee0d27e8c41b64a590 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Mon, 7 Feb 2022 23:19:07 +0100 Subject: [PATCH 13/14] Add missing computed attributes --- .../service/imagebuilder/container_recipe.go | 20 +++++++++++++++++++ .../imagebuilder/container_recipe_test.go | 4 ++++ ...magebuilder_container_recipe.html.markdown | 8 ++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/internal/service/imagebuilder/container_recipe.go b/internal/service/imagebuilder/container_recipe.go index a74cd030eb4a..147be6e9a94f 100644 --- a/internal/service/imagebuilder/container_recipe.go +++ b/internal/service/imagebuilder/container_recipe.go @@ -70,6 +70,10 @@ func ResourceContainerRecipe() *schema.Resource { ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"DOCKER"}, false), }, + "date_created": { + Type: schema.TypeString, + Computed: true, + }, "description": { Type: schema.TypeString, Optional: true, @@ -91,6 +95,10 @@ func ResourceContainerRecipe() *schema.Resource { ExactlyOneOf: []string{"dockerfile_template_data", "dockerfile_template_uri"}, ValidateFunc: validation.StringMatch(regexp.MustCompile(`^s3://`), "must begin with s3://"), }, + "encrypted": { + Type: schema.TypeBool, + Computed: true, + }, "instance_configuration": { Type: schema.TypeList, Optional: true, @@ -210,12 +218,20 @@ func ResourceContainerRecipe() *schema.Resource { ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 128), }, + "owner": { + Type: schema.TypeString, + Computed: true, + }, "parent_image": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 1024), }, + "platform": { + Type: schema.TypeString, + Computed: true, + }, "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), "target_repository": { @@ -360,8 +376,10 @@ func resourceContainerRecipeRead(d *schema.ResourceData, meta interface{}) error d.Set("arn", containerRecipe.Arn) d.Set("component", flattenComponentConfigurations(containerRecipe.Components)) d.Set("container_type", containerRecipe.ContainerType) + d.Set("date_created", containerRecipe.DateCreated) d.Set("description", containerRecipe.Description) d.Set("dockerfile_template_data", containerRecipe.DockerfileTemplateData) + d.Set("encrypted", containerRecipe.Encrypted) if containerRecipe.InstanceConfiguration != nil { d.Set("instance_configuration", []interface{}{flattenInstanceConfiguration(containerRecipe.InstanceConfiguration)}) @@ -371,7 +389,9 @@ func resourceContainerRecipeRead(d *schema.ResourceData, meta interface{}) error d.Set("kms_key_id", containerRecipe.KmsKeyId) d.Set("name", containerRecipe.Name) + d.Set("owner", containerRecipe.Owner) d.Set("parent_image", containerRecipe.ParentImage) + d.Set("platform", containerRecipe.Platform) tags := KeyValueTags(containerRecipe.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) diff --git a/internal/service/imagebuilder/container_recipe_test.go b/internal/service/imagebuilder/container_recipe_test.go index 581429635a9a..61422b41df98 100644 --- a/internal/service/imagebuilder/container_recipe_test.go +++ b/internal/service/imagebuilder/container_recipe_test.go @@ -36,11 +36,15 @@ func TestAccImageBuilderContainerRecipe_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "component.0.parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "container_type", "DOCKER"), resource.TestCheckResourceAttr(resourceName, "description", ""), + acctest.CheckResourceAttrRFC3339(resourceName, "date_created"), resource.TestCheckResourceAttrSet(resourceName, "dockerfile_template_data"), + resource.TestCheckResourceAttr(resourceName, "encrypted", "true"), resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "0"), resource.TestCheckResourceAttr(resourceName, "kms_key_id", ""), resource.TestCheckResourceAttr(resourceName, "name", rName), + acctest.CheckResourceAttrAccountID(resourceName, "owner"), acctest.CheckResourceAttrRegionalARNAccountID(resourceName, "parent_image", "imagebuilder", "aws", "image/amazon-linux-x86-2/x.x.x"), + resource.TestCheckResourceAttr(resourceName, "platform", imagebuilder.PlatformLinux), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "target_repository.#", "1"), resource.TestCheckResourceAttrPair(resourceName, "target_repository.0.repository_name", "aws_ecr_repository.test", "name"), diff --git a/website/docs/r/imagebuilder_container_recipe.html.markdown b/website/docs/r/imagebuilder_container_recipe.html.markdown index 667d96c34349..391bebdded69 100644 --- a/website/docs/r/imagebuilder_container_recipe.html.markdown +++ b/website/docs/r/imagebuilder_container_recipe.html.markdown @@ -93,7 +93,7 @@ The following arguments are required: The following arguments are optional: -* `block_device_mapping` - (Optional) Configuration block(s) with block device mappings for the the image recipe. Detailed below. +* `block_device_mapping` - (Optional) Configuration block(s) with block device mappings for the the container recipe. Detailed below. * `image` - (Optional) The AMI ID to use as the base image for a container build and test instance. If not specified, Image Builder will use the appropriate ECS-optimized AMI as a base image. ### block_device_mapping @@ -121,7 +121,11 @@ The following arguments are optional: In addition to all arguments above, the following attributes are exported: -* `arn` - (Required) Amazon Resource Name (ARN) of the image recipe. +* `arn` - (Required) Amazon Resource Name (ARN) of the container recipe. +* `date_created` - Date the container recipe was created. +* `encrypted` - A flag that indicates if the target container is encrypted. +* `owner` - Owner of the container recipe. +* `platform` - Platform of the container recipe. * `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). ## Import From 5d977d6cf59f0d47da6aa28b30c0f6bf849e484e Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Tue, 8 Feb 2022 22:15:48 +0100 Subject: [PATCH 14/14] Improve error checks --- internal/service/imagebuilder/container_recipe.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/service/imagebuilder/container_recipe.go b/internal/service/imagebuilder/container_recipe.go index 147be6e9a94f..22fba551488f 100644 --- a/internal/service/imagebuilder/container_recipe.go +++ b/internal/service/imagebuilder/container_recipe.go @@ -364,11 +364,11 @@ func resourceContainerRecipeRead(d *schema.ResourceData, meta interface{}) error } if err != nil { - return fmt.Errorf("error creating Image Builder Container Recipe: %w", err) + return fmt.Errorf("error getting Image Builder Container Recipe (%s): %w", d.Id(), err) } - if output == nil { - return fmt.Errorf("error creating Image Builder Container Recipe: empty response") + if output == nil || output.ContainerRecipe == nil { + return fmt.Errorf("error getting Image Builder Container Recipe (%s): empty response", d.Id()) } containerRecipe := output.ContainerRecipe