From d71a43aecfddf4e2bd70143a9d21018e057eece2 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Wed, 25 Nov 2020 10:21:06 -0500 Subject: [PATCH] New Resource: aws_imagebuilder_image_pipeline (#16299) Reference: https://github.com/hashicorp/terraform-provider-aws/issues/11084 Reference: https://github.com/hashicorp/terraform-provider-aws/pull/13485 Reference: https://github.com/hashicorp/terraform-provider-aws/pull/16218 (required for `aws_imagebuilder_image_recipe` resource implementation) Changes: ``` * **New Data Source:** `aws_imagebuilder_image_pipeline` * **New Resource:** `aws_imagebuilder_image_pipeline` ``` Output from acceptance testing in AWS Commercial: ``` --- PASS: TestAccAwsImageBuilderImagePipeline_basic (36.73s) --- PASS: TestAccAwsImageBuilderImagePipeline_Description (59.17s) --- PASS: TestAccAwsImageBuilderImagePipeline_disappears (28.69s) --- PASS: TestAccAwsImageBuilderImagePipeline_DistributionConfigurationArn (62.02s) --- PASS: TestAccAwsImageBuilderImagePipeline_EnhancedImageMetadataEnabled (61.63s) --- PASS: TestAccAwsImageBuilderImagePipeline_ImageRecipeArn (57.56s) --- PASS: TestAccAwsImageBuilderImagePipeline_ImageTestsConfiguration_ImageTestsEnabled (61.52s) --- PASS: TestAccAwsImageBuilderImagePipeline_ImageTestsConfiguration_TimeoutMinutes (61.49s) --- PASS: TestAccAwsImageBuilderImagePipeline_InfrastructureConfigurationArn (57.93s) --- PASS: TestAccAwsImageBuilderImagePipeline_Schedule_PipelineExecutionStartCondition (61.29s) --- PASS: TestAccAwsImageBuilderImagePipeline_Schedule_ScheduleExpression (62.21s) --- PASS: TestAccAwsImageBuilderImagePipeline_Status (57.90s) --- PASS: TestAccAwsImageBuilderImagePipeline_Tags (73.01s) --- PASS: TestAccAwsImageBuilderImagePipelineDataSource_Arn (33.95s) ``` Output from acceptance testing in AWS GovCloud (US): ``` --- PASS: TestAccAwsImageBuilderImagePipeline_basic (45.88s) --- PASS: TestAccAwsImageBuilderImagePipeline_Description (65.03s) --- PASS: TestAccAwsImageBuilderImagePipeline_disappears (39.74s) --- PASS: TestAccAwsImageBuilderImagePipeline_DistributionConfigurationArn (66.42s) --- PASS: TestAccAwsImageBuilderImagePipeline_EnhancedImageMetadataEnabled (63.39s) --- PASS: TestAccAwsImageBuilderImagePipeline_ImageRecipeArn (66.54s) --- PASS: TestAccAwsImageBuilderImagePipeline_ImageTestsConfiguration_ImageTestsEnabled (65.08s) --- PASS: TestAccAwsImageBuilderImagePipeline_ImageTestsConfiguration_TimeoutMinutes (61.28s) --- PASS: TestAccAwsImageBuilderImagePipeline_InfrastructureConfigurationArn (66.96s) --- PASS: TestAccAwsImageBuilderImagePipeline_Schedule_PipelineExecutionStartCondition (61.44s) --- PASS: TestAccAwsImageBuilderImagePipeline_Schedule_ScheduleExpression (60.45s) --- PASS: TestAccAwsImageBuilderImagePipeline_Status (65.08s) --- PASS: TestAccAwsImageBuilderImagePipeline_Tags (81.64s) --- PASS: TestAccAwsImageBuilderImagePipelineDataSource_Arn (36.13s) ``` Output from sweeper in AWS Commercial: ``` 2020/11/18 20:23:09 [DEBUG] Running Sweepers for region (us-west-2): 2020/11/18 20:23:09 [DEBUG] Running Sweeper (aws_imagebuilder_image_pipeline) in region (us-west-2) 2020/11/18 20:23:11 Sweeper Tests ran successfully: - aws_imagebuilder_image_pipeline 2020/11/18 20:23:11 [DEBUG] Running Sweepers for region (us-east-1): 2020/11/18 20:23:11 [DEBUG] Running Sweeper (aws_imagebuilder_image_pipeline) in region (us-east-1) 2020/11/18 20:23:11 Sweeper Tests ran successfully: - aws_imagebuilder_image_pipeline ok github.com/terraform-providers/terraform-provider-aws/aws 5.138s ``` Output from sweeper in AWS GovCloud (US): ``` 2020/11/18 20:23:10 [DEBUG] Running Sweepers for region (us-gov-west-1): 2020/11/18 20:23:10 [DEBUG] Running Sweeper (aws_imagebuilder_image_pipeline) in region (us-gov-west-1) 2020/11/18 20:23:13 Sweeper Tests ran successfully: - aws_imagebuilder_image_pipeline ok github.com/terraform-providers/terraform-provider-aws/aws 4.805s ``` Co-authored-by: Dogers Co-authored-by: Jakub Kania Co-authored-by: Bill Schneider --- ..._source_aws_imagebuilder_image_pipeline.go | 159 ++++ ...ce_aws_imagebuilder_image_pipeline_test.go | 117 +++ aws/provider.go | 2 + ...esource_aws_imagebuilder_image_pipeline.go | 406 ++++++++ ...ce_aws_imagebuilder_image_pipeline_test.go | 869 ++++++++++++++++++ .../imagebuilder_image_pipeline.html.markdown | 47 + .../imagebuilder_image_pipeline.html.markdown | 79 ++ 7 files changed, 1679 insertions(+) create mode 100644 aws/data_source_aws_imagebuilder_image_pipeline.go create mode 100644 aws/data_source_aws_imagebuilder_image_pipeline_test.go create mode 100644 aws/resource_aws_imagebuilder_image_pipeline.go create mode 100644 aws/resource_aws_imagebuilder_image_pipeline_test.go create mode 100644 website/docs/d/imagebuilder_image_pipeline.html.markdown create mode 100644 website/docs/r/imagebuilder_image_pipeline.html.markdown diff --git a/aws/data_source_aws_imagebuilder_image_pipeline.go b/aws/data_source_aws_imagebuilder_image_pipeline.go new file mode 100644 index 00000000000..8c35a726395 --- /dev/null +++ b/aws/data_source_aws_imagebuilder_image_pipeline.go @@ -0,0 +1,159 @@ +package aws + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/imagebuilder" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" +) + +func dataSourceAwsImageBuilderImagePipeline() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsImageBuilderImagePipelineRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArn, + }, + "date_created": { + Type: schema.TypeString, + Computed: true, + }, + "date_last_run": { + Type: schema.TypeString, + Computed: true, + }, + "date_next_run": { + Type: schema.TypeString, + Computed: true, + }, + "date_updated": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "distribution_configuration_arn": { + Type: schema.TypeString, + Computed: true, + }, + "enhanced_image_metadata_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + "image_recipe_arn": { + Type: schema.TypeString, + Computed: true, + }, + "image_tests_configuration": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "image_tests_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + "timeout_minutes": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "infrastructure_configuration_arn": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "platform": { + Type: schema.TypeString, + Computed: true, + }, + "schedule": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "pipeline_execution_start_condition": { + Type: schema.TypeString, + Computed: true, + }, + "schedule_expression": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchemaComputed(), + }, + } +} + +func dataSourceAwsImageBuilderImagePipelineRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).imagebuilderconn + + input := &imagebuilder.GetImagePipelineInput{} + + if v, ok := d.GetOk("arn"); ok { + input.ImagePipelineArn = aws.String(v.(string)) + } + + output, err := conn.GetImagePipeline(input) + + if err != nil { + return fmt.Errorf("error getting Image Builder Image Pipeline: %w", err) + } + + if output == nil || output.ImagePipeline == nil { + return fmt.Errorf("error getting Image Builder Image Pipeline: empty response") + } + + imagePipeline := output.ImagePipeline + + d.SetId(aws.StringValue(imagePipeline.Arn)) + d.Set("arn", imagePipeline.Arn) + d.Set("date_created", imagePipeline.DateCreated) + d.Set("date_last_run", imagePipeline.DateLastRun) + d.Set("date_next_run", imagePipeline.DateNextRun) + d.Set("date_updated", imagePipeline.DateUpdated) + d.Set("description", imagePipeline.Description) + d.Set("distribution_configuration_arn", imagePipeline.DistributionConfigurationArn) + d.Set("enhanced_image_metadata_enabled", imagePipeline.EnhancedImageMetadataEnabled) + d.Set("image_recipe_arn", imagePipeline.ImageRecipeArn) + + if imagePipeline.ImageTestsConfiguration != nil { + d.Set("image_tests_configuration", []interface{}{flattenImageBuilderImageTestsConfiguration(imagePipeline.ImageTestsConfiguration)}) + } else { + d.Set("image_tests_configuration", nil) + } + + d.Set("infrastructure_configuration_arn", imagePipeline.InfrastructureConfigurationArn) + d.Set("name", imagePipeline.Name) + d.Set("platform", imagePipeline.Platform) + + if imagePipeline.Schedule != nil { + d.Set("schedule", []interface{}{flattenImageBuilderSchedule(imagePipeline.Schedule)}) + } else { + d.Set("schedule", nil) + } + + d.Set("status", imagePipeline.Status) + d.Set("tags", keyvaluetags.ImagebuilderKeyValueTags(imagePipeline.Tags).IgnoreAws().IgnoreConfig(meta.(*AWSClient).IgnoreTagsConfig).Map()) + + return nil +} diff --git a/aws/data_source_aws_imagebuilder_image_pipeline_test.go b/aws/data_source_aws_imagebuilder_image_pipeline_test.go new file mode 100644 index 00000000000..4f5b4d6cdcb --- /dev/null +++ b/aws/data_source_aws_imagebuilder_image_pipeline_test.go @@ -0,0 +1,117 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccAwsImageBuilderImagePipelineDataSource_Arn(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + dataSourceName := "data.aws_imagebuilder_image_pipeline.test" + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineDataSourceConfigArn(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "date_created", resourceName, "date_created"), + resource.TestCheckResourceAttrPair(dataSourceName, "date_last_run", resourceName, "date_last_run"), + resource.TestCheckResourceAttrPair(dataSourceName, "date_next_run", resourceName, "date_next_run"), + resource.TestCheckResourceAttrPair(dataSourceName, "date_updated", resourceName, "date_updated"), + resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"), + resource.TestCheckResourceAttrPair(dataSourceName, "distribution_configuration_arn", resourceName, "distribution_configuration_arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "enhanced_image_metadata_enabled", resourceName, "enhanced_image_metadata_enabled"), + resource.TestCheckResourceAttrPair(dataSourceName, "image_recipe_arn", resourceName, "image_recipe_arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "image_tests_configuration.#", resourceName, "image_tests_configuration.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "infrastructure_configuration_arn", resourceName, "infrastructure_configuration_arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "name", resourceName, "name"), + resource.TestCheckResourceAttrPair(dataSourceName, "platform", resourceName, "platform"), + resource.TestCheckResourceAttrPair(dataSourceName, "schedule.#", resourceName, "schedule.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "status", resourceName, "status"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), + ), + }, + }, + }) +} + +func testAccAwsImageBuilderImagePipelineDataSourceConfigArn(rName string) string { + return fmt.Sprintf(` +data "aws_region" "current" {} + +data "aws_partition" "current" {} + +resource "aws_iam_instance_profile" "test" { + name = aws_iam_role.role.name + role = aws_iam_role.role.name +} + +resource "aws_iam_role" "role" { + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.${data.aws_partition.current.dns_suffix}" + } + Sid = "" + }] + }) + name = %[1]q +} + +resource "aws_imagebuilder_component" "test" { + data = yamlencode({ + phases = [{ + name = "build" + steps = [{ + action = "ExecuteBash" + inputs = { + commands = ["echo 'hello world'"] + } + name = "example" + onFailure = "Continue" + }] + }] + schemaVersion = 1.0 + }) + name = %[1]q + platform = "Linux" + version = "1.0.0" +} + +resource "aws_imagebuilder_image_recipe" "test" { + component { + component_arn = aws_imagebuilder_component.test.arn + } + + name = %[1]q + parent_image = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:image/amazon-linux-2-x86/x.x.x" + version = "1.0.0" +} + +resource "aws_imagebuilder_infrastructure_configuration" "test" { + instance_profile_name = aws_iam_instance_profile.test.name + name = %[1]q +} + +resource "aws_imagebuilder_image_pipeline" "test" { + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q +} + +data "aws_imagebuilder_image_pipeline" "test" { + arn = aws_imagebuilder_image_pipeline.test.arn +} +`, rName) +} diff --git a/aws/provider.go b/aws/provider.go index bef3742577c..0bf50aa3b61 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -268,6 +268,7 @@ func Provider() *schema.Provider { "aws_iam_user": dataSourceAwsIAMUser(), "aws_imagebuilder_component": dataSourceAwsImageBuilderComponent(), "aws_imagebuilder_distribution_configuration": datasourceAwsImageBuilderDistributionConfiguration(), + "aws_imagebuilder_image_pipeline": dataSourceAwsImageBuilderImagePipeline(), "aws_imagebuilder_image_recipe": dataSourceAwsImageBuilderImageRecipe(), "aws_imagebuilder_infrastructure_configuration": datasourceAwsImageBuilderInfrastructureConfiguration(), "aws_internet_gateway": dataSourceAwsInternetGateway(), @@ -711,6 +712,7 @@ func Provider() *schema.Provider { "aws_iam_user_login_profile": resourceAwsIamUserLoginProfile(), "aws_imagebuilder_component": resourceAwsImageBuilderComponent(), "aws_imagebuilder_distribution_configuration": resourceAwsImageBuilderDistributionConfiguration(), + "aws_imagebuilder_image_pipeline": resourceAwsImageBuilderImagePipeline(), "aws_imagebuilder_image_recipe": resourceAwsImageBuilderImageRecipe(), "aws_imagebuilder_infrastructure_configuration": resourceAwsImageBuilderInfrastructureConfiguration(), "aws_inspector_assessment_target": resourceAWSInspectorAssessmentTarget(), diff --git a/aws/resource_aws_imagebuilder_image_pipeline.go b/aws/resource_aws_imagebuilder_image_pipeline.go new file mode 100644 index 00000000000..d0e342e6a8f --- /dev/null +++ b/aws/resource_aws_imagebuilder_image_pipeline.go @@ -0,0 +1,406 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + + "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/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" +) + +func resourceAwsImageBuilderImagePipeline() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsImageBuilderImagePipelineCreate, + Read: resourceAwsImageBuilderImagePipelineRead, + Update: resourceAwsImageBuilderImagePipelineUpdate, + Delete: resourceAwsImageBuilderImagePipelineDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "date_created": { + Type: schema.TypeString, + Computed: true, + }, + "date_last_run": { + Type: schema.TypeString, + Computed: true, + }, + "date_next_run": { + Type: schema.TypeString, + Computed: true, + }, + "date_updated": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + "distribution_configuration_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^arn:aws[^:]*:imagebuilder:[^:]+:(?:\d{12}|aws):distribution-configuration/[a-z0-9-_]+$`), "valid distribution configuration ARN must be provided"), + }, + "enhanced_image_metadata_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "image_recipe_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^arn:aws[^:]*:imagebuilder:[^:]+:(?:\d{12}|aws):image-recipe/[a-z0-9-_]+/\d+\.\d+\.\d+$`), "valid image recipe ARN must be provided"), + }, + "image_tests_configuration": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "image_tests_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "timeout_minutes": { + Type: schema.TypeInt, + Optional: true, + Default: 720, + ValidateFunc: validation.IntBetween(60, 1440), + }, + }, + }, + }, + "infrastructure_configuration_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^arn:aws[^:]*:imagebuilder:[^:]+:(?:\d{12}|aws):infrastructure-configuration/[a-z0-9-_]+$`), "valid infrastructure configuration ARN must be provided"), + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile("^[-_A-Za-z-0-9][-_A-Za-z0-9 ]{1,126}[-_A-Za-z-0-9]$"), "valid name must be provided"), + }, + "platform": { + Type: schema.TypeString, + Computed: true, + }, + "schedule": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "pipeline_execution_start_condition": { + Type: schema.TypeString, + Optional: true, + Default: imagebuilder.PipelineExecutionStartConditionExpressionMatchAndDependencyUpdatesAvailable, + ValidateFunc: validation.StringInSlice(imagebuilder.PipelineExecutionStartCondition_Values(), false), + }, + "schedule_expression": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + }, + }, + }, + "status": { + Type: schema.TypeString, + Optional: true, + Default: imagebuilder.PipelineStatusEnabled, + ValidateFunc: validation.StringInSlice(imagebuilder.PipelineStatus_Values(), false), + }, + "tags": tagsSchema(), + }, + } +} + +func resourceAwsImageBuilderImagePipelineCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).imagebuilderconn + + input := &imagebuilder.CreateImagePipelineInput{ + ClientToken: aws.String(resource.UniqueId()), + EnhancedImageMetadataEnabled: aws.Bool(d.Get("enhanced_image_metadata_enabled").(bool)), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("distribution_configuration_arn"); ok { + input.DistributionConfigurationArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("image_recipe_arn"); ok { + input.ImageRecipeArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("image_tests_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.ImageTestsConfiguration = expandImageBuilderImageTestConfiguration(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("infrastructure_configuration_arn"); ok { + input.InfrastructureConfigurationArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("name"); ok { + input.Name = aws.String(v.(string)) + } + + if v, ok := d.GetOk("schedule"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.Schedule = expandImageBuilderPipelineSchedule(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("status"); ok { + input.Status = aws.String(v.(string)) + } + + if v, ok := d.GetOk("tags"); ok && len(v.(map[string]interface{})) > 0 { + input.Tags = keyvaluetags.New(v.(map[string]interface{})).IgnoreAws().ImagebuilderTags() + } + + output, err := conn.CreateImagePipeline(input) + + if err != nil { + return fmt.Errorf("error creating Image Builder Image Pipeline: %w", err) + } + + if output == nil { + return fmt.Errorf("error creating Image Builder Image Pipeline: empty response") + } + + d.SetId(aws.StringValue(output.ImagePipelineArn)) + + return resourceAwsImageBuilderImagePipelineRead(d, meta) +} + +func resourceAwsImageBuilderImagePipelineRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).imagebuilderconn + + input := &imagebuilder.GetImagePipelineInput{ + ImagePipelineArn: aws.String(d.Id()), + } + + output, err := conn.GetImagePipeline(input) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, imagebuilder.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] Image Builder Image Pipeline (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error getting Image Builder Image Pipeline (%s): %w", d.Id(), err) + } + + if output == nil || output.ImagePipeline == nil { + return fmt.Errorf("error getting Image Builder Image Pipeline (%s): empty response", d.Id()) + } + + imagePipeline := output.ImagePipeline + + d.Set("arn", imagePipeline.Arn) + d.Set("date_created", imagePipeline.DateCreated) + d.Set("date_last_run", imagePipeline.DateLastRun) + d.Set("date_next_run", imagePipeline.DateNextRun) + d.Set("date_updated", imagePipeline.DateUpdated) + d.Set("description", imagePipeline.Description) + d.Set("distribution_configuration_arn", imagePipeline.DistributionConfigurationArn) + d.Set("enhanced_image_metadata_enabled", imagePipeline.EnhancedImageMetadataEnabled) + d.Set("image_recipe_arn", imagePipeline.ImageRecipeArn) + + if imagePipeline.ImageTestsConfiguration != nil { + d.Set("image_tests_configuration", []interface{}{flattenImageBuilderImageTestsConfiguration(imagePipeline.ImageTestsConfiguration)}) + } else { + d.Set("image_tests_configuration", nil) + } + + d.Set("infrastructure_configuration_arn", imagePipeline.InfrastructureConfigurationArn) + d.Set("name", imagePipeline.Name) + d.Set("platform", imagePipeline.Platform) + + if imagePipeline.Schedule != nil { + d.Set("schedule", []interface{}{flattenImageBuilderSchedule(imagePipeline.Schedule)}) + } else { + d.Set("schedule", nil) + } + + d.Set("status", imagePipeline.Status) + d.Set("tags", keyvaluetags.ImagebuilderKeyValueTags(imagePipeline.Tags).IgnoreAws().IgnoreConfig(meta.(*AWSClient).IgnoreTagsConfig).Map()) + + return nil +} + +func resourceAwsImageBuilderImagePipelineUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).imagebuilderconn + + if d.HasChanges( + "description", + "distribution_configuration_arn", + "enhanced_image_metadata_enabled", + "image_recipe_arn", + "image_tests_configuration", + "infrastructure_configuration_arn", + "schedule", + "status", + ) { + input := &imagebuilder.UpdateImagePipelineInput{ + ClientToken: aws.String(resource.UniqueId()), + EnhancedImageMetadataEnabled: aws.Bool(d.Get("enhanced_image_metadata_enabled").(bool)), + ImagePipelineArn: aws.String(d.Id()), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("distribution_configuration_arn"); ok { + input.DistributionConfigurationArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("image_recipe_arn"); ok { + input.ImageRecipeArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("image_tests_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.ImageTestsConfiguration = expandImageBuilderImageTestConfiguration(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("infrastructure_configuration_arn"); ok { + input.InfrastructureConfigurationArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("schedule"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.Schedule = expandImageBuilderPipelineSchedule(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("status"); ok { + input.Status = aws.String(v.(string)) + } + + _, err := conn.UpdateImagePipeline(input) + + if err != nil { + return fmt.Errorf("error updating Image Builder Image Pipeline (%s): %w", d.Id(), err) + } + } + + if d.HasChange("tags") { + o, n := d.GetChange("tags") + + if err := keyvaluetags.ImagebuilderUpdateTags(conn, d.Id(), o, n); err != nil { + return fmt.Errorf("error updating tags for Image Builder Image Pipeline (%s): %w", d.Id(), err) + } + } + + return resourceAwsImageBuilderImagePipelineRead(d, meta) +} + +func resourceAwsImageBuilderImagePipelineDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).imagebuilderconn + + input := &imagebuilder.DeleteImagePipelineInput{ + ImagePipelineArn: aws.String(d.Id()), + } + + _, err := conn.DeleteImagePipeline(input) + + if tfawserr.ErrCodeEquals(err, imagebuilder.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Image Builder Image Pipeline (%s): %w", d.Id(), err) + } + + return nil +} + +func expandImageBuilderImageTestConfiguration(tfMap map[string]interface{}) *imagebuilder.ImageTestsConfiguration { + if tfMap == nil { + return nil + } + + apiObject := &imagebuilder.ImageTestsConfiguration{} + + if v, ok := tfMap["image_tests_enabled"].(bool); ok { + apiObject.ImageTestsEnabled = aws.Bool(v) + } + + if v, ok := tfMap["timeout_minutes"].(int); ok && v != 0 { + apiObject.TimeoutMinutes = aws.Int64(int64(v)) + } + + return apiObject +} + +func expandImageBuilderPipelineSchedule(tfMap map[string]interface{}) *imagebuilder.Schedule { + if tfMap == nil { + return nil + } + + apiObject := &imagebuilder.Schedule{} + + if v, ok := tfMap["pipeline_execution_start_condition"].(string); ok && v != "" { + apiObject.PipelineExecutionStartCondition = aws.String(v) + } + + if v, ok := tfMap["schedule_expression"].(string); ok && v != "" { + apiObject.ScheduleExpression = aws.String(v) + } + + return apiObject +} + +func flattenImageBuilderImageTestsConfiguration(apiObject *imagebuilder.ImageTestsConfiguration) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.ImageTestsEnabled; v != nil { + tfMap["image_tests_enabled"] = aws.BoolValue(v) + } + + if v := apiObject.TimeoutMinutes; v != nil { + tfMap["timeout_minutes"] = aws.Int64Value(v) + } + + return tfMap +} + +func flattenImageBuilderSchedule(apiObject *imagebuilder.Schedule) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.PipelineExecutionStartCondition; v != nil { + tfMap["pipeline_execution_start_condition"] = aws.StringValue(v) + } + + if v := apiObject.ScheduleExpression; v != nil { + tfMap["schedule_expression"] = aws.StringValue(v) + } + + return tfMap +} diff --git a/aws/resource_aws_imagebuilder_image_pipeline_test.go b/aws/resource_aws_imagebuilder_image_pipeline_test.go new file mode 100644 index 00000000000..9935fb1f640 --- /dev/null +++ b/aws/resource_aws_imagebuilder_image_pipeline_test.go @@ -0,0 +1,869 @@ +package aws + +import ( + "fmt" + "log" + "testing" + + "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/go-multierror" + "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" +) + +func init() { + resource.AddTestSweepers("aws_imagebuilder_image_pipeline", &resource.Sweeper{ + Name: "aws_imagebuilder_image_pipeline", + F: testSweepImageBuilderImagePipelines, + }) +} + +func testSweepImageBuilderImagePipelines(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.(*AWSClient).imagebuilderconn + + var sweeperErrs *multierror.Error + + input := &imagebuilder.ListImagePipelinesInput{} + + err = conn.ListImagePipelinesPages(input, func(page *imagebuilder.ListImagePipelinesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, imagePipeline := range page.ImagePipelineList { + if imagePipeline == nil { + continue + } + + arn := aws.StringValue(imagePipeline.Arn) + + r := resourceAwsImageBuilderImagePipeline() + d := r.Data(nil) + d.SetId(arn) + + err := r.Delete(d, client) + + if err != nil { + sweeperErr := fmt.Errorf("error deleting Image Builder Image Pipeline (%s): %w", arn, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + } + + return !lastPage + }) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping Image Builder Image Pipeline 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 Image Pipelines: %w", err)) + } + + return sweeperErrs.ErrorOrNil() +} + +func TestAccAwsImageBuilderImagePipeline_basic(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + imageRecipeResourceName := "aws_imagebuilder_image_recipe.test" + infrastructureConfigurationResourceName := "aws_imagebuilder_infrastructure_configuration.test" + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "imagebuilder", fmt.Sprintf("image-pipeline/%s", rName)), + testAccCheckResourceAttrRfc3339(resourceName, "date_created"), + resource.TestCheckResourceAttr(resourceName, "date_last_run", ""), + resource.TestCheckResourceAttr(resourceName, "date_next_run", ""), + testAccCheckResourceAttrRfc3339(resourceName, "date_updated"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "distribution_configuration_arn", ""), + resource.TestCheckResourceAttr(resourceName, "enhanced_image_metadata_enabled", "true"), + resource.TestCheckResourceAttrPair(resourceName, "image_recipe_arn", imageRecipeResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "image_tests_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "image_tests_configuration.0.image_tests_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "image_tests_configuration.0.timeout_minutes", "720"), + resource.TestCheckResourceAttrPair(resourceName, "infrastructure_configuration_arn", infrastructureConfigurationResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "platform", imagebuilder.PlatformLinux), + resource.TestCheckResourceAttr(resourceName, "schedule.#", "0"), + resource.TestCheckResourceAttr(resourceName, "status", imagebuilder.PipelineStatusEnabled), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsImageBuilderImagePipeline_disappears(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsImageBuilderImagePipeline(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAwsImageBuilderImagePipeline_Description(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineConfigDescription(rName, "description1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", "description1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsImageBuilderImagePipelineConfigDescription(rName, "description2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", "description2"), + ), + }, + }, + }) +} + +func TestAccAwsImageBuilderImagePipeline_DistributionConfigurationArn(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + distributionConfigurationResourceName := "aws_imagebuilder_distribution_configuration.test" + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineConfigDistributionConfigurationArn1(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "distribution_configuration_arn", distributionConfigurationResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsImageBuilderImagePipelineConfigDistributionConfigurationArn2(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "distribution_configuration_arn", distributionConfigurationResourceName, "arn"), + ), + }, + }, + }) +} + +func TestAccAwsImageBuilderImagePipeline_EnhancedImageMetadataEnabled(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineConfigEnhancedImageMetadataEnabled(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "enhanced_image_metadata_enabled", "false"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsImageBuilderImagePipelineConfigEnhancedImageMetadataEnabled(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "enhanced_image_metadata_enabled", "true"), + ), + }, + }, + }) +} + +func TestAccAwsImageBuilderImagePipeline_ImageRecipeArn(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + imageRecipeResourceName := "aws_imagebuilder_image_recipe.test" + imageRecipeResourceName2 := "aws_imagebuilder_image_recipe.test2" + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "image_recipe_arn", imageRecipeResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsImageBuilderImagePipelineConfigImageRecipeArn2(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "image_recipe_arn", imageRecipeResourceName2, "arn"), + ), + }, + }, + }) +} + +func TestAccAwsImageBuilderImagePipeline_ImageTestsConfiguration_ImageTestsEnabled(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineConfigImageTestsConfigurationImageTestsEnabled(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "image_tests_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "image_tests_configuration.0.image_tests_enabled", "false"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsImageBuilderImagePipelineConfigImageTestsConfigurationImageTestsEnabled(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "image_tests_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "image_tests_configuration.0.image_tests_enabled", "true"), + ), + }, + }, + }) +} + +func TestAccAwsImageBuilderImagePipeline_ImageTestsConfiguration_TimeoutMinutes(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineConfigImageTestsConfigurationTimeoutMinutes(rName, 721), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "image_tests_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "image_tests_configuration.0.timeout_minutes", "721"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsImageBuilderImagePipelineConfigImageTestsConfigurationTimeoutMinutes(rName, 722), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "image_tests_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "image_tests_configuration.0.timeout_minutes", "722"), + ), + }, + }, + }) +} + +func TestAccAwsImageBuilderImagePipeline_InfrastructureConfigurationArn(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + infrastructureConfigurationResourceName := "aws_imagebuilder_infrastructure_configuration.test" + infrastructureConfigurationResourceName2 := "aws_imagebuilder_infrastructure_configuration.test2" + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "infrastructure_configuration_arn", infrastructureConfigurationResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsImageBuilderImagePipelineConfigInfrastructureConfigurationArn2(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "infrastructure_configuration_arn", infrastructureConfigurationResourceName2, "arn"), + ), + }, + }, + }) +} + +func TestAccAwsImageBuilderImagePipeline_Schedule_PipelineExecutionStartCondition(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineConfigSchedulePipelineExecutionStartCondition(rName, imagebuilder.PipelineExecutionStartConditionExpressionMatchAndDependencyUpdatesAvailable), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "schedule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "schedule.0.pipeline_execution_start_condition", imagebuilder.PipelineExecutionStartConditionExpressionMatchAndDependencyUpdatesAvailable), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsImageBuilderImagePipelineConfigSchedulePipelineExecutionStartCondition(rName, imagebuilder.PipelineExecutionStartConditionExpressionMatchOnly), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "schedule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "schedule.0.pipeline_execution_start_condition", imagebuilder.PipelineExecutionStartConditionExpressionMatchOnly), + ), + }, + }, + }) +} + +func TestAccAwsImageBuilderImagePipeline_Schedule_ScheduleExpression(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineConfigScheduleScheduleExpression(rName, "cron(1 0 * * *)"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "schedule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "schedule.0.schedule_expression", "cron(1 0 * * *)"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsImageBuilderImagePipelineConfigScheduleScheduleExpression(rName, "cron(2 0 * * *)"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "schedule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "schedule.0.schedule_expression", "cron(2 0 * * *)"), + ), + }, + }, + }) +} + +func TestAccAwsImageBuilderImagePipeline_Status(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineConfigStatus(rName, imagebuilder.PipelineStatusDisabled), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "status", imagebuilder.PipelineStatusDisabled), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsImageBuilderImagePipelineConfigStatus(rName, imagebuilder.PipelineStatusEnabled), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "status", imagebuilder.PipelineStatusEnabled), + ), + }, + }, + }) +} + +func TestAccAwsImageBuilderImagePipeline_Tags(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_imagebuilder_image_pipeline.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsImageBuilderImagePipelineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsImageBuilderImagePipelineConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsImageBuilderImagePipelineConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAwsImageBuilderImagePipelineConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsImageBuilderImagePipelineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckAwsImageBuilderImagePipelineDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).imagebuilderconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_imagebuilder_image_pipeline" { + continue + } + + input := &imagebuilder.GetImagePipelineInput{ + ImagePipelineArn: aws.String(rs.Primary.ID), + } + + output, err := conn.GetImagePipeline(input) + + if tfawserr.ErrCodeEquals(err, imagebuilder.ErrCodeResourceNotFoundException) { + continue + } + + if err != nil { + return fmt.Errorf("error getting Image Builder Image Pipeline (%s): %w", rs.Primary.ID, err) + } + + if output != nil { + return fmt.Errorf("Image Builder Image Pipeline (%s) still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckAwsImageBuilderImagePipelineExists(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 := testAccProvider.Meta().(*AWSClient).imagebuilderconn + + input := &imagebuilder.GetImagePipelineInput{ + ImagePipelineArn: aws.String(rs.Primary.ID), + } + + _, err := conn.GetImagePipeline(input) + + if err != nil { + return fmt.Errorf("error getting Image Builder Image Pipeline (%s): %w", rs.Primary.ID, err) + } + + return nil + } +} + +func testAccAwsImageBuilderImagePipelineConfigBase(rName string) string { + return fmt.Sprintf(` +data "aws_region" "current" {} + +data "aws_partition" "current" {} + +resource "aws_iam_instance_profile" "test" { + name = aws_iam_role.role.name + role = aws_iam_role.role.name +} + +resource "aws_iam_role" "role" { + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.${data.aws_partition.current.dns_suffix}" + } + Sid = "" + }] + }) + name = %[1]q +} + +resource "aws_imagebuilder_component" "test" { + data = yamlencode({ + phases = [{ + name = "build" + steps = [{ + action = "ExecuteBash" + inputs = { + commands = ["echo 'hello world'"] + } + name = "example" + onFailure = "Continue" + }] + }] + schemaVersion = 1.0 + }) + name = %[1]q + platform = "Linux" + version = "1.0.0" +} + +resource "aws_imagebuilder_image_recipe" "test" { + component { + component_arn = aws_imagebuilder_component.test.arn + } + + name = %[1]q + parent_image = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:image/amazon-linux-2-x86/x.x.x" + version = "1.0.0" +} + +resource "aws_imagebuilder_infrastructure_configuration" "test" { + instance_profile_name = aws_iam_instance_profile.test.name + name = %[1]q +} +`, rName) +} + +func testAccAwsImageBuilderImagePipelineConfigDescription(rName string, description string) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_image_pipeline" "test" { + description = %[2]q + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q +} +`, rName, description)) +} + +func testAccAwsImageBuilderImagePipelineConfigDistributionConfigurationArn1(rName string) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_distribution_configuration" "test" { + name = "%[1]s-1" + + distribution { + region = data.aws_region.current.name + } + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_imagebuilder_image_pipeline" "test" { + distribution_configuration_arn = aws_imagebuilder_distribution_configuration.test.arn + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q +} +`, rName)) +} + +func testAccAwsImageBuilderImagePipelineConfigDistributionConfigurationArn2(rName string) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_distribution_configuration" "test" { + name = "%[1]s-2" + + distribution { + region = data.aws_region.current.name + } + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_imagebuilder_image_pipeline" "test" { + distribution_configuration_arn = aws_imagebuilder_distribution_configuration.test.arn + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q +} +`, rName)) +} + +func testAccAwsImageBuilderImagePipelineConfigEnhancedImageMetadataEnabled(rName string, enhancedImageMetadataEnabled bool) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_image_pipeline" "test" { + enhanced_image_metadata_enabled = %[2]t + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q +} +`, rName, enhancedImageMetadataEnabled)) +} + +func testAccAwsImageBuilderImagePipelineConfigImageRecipeArn2(rName string) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_image_recipe" "test2" { + component { + component_arn = aws_imagebuilder_component.test.arn + } + + name = "%[1]s-2" + parent_image = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:image/amazon-linux-2-x86/x.x.x" + version = "1.0.0" +} + +resource "aws_imagebuilder_image_pipeline" "test" { + image_recipe_arn = aws_imagebuilder_image_recipe.test2.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q +} +`, rName)) +} + +func testAccAwsImageBuilderImagePipelineConfigImageTestsConfigurationImageTestsEnabled(rName string, imageTestsEnabled bool) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_image_pipeline" "test" { + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q + + image_tests_configuration { + image_tests_enabled = %[2]t + } +} +`, rName, imageTestsEnabled)) +} + +func testAccAwsImageBuilderImagePipelineConfigImageTestsConfigurationTimeoutMinutes(rName string, timeoutMinutes int) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_image_pipeline" "test" { + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q + + image_tests_configuration { + timeout_minutes = %[2]d + } +} +`, rName, timeoutMinutes)) +} + +func testAccAwsImageBuilderImagePipelineConfigInfrastructureConfigurationArn2(rName string) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_infrastructure_configuration" "test2" { + instance_profile_name = aws_iam_instance_profile.test.name + name = "%[1]s-2" +} + +resource "aws_imagebuilder_image_pipeline" "test" { + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test2.arn + name = %[1]q +} +`, rName)) +} + +func testAccAwsImageBuilderImagePipelineConfigName(rName string) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_image_pipeline" "test" { + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q +} +`, rName)) +} + +func testAccAwsImageBuilderImagePipelineConfigSchedulePipelineExecutionStartCondition(rName string, pipelineExecutionStartCondition string) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_image_pipeline" "test" { + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q + + schedule { + pipeline_execution_start_condition = %[2]q + schedule_expression = "cron(0 0 * * *)" + } +} +`, rName, pipelineExecutionStartCondition)) +} + +func testAccAwsImageBuilderImagePipelineConfigScheduleScheduleExpression(rName string, scheduleExpression string) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_image_pipeline" "test" { + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q + + schedule { + schedule_expression = %[2]q + } +} +`, rName, scheduleExpression)) +} + +func testAccAwsImageBuilderImagePipelineConfigStatus(rName string, status string) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_image_pipeline" "test" { + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q + status = %[2]q +} +`, rName, status)) +} + +func testAccAwsImageBuilderImagePipelineConfigTags1(rName string, tagKey1 string, tagValue1 string) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_image_pipeline" "test" { + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1)) +} + +func testAccAwsImageBuilderImagePipelineConfigTags2(rName string, tagKey1 string, tagValue1 string, tagKey2 string, tagValue2 string) string { + return composeConfig( + testAccAwsImageBuilderImagePipelineConfigBase(rName), + fmt.Sprintf(` +resource "aws_imagebuilder_image_pipeline" "test" { + image_recipe_arn = aws_imagebuilder_image_recipe.test.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.test.arn + name = %[1]q + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) +} diff --git a/website/docs/d/imagebuilder_image_pipeline.html.markdown b/website/docs/d/imagebuilder_image_pipeline.html.markdown new file mode 100644 index 00000000000..76d976e40ea --- /dev/null +++ b/website/docs/d/imagebuilder_image_pipeline.html.markdown @@ -0,0 +1,47 @@ +--- +subcategory: "Image Builder" +layout: "aws" +page_title: "AWS: aws_imagebuilder_image_pipeline" +description: |- + Provides details about an Image Builder Image Pipeline +--- + +# Data Source: aws_imagebuilder_image_pipeline + +Provides details about an Image Builder Image Pipeline. + +## Example Usage + +```hcl +data "aws_imagebuilder_image_pipeline" "example" { + arn = "arn:aws:imagebuilder:us-west-2:aws:image-pipeline/example" +} +``` + +## Argument Reference + +* `arn` - (Required) Amazon Resource Name (ARN) of the image pipeline. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `date_created` - Date the image pipeline was created. +* `date_last_run` - Date the image pipeline was last run. +* `date_next_run` - Date the image pipeline will run next. +* `date_updated` - Date the image pipeline was updated. +* `description` - Description of the image pipeline. +* `distribution_configuration_arn` - Amazon Resource Name (ARN) of the Image Builder Distribution Configuration. +* `enhanced_image_metadata_enabled` - Whether additional information about the image being created is collected. +* `image_recipe_arn` - Amazon Resource Name (ARN) of the Image Builder Infrastructure Recipe. +* `image_tests_configuration` - List of an object with image tests configuration. + * `image_tests_enabled` - Whether image tests are enabled. + * `timeout_minutes` - Number of minutes before image tests time out. +* `infrastructure_configuration_arn` - Amazon Resource Name (ARN) of the Image Builder Infrastructure Configuration. +* `name` - Name of the image pipeline. +* `platform` - Platform of the image pipeline. +* `schedule` - List of an object with schedule settings. + * `pipeline_execution_start_condition` - Condition when the pipeline should trigger a new image build. + * `schedule_expression` - Cron expression of how often the pipeline start condition is evaluated. +* `status` - Status of the image pipeline. +* `tags` - Key-value map of resource tags for the image pipeline. diff --git a/website/docs/r/imagebuilder_image_pipeline.html.markdown b/website/docs/r/imagebuilder_image_pipeline.html.markdown new file mode 100644 index 00000000000..8acbaf5a2b7 --- /dev/null +++ b/website/docs/r/imagebuilder_image_pipeline.html.markdown @@ -0,0 +1,79 @@ +--- +subcategory: "Image Builder" +layout: "aws" +page_title: "AWS: aws_imagebuilder_image_pipeline" +description: |- + Manages an Image Builder Image Pipeline +--- + +# Resource: aws_imagebuilder_image_pipeline + +Manages an Image Builder Image Pipeline. + +## Example Usage + +```hcl +resource "aws_imagebuilder_image_pipeline" "example" { + image_recipe_arn = aws_imagebuilder_image_recipe.example.arn + infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.example.arn + name = "example" + + schedule { + schedule_expression = "cron(0 0 * * *)" + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `image_recipe_arn` - (Required) Amazon Resource Name (ARN) of the Image Builder Infrastructure Recipe. +* `infrastructure_configuration_arn` - (Required) Amazon Resource Name (ARN) of the Image Builder Infrastructure Configuration. +* `name` - (Required) Name of the image pipeline. + +The following arguments are optional: + +* `description` - (Optional) Description of the image pipeline. +* `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`. +* `image_tests_configuration` - (Optional) Configuration block with image tests configuration. Detailed below. +* `schedule` - (Optional) Configuration block with schedule settings. Detailed below. +* `status` - (Optional) Status of the image pipeline. Valid values are `DISABLED` and `ENABLED`. Defaults to `ENABLED`. +* `tags` - (Optional) Key-value map of resource tags for the image pipeline. + +### image_tests_configuration + +The following arguments are optional: + +* `image_tests_enabled` - (Optional) Whether image tests are enabled. Defaults to `true`. +* `timeout_minutes` - (Optional) Number of minutes before image tests time out. Valid values are between `60` and `1440`. Defaults to `720`. + +### schedule + +The following arguments are required: + +* `schedule_expression` - (Required) Cron expression of how often the pipeline start condition is evaluated. For example, `cron(0 0 * * *)` is evaluated every day at midnight UTC. + +The following arguments are optional: + +* `pipeline_execution_start_condition` - (Optional) Condition when the pipeline should trigger a new image build. Valid values are `EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE` and `EXPRESSION_MATCH_ONLY`. Defaults to `EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - Amazon Resource Name (ARN) of the image pipeline. +* `date_created` - Date the image pipeline was created. +* `date_last_run` - Date the image pipeline was last run. +* `date_next_run` - Date the image pipeline will run next. +* `date_updated` - Date the image pipeline was updated. +* `platform` - Platform of the image pipeline. + +## Import + +`aws_imagebuilder_image_pipeline` resources can be imported using the Amazon Resource Name (ARN), e.g. + +``` +$ terraform import aws_imagebuilder_image_pipeline.example arn:aws:imagebuilder:us-east-1:123456789012:image-pipeline/example +```