From 38d4262734e866d6de26bad703685342e113f470 Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Tue, 11 May 2021 00:27:23 -0400 Subject: [PATCH 01/12] add service client without SDK --- .hashibot.hcl | 8 ++++ aws/config.go | 3 ++ .../keyvaluetags/generators/listtags/main.go | 1 + .../generators/servicetags/main.go | 1 + .../generators/updatetags/main.go | 1 + aws/internal/keyvaluetags/list_tags_gen.go | 18 +++++++++ .../service_generation_customizations.go | 3 ++ aws/internal/keyvaluetags/service_tags_gen.go | 28 ++++++++++++++ aws/internal/keyvaluetags/update_tags_gen.go | 37 +++++++++++++++++++ aws/provider.go | 1 + infrastructure/repository/labels-service.tf | 1 + website/allowed-subcategories.txt | 1 + .../guides/custom-service-endpoints.html.md | 1 + 13 files changed, 104 insertions(+) diff --git a/.hashibot.hcl b/.hashibot.hcl index a9dbcacc9d4..19a16a5cda6 100644 --- a/.hashibot.hcl +++ b/.hashibot.hcl @@ -132,6 +132,9 @@ behavior "regexp_issue_labeler_v2" "service_labels" { "service/appmesh" = [ "aws_appmesh_", ], + "service/apprunner" = [ + "aws_apprunner_", + ], "service/appstream" = [ "aws_appstream_", ], @@ -741,6 +744,11 @@ behavior "pull_request_path_labeler" "service_labels" { "**/*_appmesh_*", "**/appmesh_*" ] + "service/apprunner" = [ + "aws/internal/service/apprunner/**/*", + "**/*_apprunner_*", + "**/apprunner_*" + ] "service/appstream" = [ "aws/internal/service/appstream/**/*", "**/*_appstream_*", diff --git a/aws/config.go b/aws/config.go index 9710e36f982..372e3401250 100644 --- a/aws/config.go +++ b/aws/config.go @@ -18,6 +18,7 @@ import ( "github.com/aws/aws-sdk-go/service/applicationautoscaling" "github.com/aws/aws-sdk-go/service/applicationinsights" "github.com/aws/aws-sdk-go/service/appmesh" + "github.com/aws/aws-sdk-go/service/apprunner" "github.com/aws/aws-sdk-go/service/appstream" "github.com/aws/aws-sdk-go/service/appsync" "github.com/aws/aws-sdk-go/service/athena" @@ -221,6 +222,7 @@ type AWSClient struct { appconfigconn *appconfig.AppConfig applicationinsightsconn *applicationinsights.ApplicationInsights appmeshconn *appmesh.AppMesh + apprunnerconn *apprunner.AppRunner appstreamconn *appstream.AppStream appsyncconn *appsync.AppSync athenaconn *athena.Athena @@ -468,6 +470,7 @@ func (c *Config) Client() (interface{}, error) { appconfigconn: appconfig.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["appconfig"])})), applicationinsightsconn: applicationinsights.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["applicationinsights"])})), appmeshconn: appmesh.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["appmesh"])})), + apprunnerconn: apprunner.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["apprunner"])})), appstreamconn: appstream.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["appstream"])})), appsyncconn: appsync.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["appsync"])})), athenaconn: athena.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["athena"])})), diff --git a/aws/internal/keyvaluetags/generators/listtags/main.go b/aws/internal/keyvaluetags/generators/listtags/main.go index ebd9a627a0b..f7e35a5cd60 100644 --- a/aws/internal/keyvaluetags/generators/listtags/main.go +++ b/aws/internal/keyvaluetags/generators/listtags/main.go @@ -23,6 +23,7 @@ var serviceNames = []string{ "amplify", "apigatewayv2", "appmesh", + "apprunner", "appstream", "appsync", "athena", diff --git a/aws/internal/keyvaluetags/generators/servicetags/main.go b/aws/internal/keyvaluetags/generators/servicetags/main.go index 544f2fcef48..39c30ad7baa 100644 --- a/aws/internal/keyvaluetags/generators/servicetags/main.go +++ b/aws/internal/keyvaluetags/generators/servicetags/main.go @@ -21,6 +21,7 @@ var sliceServiceNames = []string{ "acm", "acmpca", "appmesh", + "apprunner", "athena", "autoscaling", "cloud9", diff --git a/aws/internal/keyvaluetags/generators/updatetags/main.go b/aws/internal/keyvaluetags/generators/updatetags/main.go index 309b3dff0c0..d330cbdf48e 100644 --- a/aws/internal/keyvaluetags/generators/updatetags/main.go +++ b/aws/internal/keyvaluetags/generators/updatetags/main.go @@ -24,6 +24,7 @@ var serviceNames = []string{ "apigateway", "apigatewayv2", "appmesh", + "apprunner", "appstream", "appsync", "athena", diff --git a/aws/internal/keyvaluetags/list_tags_gen.go b/aws/internal/keyvaluetags/list_tags_gen.go index 27f4e1309ee..4b2688e7c3f 100644 --- a/aws/internal/keyvaluetags/list_tags_gen.go +++ b/aws/internal/keyvaluetags/list_tags_gen.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/service/amplify" "github.com/aws/aws-sdk-go/service/apigatewayv2" "github.com/aws/aws-sdk-go/service/appmesh" + "github.com/aws/aws-sdk-go/service/apprunner" "github.com/aws/aws-sdk-go/service/appstream" "github.com/aws/aws-sdk-go/service/appsync" "github.com/aws/aws-sdk-go/service/athena" @@ -215,6 +216,23 @@ func AppmeshListTags(conn *appmesh.AppMesh, identifier string) (KeyValueTags, er return AppmeshKeyValueTags(output.Tags), nil } +// ApprunnerListTags lists apprunner service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func ApprunnerListTags(conn *apprunner.AppRunner, identifier string) (KeyValueTags, error) { + input := &apprunner.ListTagsForResourceInput{ + ResourceArn: aws.String(identifier), + } + + output, err := conn.ListTagsForResource(input) + + if err != nil { + return New(nil), err + } + + return ApprunnerKeyValueTags(output.Tags), nil +} + // AppstreamListTags lists appstream service tags. // The identifier is typically the Amazon Resource Name (ARN), although // it may also be a different identifier depending on the service. diff --git a/aws/internal/keyvaluetags/service_generation_customizations.go b/aws/internal/keyvaluetags/service_generation_customizations.go index 808c332e1e9..34819c2b764 100644 --- a/aws/internal/keyvaluetags/service_generation_customizations.go +++ b/aws/internal/keyvaluetags/service_generation_customizations.go @@ -13,6 +13,7 @@ import ( "github.com/aws/aws-sdk-go/service/apigateway" "github.com/aws/aws-sdk-go/service/apigatewayv2" "github.com/aws/aws-sdk-go/service/appmesh" + "github.com/aws/aws-sdk-go/service/apprunner" "github.com/aws/aws-sdk-go/service/appstream" "github.com/aws/aws-sdk-go/service/appsync" "github.com/aws/aws-sdk-go/service/athena" @@ -146,6 +147,8 @@ func ServiceClientType(serviceName string) string { funcType = reflect.TypeOf(apigatewayv2.New) case "appmesh": funcType = reflect.TypeOf(appmesh.New) + case "apprunner": + funcType = reflect.TypeOf(apprunner.New) case "appstream": funcType = reflect.TypeOf(appstream.New) case "appsync": diff --git a/aws/internal/keyvaluetags/service_tags_gen.go b/aws/internal/keyvaluetags/service_tags_gen.go index 9386b0917c9..2abcf13632a 100644 --- a/aws/internal/keyvaluetags/service_tags_gen.go +++ b/aws/internal/keyvaluetags/service_tags_gen.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/service/acm" "github.com/aws/aws-sdk-go/service/acmpca" "github.com/aws/aws-sdk-go/service/appmesh" + "github.com/aws/aws-sdk-go/service/apprunner" "github.com/aws/aws-sdk-go/service/athena" "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/aws/aws-sdk-go/service/cloud9" @@ -579,6 +580,33 @@ func AppmeshKeyValueTags(tags []*appmesh.TagRef) KeyValueTags { return New(m) } +// ApprunnerTags returns apprunner service tags. +func (tags KeyValueTags) ApprunnerTags() []*apprunner.Tag { + result := make([]*apprunner.Tag, 0, len(tags)) + + for k, v := range tags.Map() { + tag := &apprunner.Tag{ + Key: aws.String(k), + Value: aws.String(v), + } + + result = append(result, tag) + } + + return result +} + +// ApprunnerKeyValueTags creates KeyValueTags from apprunner service tags. +func ApprunnerKeyValueTags(tags []*apprunner.Tag) KeyValueTags { + m := make(map[string]*string, len(tags)) + + for _, tag := range tags { + m[aws.StringValue(tag.Key)] = tag.Value + } + + return New(m) +} + // AthenaTags returns athena service tags. func (tags KeyValueTags) AthenaTags() []*athena.Tag { result := make([]*athena.Tag, 0, len(tags)) diff --git a/aws/internal/keyvaluetags/update_tags_gen.go b/aws/internal/keyvaluetags/update_tags_gen.go index b3b5e1282a0..19f2c314012 100644 --- a/aws/internal/keyvaluetags/update_tags_gen.go +++ b/aws/internal/keyvaluetags/update_tags_gen.go @@ -13,6 +13,7 @@ import ( "github.com/aws/aws-sdk-go/service/apigateway" "github.com/aws/aws-sdk-go/service/apigatewayv2" "github.com/aws/aws-sdk-go/service/appmesh" + "github.com/aws/aws-sdk-go/service/apprunner" "github.com/aws/aws-sdk-go/service/appstream" "github.com/aws/aws-sdk-go/service/appsync" "github.com/aws/aws-sdk-go/service/athena" @@ -375,6 +376,42 @@ func AppmeshUpdateTags(conn *appmesh.AppMesh, identifier string, oldTagsMap inte return nil } +// ApprunnerUpdateTags updates apprunner service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func ApprunnerUpdateTags(conn *apprunner.AppRunner, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error { + oldTags := New(oldTagsMap) + newTags := New(newTagsMap) + + if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 { + input := &apprunner.UntagResourceInput{ + ResourceArn: aws.String(identifier), + TagKeys: aws.StringSlice(removedTags.IgnoreAws().Keys()), + } + + _, err := conn.UntagResource(input) + + if err != nil { + return fmt.Errorf("error untagging resource (%s): %w", identifier, err) + } + } + + if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 { + input := &apprunner.TagResourceInput{ + ResourceArn: aws.String(identifier), + Tags: updatedTags.IgnoreAws().ApprunnerTags(), + } + + _, err := conn.TagResource(input) + + if err != nil { + return fmt.Errorf("error tagging resource (%s): %w", identifier, err) + } + } + + return nil +} + // AppstreamUpdateTags updates appstream service tags. // The identifier is typically the Amazon Resource Name (ARN), although // it may also be a different identifier depending on the service. diff --git a/aws/provider.go b/aws/provider.go index 243982472e6..32f6e21badc 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -1236,6 +1236,7 @@ func init() { "applicationautoscaling", "applicationinsights", "appmesh", + "apprunner", "appstream", "appsync", "athena", diff --git a/infrastructure/repository/labels-service.tf b/infrastructure/repository/labels-service.tf index 2c39a1150af..753b05e32cc 100644 --- a/infrastructure/repository/labels-service.tf +++ b/infrastructure/repository/labels-service.tf @@ -19,6 +19,7 @@ variable "service_labels" { "applicationdiscoveryservice", "applicationinsights", "appmesh", + "apprunner", "appstream", "appsync", "athena", diff --git a/website/allowed-subcategories.txt b/website/allowed-subcategories.txt index 4ccc7e35cc1..fbeef224590 100644 --- a/website/allowed-subcategories.txt +++ b/website/allowed-subcategories.txt @@ -6,6 +6,7 @@ API Gateway v2 (WebSocket and HTTP APIs) Access Analyzer AppConfig AppMesh +App Runner AppSync Application Autoscaling Athena diff --git a/website/docs/guides/custom-service-endpoints.html.md b/website/docs/guides/custom-service-endpoints.html.md index e618d1b85d1..fc513621a1d 100644 --- a/website/docs/guides/custom-service-endpoints.html.md +++ b/website/docs/guides/custom-service-endpoints.html.md @@ -63,6 +63,7 @@ The Terraform AWS Provider allows the following endpoints to be customized:
  • applicationautoscaling
  • applicationinsights
  • appmesh
  • +
  • apprunner
  • appstream
  • appsync
  • athena
  • From bbe13a92bac650beab4b4574636892708f07a9c6 Mon Sep 17 00:00:00 2001 From: angie pinilla Date: Mon, 17 May 2021 21:19:46 -0400 Subject: [PATCH 02/12] resource/apprunner_auto_scaling_configuration_version (#360) * add auto scaling config version resource * CR/linter updates --- .../service/apprunner/waiter/status.go | 29 ++ .../service/apprunner/waiter/waiter.go | 43 ++ aws/provider.go | 1 + ...nner_auto_scaling_configuration_version.go | 227 ++++++++ ...auto_scaling_configuration_version_test.go | 491 ++++++++++++++++++ ...caling_configuration_version.html.markdown | 55 ++ 6 files changed, 846 insertions(+) create mode 100644 aws/internal/service/apprunner/waiter/status.go create mode 100644 aws/internal/service/apprunner/waiter/waiter.go create mode 100644 aws/resource_aws_apprunner_auto_scaling_configuration_version.go create mode 100644 aws/resource_aws_apprunner_auto_scaling_configuration_version_test.go create mode 100644 website/docs/r/apprunner_auto_scaling_configuration_version.html.markdown diff --git a/aws/internal/service/apprunner/waiter/status.go b/aws/internal/service/apprunner/waiter/status.go new file mode 100644 index 00000000000..e63cefcf52a --- /dev/null +++ b/aws/internal/service/apprunner/waiter/status.go @@ -0,0 +1,29 @@ +package waiter + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apprunner" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func AutoScalingConfigurationStatus(ctx context.Context, conn *apprunner.AppRunner, arn string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + input := &apprunner.DescribeAutoScalingConfigurationInput{ + AutoScalingConfigurationArn: aws.String(arn), + } + + output, err := conn.DescribeAutoScalingConfigurationWithContext(ctx, input) + + if err != nil { + return nil, "", err + } + + if output == nil || output.AutoScalingConfiguration == nil { + return nil, "", nil + } + + return output.AutoScalingConfiguration, aws.StringValue(output.AutoScalingConfiguration.Status), nil + } +} diff --git a/aws/internal/service/apprunner/waiter/waiter.go b/aws/internal/service/apprunner/waiter/waiter.go new file mode 100644 index 00000000000..1646c473a47 --- /dev/null +++ b/aws/internal/service/apprunner/waiter/waiter.go @@ -0,0 +1,43 @@ +package waiter + +import ( + "context" + "time" + + "github.com/aws/aws-sdk-go/service/apprunner" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +const ( + AutoScalingConfigurationStatusActive = "active" + AutoScalingConfigurationStatusInactive = "inactive" + + AutoScalingConfigurationCreateTimeout = 2 * time.Minute + AutoScalingConfigurationDeleteTimeout = 2 * time.Minute +) + +func AutoScalingConfigurationActive(ctx context.Context, conn *apprunner.AppRunner, arn string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{}, + Target: []string{AutoScalingConfigurationStatusActive}, + Refresh: AutoScalingConfigurationStatus(ctx, conn, arn), + Timeout: AutoScalingConfigurationCreateTimeout, + } + + _, err := stateConf.WaitForState() + + return err +} + +func AutoScalingConfigurationInactive(ctx context.Context, conn *apprunner.AppRunner, arn string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{AutoScalingConfigurationStatusActive}, + Target: []string{AutoScalingConfigurationStatusInactive}, + Refresh: AutoScalingConfigurationStatus(ctx, conn, arn), + Timeout: AutoScalingConfigurationDeleteTimeout, + } + + _, err := stateConf.WaitForState() + + return err +} diff --git a/aws/provider.go b/aws/provider.go index bfa4cd68c6b..f4d7cda65d6 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -494,6 +494,7 @@ func Provider() *schema.Provider { "aws_appmesh_virtual_node": resourceAwsAppmeshVirtualNode(), "aws_appmesh_virtual_router": resourceAwsAppmeshVirtualRouter(), "aws_appmesh_virtual_service": resourceAwsAppmeshVirtualService(), + "aws_apprunner_auto_scaling_configuration_version": resourceAwsAppRunnerAutoScalingConfigurationVersion(), "aws_appsync_api_key": resourceAwsAppsyncApiKey(), "aws_appsync_datasource": resourceAwsAppsyncDatasource(), "aws_appsync_function": resourceAwsAppsyncFunction(), diff --git a/aws/resource_aws_apprunner_auto_scaling_configuration_version.go b/aws/resource_aws_apprunner_auto_scaling_configuration_version.go new file mode 100644 index 00000000000..2eff20282b9 --- /dev/null +++ b/aws/resource_aws_apprunner_auto_scaling_configuration_version.go @@ -0,0 +1,227 @@ +package aws + +import ( + "context" + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apprunner" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/waiter" +) + +func resourceAwsAppRunnerAutoScalingConfigurationVersion() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceAwsAppRunnerAutoScalingConfigurationCreate, + ReadWithoutTimeout: resourceAwsAppRunnerAutoScalingConfigurationRead, + UpdateWithoutTimeout: resourceAwsAppRunnerAutoScalingConfigurationUpdate, + DeleteWithoutTimeout: resourceAwsAppRunnerAutoScalingConfigurationDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "auto_scaling_configuration_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "auto_scaling_configuration_revision": { + Type: schema.TypeInt, + Computed: true, + }, + "latest": { + Type: schema.TypeBool, + Computed: true, + }, + "max_concurrency": { + Type: schema.TypeInt, + Optional: true, + Default: 100, + ForceNew: true, + }, + "max_size": { + Type: schema.TypeInt, + Optional: true, + Default: 50, + ForceNew: true, + }, + "min_size": { + Type: schema.TypeInt, + Optional: true, + Default: 1, + ForceNew: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchema(), + "tags_all": tagsSchemaComputed(), + }, + + CustomizeDiff: SetTagsDiff, + } +} + +func resourceAwsAppRunnerAutoScalingConfigurationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) + + name := d.Get("auto_scaling_configuration_name").(string) + + input := &apprunner.CreateAutoScalingConfigurationInput{ + AutoScalingConfigurationName: aws.String(name), + } + + if v, ok := d.GetOk("max_concurrency"); ok { + input.MaxConcurrency = aws.Int64(int64(v.(int))) + } + + if v, ok := d.GetOk("max_size"); ok { + input.MaxSize = aws.Int64(int64(v.(int))) + } + + if v, ok := d.GetOk("min_size"); ok { + input.MinSize = aws.Int64(int64(v.(int))) + } + + if len(tags) > 0 { + input.Tags = tags.IgnoreAws().ApprunnerTags() + } + + output, err := conn.CreateAutoScalingConfigurationWithContext(ctx, input) + + if err != nil { + return diag.FromErr(fmt.Errorf("error creating App Runner AutoScaling Configuration Version (%s): %w", name, err)) + } + + if output == nil || output.AutoScalingConfiguration == nil { + return diag.FromErr(fmt.Errorf("error creating App Runner AutoScaling Configuration Version (%s): empty output", name)) + } + + d.SetId(aws.StringValue(output.AutoScalingConfiguration.AutoScalingConfigurationArn)) + + if err := waiter.AutoScalingConfigurationActive(ctx, conn, d.Id()); err != nil { + return diag.FromErr(fmt.Errorf("error waiting for AutoScaling Configuration Version (%s) creation: %w", d.Id(), err)) + } + + return resourceAwsAppRunnerAutoScalingConfigurationRead(ctx, d, meta) +} + +func resourceAwsAppRunnerAutoScalingConfigurationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + input := &apprunner.DescribeAutoScalingConfigurationInput{ + AutoScalingConfigurationArn: aws.String(d.Id()), + } + + output, err := conn.DescribeAutoScalingConfigurationWithContext(ctx, input) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] App Runner AutoScaling Configuration Version (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error reading App Runner AutoScaling Configuration Version (%s): %w", d.Id(), err)) + } + + if output == nil || output.AutoScalingConfiguration == nil { + return diag.FromErr(fmt.Errorf("error reading App Runner AutoScaling Configuration Version (%s): empty output", d.Id())) + } + + if aws.StringValue(output.AutoScalingConfiguration.Status) == waiter.AutoScalingConfigurationStatusInactive { + if d.IsNewResource() { + return diag.FromErr(fmt.Errorf("error reading App Runner AutoScaling Configuration Version (%s): %s after creation", d.Id(), aws.StringValue(output.AutoScalingConfiguration.Status))) + } + log.Printf("[WARN] App Runner AutoScaling Configuration Version (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + config := output.AutoScalingConfiguration + arn := aws.StringValue(config.AutoScalingConfigurationArn) + + d.Set("arn", arn) + d.Set("auto_scaling_configuration_name", config.AutoScalingConfigurationName) + d.Set("auto_scaling_configuration_revision", config.AutoScalingConfigurationRevision) + d.Set("latest", config.Latest) + d.Set("max_concurrency", config.MaxConcurrency) + d.Set("max_size", config.MaxSize) + d.Set("min_size", config.MinSize) + d.Set("status", config.Status) + + tags, err := keyvaluetags.ApprunnerListTags(conn, arn) + + if err != nil { + return diag.FromErr(fmt.Errorf("error listing tags for App Runner AutoScaling Configuration Version (%s): %s", arn, err)) + } + + tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting tags: %w", err)) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting tags_all: %w", err)) + } + + return nil +} + +func resourceAwsAppRunnerAutoScalingConfigurationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := keyvaluetags.ApprunnerUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return diag.FromErr(fmt.Errorf("error updating App Runner AutoScaling Configuration Version (%s) tags: %s", d.Get("arn").(string), err)) + } + } + + return resourceAwsAppRunnerAutoScalingConfigurationRead(ctx, d, meta) +} + +func resourceAwsAppRunnerAutoScalingConfigurationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + + input := &apprunner.DeleteAutoScalingConfigurationInput{ + AutoScalingConfigurationArn: aws.String(d.Id()), + } + + _, err := conn.DeleteAutoScalingConfigurationWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error deleting App Runner AutoScaling Configuration Version (%s): %w", d.Id(), err)) + } + + if err := waiter.AutoScalingConfigurationInactive(ctx, conn, d.Id()); err != nil { + if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + return nil + } + return diag.FromErr(fmt.Errorf("error waiting for AutoScaling Configuration Version (%s) deletion: %w", d.Id(), err)) + } + + return nil +} diff --git a/aws/resource_aws_apprunner_auto_scaling_configuration_version_test.go b/aws/resource_aws_apprunner_auto_scaling_configuration_version_test.go new file mode 100644 index 00000000000..85f27cf6091 --- /dev/null +++ b/aws/resource_aws_apprunner_auto_scaling_configuration_version_test.go @@ -0,0 +1,491 @@ +package aws + +import ( + "context" + "fmt" + "log" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apprunner" + "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_apprunner_auto_scaling_configuration_version", &resource.Sweeper{ + Name: "aws_apprunner_auto_scaling_configuration_version", + F: testSweepAppRunnerAutoScalingConfigurationVersions, + Dependencies: []string{"aws_apprunner_service"}, + }) +} + +func testSweepAppRunnerAutoScalingConfigurationVersions(region string) error { + client, err := sharedClientForRegion(region) + + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + + conn := client.(*AWSClient).apprunnerconn + sweepResources := make([]*testSweepResource, 0) + ctx := context.Background() + var errs *multierror.Error + + input := &apprunner.ListAutoScalingConfigurationsInput{} + + err = conn.ListAutoScalingConfigurationsPagesWithContext(ctx, input, func(page *apprunner.ListAutoScalingConfigurationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, summaryConfig := range page.AutoScalingConfigurationSummaryList { + if summaryConfig == nil { + continue + } + + arn := aws.StringValue(summaryConfig.AutoScalingConfigurationArn) + + log.Printf("[INFO] Deleting App Runner AutoScaling Configuration Version (%s)", arn) + r := resourceAwsAppRunnerAutoScalingConfigurationVersion() + d := r.Data(nil) + d.SetId(arn) + + sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) + } + + return !lastPage + }) + + if err != nil { + errs = multierror.Append(errs, fmt.Errorf("error listing App Runner AutoScaling Configuration Versions: %w", err)) + } + + if err = testSweepResourceOrchestrator(sweepResources); err != nil { + errs = multierror.Append(errs, fmt.Errorf("error sweeping App Runner AutoScaling Configuration Version for %s: %w", region, err)) + } + + if testSweepSkipSweepError(errs.ErrorOrNil()) { + log.Printf("[WARN] Skipping App Runner AutoScaling Configuration Versions sweep for %s: %s", region, errs) + return nil + } + + return errs.ErrorOrNil() +} + +func TestAccAwsAppRunnerAutoScalingConfigurationVersion_basic(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_auto_scaling_configuration_version.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerAutoScalingConfigurationVersionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerAutoScalingConfigurationVersionConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(resourceName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`autoscalingconfiguration/%s/1/.+`, rName))), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_name", rName), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_revision", "1"), + resource.TestCheckResourceAttr(resourceName, "latest", "true"), + resource.TestCheckResourceAttr(resourceName, "max_concurrency", "100"), + resource.TestCheckResourceAttr(resourceName, "max_size", "50"), + resource.TestCheckResourceAttr(resourceName, "min_size", "1"), + resource.TestCheckResourceAttr(resourceName, "status", "active"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsAppRunnerAutoScalingConfigurationVersion_complex(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_auto_scaling_configuration_version.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerAutoScalingConfigurationVersionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerAutoScalingConfigurationVersionConfig_withNonDefaults(rName, 50, 10, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(resourceName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`autoscalingconfiguration/%s/1/.+`, rName))), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_name", rName), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_revision", "1"), + resource.TestCheckResourceAttr(resourceName, "latest", "true"), + resource.TestCheckResourceAttr(resourceName, "max_concurrency", "50"), + resource.TestCheckResourceAttr(resourceName, "max_size", "10"), + resource.TestCheckResourceAttr(resourceName, "min_size", "2"), + resource.TestCheckResourceAttr(resourceName, "status", "active"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // Test resource recreation such that the revision number is still 1 + Config: testAccAppRunnerAutoScalingConfigurationVersionConfig_withNonDefaults(rName, 150, 20, 5), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(resourceName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`autoscalingconfiguration/%s/1/.+`, rName))), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_name", rName), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_revision", "1"), + resource.TestCheckResourceAttr(resourceName, "latest", "true"), + resource.TestCheckResourceAttr(resourceName, "max_concurrency", "150"), + resource.TestCheckResourceAttr(resourceName, "max_size", "20"), + resource.TestCheckResourceAttr(resourceName, "min_size", "5"), + resource.TestCheckResourceAttr(resourceName, "status", "active"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // Test resource recreation such that the revision number is still 1 + Config: testAccAppRunnerAutoScalingConfigurationVersionConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(resourceName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`autoscalingconfiguration/%s/1/.+`, rName))), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_name", rName), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_revision", "1"), + resource.TestCheckResourceAttr(resourceName, "latest", "true"), + resource.TestCheckResourceAttr(resourceName, "max_concurrency", "100"), + resource.TestCheckResourceAttr(resourceName, "max_size", "50"), + resource.TestCheckResourceAttr(resourceName, "min_size", "1"), + resource.TestCheckResourceAttr(resourceName, "status", "active"), + ), + }, + }, + }) +} + +func TestAccAwsAppRunnerAutoScalingConfigurationVersion_MultipleVersions(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_auto_scaling_configuration_version.test" + otherResourceName := "aws_apprunner_auto_scaling_configuration_version.other" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerAutoScalingConfigurationVersionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerAutoScalingConfigurationVersionConfig_multipleVersions(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(resourceName), + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(otherResourceName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`autoscalingconfiguration/%s/1/.+`, rName))), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_name", rName), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_revision", "1"), + resource.TestCheckResourceAttr(resourceName, "latest", "true"), + resource.TestCheckResourceAttr(resourceName, "max_concurrency", "100"), + resource.TestCheckResourceAttr(resourceName, "max_size", "50"), + resource.TestCheckResourceAttr(resourceName, "min_size", "1"), + resource.TestCheckResourceAttr(resourceName, "status", "active"), + testAccMatchResourceAttrRegionalARN(otherResourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`autoscalingconfiguration/%s/2/.+`, rName))), + resource.TestCheckResourceAttr(otherResourceName, "auto_scaling_configuration_name", rName), + resource.TestCheckResourceAttr(otherResourceName, "auto_scaling_configuration_revision", "2"), + resource.TestCheckResourceAttr(otherResourceName, "latest", "true"), + resource.TestCheckResourceAttr(otherResourceName, "max_concurrency", "100"), + resource.TestCheckResourceAttr(otherResourceName, "max_size", "50"), + resource.TestCheckResourceAttr(otherResourceName, "min_size", "1"), + resource.TestCheckResourceAttr(otherResourceName, "status", "active"), + ), + }, + { + // Test update of "latest" computed attribute after apply + Config: testAccAppRunnerAutoScalingConfigurationVersionConfig_multipleVersions(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(resourceName), + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(otherResourceName), + resource.TestCheckResourceAttr(resourceName, "latest", "false"), + resource.TestCheckResourceAttr(otherResourceName, "latest", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: otherResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsAppRunnerAutoScalingConfigurationVersion_UpdateMultipleVersions(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_auto_scaling_configuration_version.test" + otherResourceName := "aws_apprunner_auto_scaling_configuration_version.other" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerAutoScalingConfigurationVersionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerAutoScalingConfigurationVersionConfig_multipleVersions(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(resourceName), + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(otherResourceName), + ), + }, + { + Config: testAccAppRunnerAutoScalingConfigurationVersionConfig_updateMultipleVersions(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(resourceName), + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(otherResourceName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`autoscalingconfiguration/%s/1/.+`, rName))), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_name", rName), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_revision", "1"), + resource.TestCheckResourceAttr(resourceName, "latest", "false"), + resource.TestCheckResourceAttr(resourceName, "max_concurrency", "100"), + resource.TestCheckResourceAttr(resourceName, "max_size", "50"), + resource.TestCheckResourceAttr(resourceName, "min_size", "1"), + resource.TestCheckResourceAttr(resourceName, "status", "active"), + testAccMatchResourceAttrRegionalARN(otherResourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`autoscalingconfiguration/%s/2/.+`, rName))), + resource.TestCheckResourceAttr(otherResourceName, "auto_scaling_configuration_name", rName), + resource.TestCheckResourceAttr(otherResourceName, "auto_scaling_configuration_revision", "2"), + resource.TestCheckResourceAttr(otherResourceName, "latest", "true"), + resource.TestCheckResourceAttr(otherResourceName, "max_concurrency", "125"), + resource.TestCheckResourceAttr(otherResourceName, "max_size", "25"), + resource.TestCheckResourceAttr(otherResourceName, "min_size", "1"), + resource.TestCheckResourceAttr(otherResourceName, "status", "active"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: otherResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsAppRunnerAutoScalingConfigurationVersion_disappears(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_auto_scaling_configuration_version.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerAutoScalingConfigurationVersionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerAutoScalingConfigurationVersionConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsAppRunnerAutoScalingConfigurationVersion(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAwsAppRunnerAutoScalingConfigurationVersion_tags(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_auto_scaling_configuration_version.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerAutoScalingConfigurationVersionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerAutoScalingConfigurationVersionConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAppRunnerAutoScalingConfigurationVersionConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAppRunnerAutoScalingConfigurationVersionConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckAwsAppRunnerAutoScalingConfigurationVersionDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_apprunner_auto_scaling_configuration_version" { + continue + } + + conn := testAccProvider.Meta().(*AWSClient).apprunnerconn + + input := &apprunner.DescribeAutoScalingConfigurationInput{ + AutoScalingConfigurationArn: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeAutoScalingConfigurationWithContext(context.Background(), input) + + if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + continue + } + + if err != nil { + return err + } + + if output != nil && output.AutoScalingConfiguration != nil && aws.StringValue(output.AutoScalingConfiguration.Status) != "inactive" { + return fmt.Errorf("App Runner AutoScaling Configuration (%s) still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckAwsAppRunnerAutoScalingConfigurationVersionExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No App Runner Service ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).apprunnerconn + + input := &apprunner.DescribeAutoScalingConfigurationInput{ + AutoScalingConfigurationArn: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeAutoScalingConfigurationWithContext(context.Background(), input) + + if err != nil { + return err + } + + if output == nil || output.AutoScalingConfiguration == nil { + return fmt.Errorf("App Runner AutoScaling Configuration (%s) not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccAppRunnerAutoScalingConfigurationVersionConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_apprunner_auto_scaling_configuration_version" "test" { + auto_scaling_configuration_name = %[1]q +} +`, rName) +} + +func testAccAppRunnerAutoScalingConfigurationVersionConfig_withNonDefaults(rName string, maxConcurrency, maxSize, minSize int) string { + return fmt.Sprintf(` +resource "aws_apprunner_auto_scaling_configuration_version" "test" { + auto_scaling_configuration_name = %[1]q + + max_concurrency = %[2]d + max_size = %[3]d + min_size = %[4]d +} +`, rName, maxConcurrency, maxSize, minSize) +} + +func testAccAppRunnerAutoScalingConfigurationVersionConfig_multipleVersions(rName string) string { + return fmt.Sprintf(` +resource "aws_apprunner_auto_scaling_configuration_version" "test" { + auto_scaling_configuration_name = %[1]q +} + +resource "aws_apprunner_auto_scaling_configuration_version" "other" { + auto_scaling_configuration_name = aws_apprunner_auto_scaling_configuration_version.test.auto_scaling_configuration_name +} +`, rName) +} + +func testAccAppRunnerAutoScalingConfigurationVersionConfig_updateMultipleVersions(rName string) string { + return fmt.Sprintf(` +resource "aws_apprunner_auto_scaling_configuration_version" "test" { + auto_scaling_configuration_name = %[1]q +} + +resource "aws_apprunner_auto_scaling_configuration_version" "other" { + auto_scaling_configuration_name = aws_apprunner_auto_scaling_configuration_version.test.auto_scaling_configuration_name + + max_concurrency = 125 + max_size = 25 +} +`, rName) +} + +func testAccAppRunnerAutoScalingConfigurationVersionConfigTags1(rName string, tagKey1 string, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_apprunner_auto_scaling_configuration_version" "test" { + auto_scaling_configuration_name = %[1]q + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccAppRunnerAutoScalingConfigurationVersionConfigTags2(rName string, tagKey1 string, tagValue1 string, tagKey2 string, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_apprunner_auto_scaling_configuration_version" "test" { + auto_scaling_configuration_name = %[1]q + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/website/docs/r/apprunner_auto_scaling_configuration_version.html.markdown b/website/docs/r/apprunner_auto_scaling_configuration_version.html.markdown new file mode 100644 index 00000000000..81b511427ed --- /dev/null +++ b/website/docs/r/apprunner_auto_scaling_configuration_version.html.markdown @@ -0,0 +1,55 @@ +--- +subcategory: "App Runner" +layout: "aws" +page_title: "AWS: aws_apprunner_auto_scaling_configuration_version" +description: |- + Manage an App Runner AutoScaling Configuration Version. +--- + +# Resource: aws_apprunner_auto_scaling_configuration_version + +Manages an App Runner AutoScaling Configuration Version. + +## Example Usage + +```terraform +resource "aws_apprunner_auto_scaling_configuration_version" "example" { + auto_scaling_configuration_name = "example" + + max_concurrency = 50 + max_size = 10 + min_size = 2 + + tags = { + Name = "example-apprunner-autoscaling" + } +} +``` + +## Argument Reference + +The following arguments supported: + +* `auto_scaling_configuration_name` - (Required, Forces new resource) Name of the auto scaling configuration. +* `max_concurrency` - (Optional, Forces new resource) The maximal number of concurrent requests that you want an instance to process. When the number of concurrent requests goes over this limit, App Runner scales up your service. +* `max_size` - (Optional, Forces new resource) The maximal number of instances that App Runner provisions for your service. +* `min_size` - (Optional, Forces new resource) The minimal number of instances that App Runner provisions for your service. +* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - ARN of this auto scaling configuration version. +* `auto_scaling_configuration_revision` - The revision of this auto scaling configuration. +* `latest` - Whether the auto scaling configuration has the highest `auto_scaling_configuration_revision` among all configurations that share the same `auto_scaling_configuration_name`. +* `status` - The current state of the auto scaling configuration. An INACTIVE configuration revision has been deleted and can't be used. It is permanently removed some time after deletion. +* `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 + +App Runner AutoScaling Configuration Versions can be imported by using the `arn`, e.g. + +``` +$ terraform import aws_apprunner_auto_scaling_configuration_version.example "arn:aws:apprunner:us-east-1:1234567890:autoscalingconfiguration/example/1/69bdfe0115224b0db49398b7beb68e0f +``` From 46b348dfde4fb2df4c450f965b1e6a34aca32226 Mon Sep 17 00:00:00 2001 From: angie pinilla Date: Mon, 17 May 2021 21:28:41 -0400 Subject: [PATCH 03/12] resource/apprunner_connection: new resource (#354) * apprunner connection resource * Update finder method name to ConnectionSummaryByName Co-authored-by: Kit Ewbank * Update finder usage ConnectionSummaryByName Co-authored-by: Kit Ewbank * Update aws/resource_aws_apprunner_connection.go Co-authored-by: Kit Ewbank * Update finder usage to ConnectionSummaryByName Co-authored-by: Kit Ewbank * Update aws/resource_aws_apprunner_connection.go Co-authored-by: Kit Ewbank * Update finder method name to ConnectionSummaryByName Co-authored-by: Kit Ewbank * Update finder method name to ConnectionSummaryByName Co-authored-by: Kit Ewbank * update to new sweeper method usage * add precheck for govcloud * semgrep fix Co-authored-by: Kit Ewbank --- .../service/apprunner/finder/finder.go | 45 +++ .../service/apprunner/waiter/status.go | 16 + .../service/apprunner/waiter/waiter.go | 15 + aws/provider.go | 1 + aws/resource_aws_apprunner_connection.go | 185 ++++++++++++ aws/resource_aws_apprunner_connection_test.go | 273 ++++++++++++++++++ .../docs/r/apprunner_connection.html.markdown | 50 ++++ 7 files changed, 585 insertions(+) create mode 100644 aws/internal/service/apprunner/finder/finder.go create mode 100644 aws/resource_aws_apprunner_connection.go create mode 100644 aws/resource_aws_apprunner_connection_test.go create mode 100644 website/docs/r/apprunner_connection.html.markdown diff --git a/aws/internal/service/apprunner/finder/finder.go b/aws/internal/service/apprunner/finder/finder.go new file mode 100644 index 00000000000..3d5084e7549 --- /dev/null +++ b/aws/internal/service/apprunner/finder/finder.go @@ -0,0 +1,45 @@ +package finder + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apprunner" +) + +func ConnectionSummaryByName(ctx context.Context, conn *apprunner.AppRunner, name string) (*apprunner.ConnectionSummary, error) { + input := &apprunner.ListConnectionsInput{ + ConnectionName: aws.String(name), + } + + var cs *apprunner.ConnectionSummary + + err := conn.ListConnectionsPagesWithContext(ctx, input, func(page *apprunner.ListConnectionsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, c := range page.ConnectionSummaryList { + if c == nil { + continue + } + + if aws.StringValue(c.ConnectionName) == name { + cs = c + return false + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + if cs == nil { + return nil, nil + } + + return cs, nil +} diff --git a/aws/internal/service/apprunner/waiter/status.go b/aws/internal/service/apprunner/waiter/status.go index e63cefcf52a..887bc248206 100644 --- a/aws/internal/service/apprunner/waiter/status.go +++ b/aws/internal/service/apprunner/waiter/status.go @@ -27,3 +27,19 @@ func AutoScalingConfigurationStatus(ctx context.Context, conn *apprunner.AppRunn return output.AutoScalingConfiguration, aws.StringValue(output.AutoScalingConfiguration.Status), nil } } + +func ConnectionStatus(ctx context.Context, conn *apprunner.AppRunner, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + c, err := finder.ConnectionSummaryByName(ctx, conn, name) + + if err != nil { + return nil, "", err + } + + if c == nil { + return nil, "", nil + } + + return c, aws.StringValue(c.Status), nil + } +} diff --git a/aws/internal/service/apprunner/waiter/waiter.go b/aws/internal/service/apprunner/waiter/waiter.go index 1646c473a47..f290103a047 100644 --- a/aws/internal/service/apprunner/waiter/waiter.go +++ b/aws/internal/service/apprunner/waiter/waiter.go @@ -14,6 +14,8 @@ const ( AutoScalingConfigurationCreateTimeout = 2 * time.Minute AutoScalingConfigurationDeleteTimeout = 2 * time.Minute + + ConnectionDeleteTimeout = 5 * time.Minute ) func AutoScalingConfigurationActive(ctx context.Context, conn *apprunner.AppRunner, arn string) error { @@ -41,3 +43,16 @@ func AutoScalingConfigurationInactive(ctx context.Context, conn *apprunner.AppRu return err } + +func ConnectionDeleted(ctx context.Context, conn *apprunner.AppRunner, name string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{apprunner.ConnectionStatusPendingHandshake, apprunner.ConnectionStatusAvailable, apprunner.ConnectionStatusDeleted}, + Target: []string{}, + Refresh: ConnectionStatus(ctx, conn, name), + Timeout: ConnectionDeleteTimeout, + } + + _, err := stateConf.WaitForState() + + return err +} diff --git a/aws/provider.go b/aws/provider.go index f4d7cda65d6..45cff168188 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -495,6 +495,7 @@ func Provider() *schema.Provider { "aws_appmesh_virtual_router": resourceAwsAppmeshVirtualRouter(), "aws_appmesh_virtual_service": resourceAwsAppmeshVirtualService(), "aws_apprunner_auto_scaling_configuration_version": resourceAwsAppRunnerAutoScalingConfigurationVersion(), + "aws_apprunner_connection": resourceAwsAppRunnerConnection(), "aws_appsync_api_key": resourceAwsAppsyncApiKey(), "aws_appsync_datasource": resourceAwsAppsyncDatasource(), "aws_appsync_function": resourceAwsAppsyncFunction(), diff --git a/aws/resource_aws_apprunner_connection.go b/aws/resource_aws_apprunner_connection.go new file mode 100644 index 00000000000..f513d894706 --- /dev/null +++ b/aws/resource_aws_apprunner_connection.go @@ -0,0 +1,185 @@ +package aws + +import ( + "context" + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apprunner" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "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" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/waiter" +) + +func resourceAwsAppRunnerConnection() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceAwsAppRunnerConnectionCreate, + ReadWithoutTimeout: resourceAwsAppRunnerConnectionRead, + UpdateWithoutTimeout: resourceAwsAppRunnerConnectionUpdate, + DeleteWithoutTimeout: resourceAwsAppRunnerConnectionDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "connection_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "provider_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(apprunner.ProviderType_Values(), false), + ForceNew: true, + }, + + "status": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tagsSchema(), + + "tags_all": tagsSchemaComputed(), + }, + + CustomizeDiff: SetTagsDiff, + } +} + +func resourceAwsAppRunnerConnectionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) + + name := d.Get("connection_name").(string) + + input := &apprunner.CreateConnectionInput{ + ConnectionName: aws.String(name), + ProviderType: aws.String(d.Get("provider_type").(string)), + } + + if len(tags) > 0 { + input.Tags = tags.IgnoreAws().ApprunnerTags() + } + + output, err := conn.CreateConnectionWithContext(ctx, input) + + if err != nil { + return diag.FromErr(fmt.Errorf("error creating App Runner Connection (%s): %w", name, err)) + } + + if output == nil || output.Connection == nil { + return diag.FromErr(fmt.Errorf("error creating App Runner Connection (%s): empty output", name)) + } + + d.SetId(aws.StringValue(output.Connection.ConnectionName)) + + return resourceAwsAppRunnerConnectionRead(ctx, d, meta) +} + +func resourceAwsAppRunnerConnectionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + c, err := finder.ConnectionSummaryByName(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] App Runner Connection (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error reading App Runner Connection (%s): %w", d.Id(), err)) + } + + if c == nil { + if d.IsNewResource() { + return diag.FromErr(fmt.Errorf("error reading App Runner Connection (%s): empty output after creation", d.Id())) + } + log.Printf("[WARN] App Runner Connection (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + arn := aws.StringValue(c.ConnectionArn) + + d.Set("arn", arn) + d.Set("connection_name", c.ConnectionName) + d.Set("provider_type", c.ProviderType) + d.Set("status", c.Status) + + tags, err := keyvaluetags.ApprunnerListTags(conn, arn) + + if err != nil { + return diag.FromErr(fmt.Errorf("error listing tags for App Runner Connection (%s): %w", arn, err)) + } + + tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting tags: %w", err)) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting tags_all: %w", err)) + } + + return nil +} + +func resourceAwsAppRunnerConnectionUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := keyvaluetags.ApprunnerUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return diag.FromErr(fmt.Errorf("error updating App Runner Connection (%s) tags: %w", d.Get("arn").(string), err)) + } + } + + return resourceAwsAppRunnerConnectionRead(ctx, d, meta) +} + +func resourceAwsAppRunnerConnectionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + + input := &apprunner.DeleteConnectionInput{ + ConnectionArn: aws.String(d.Get("arn").(string)), + } + + _, err := conn.DeleteConnectionWithContext(ctx, input) + + if err != nil { + if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + return nil + } + return diag.FromErr(fmt.Errorf("error deleting App Runner Connection (%s): %w", d.Id(), err)) + } + + if err := waiter.ConnectionDeleted(ctx, conn, d.Id()); err != nil { + if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + return nil + } + return diag.FromErr(fmt.Errorf("error waiting for App Runner Connection (%s) deletion: %w", d.Id(), err)) + } + + return nil +} diff --git a/aws/resource_aws_apprunner_connection_test.go b/aws/resource_aws_apprunner_connection_test.go new file mode 100644 index 00000000000..ade3149ee24 --- /dev/null +++ b/aws/resource_aws_apprunner_connection_test.go @@ -0,0 +1,273 @@ +package aws + +import ( + "context" + "fmt" + "log" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apprunner" + "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" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/finder" +) + +func init() { + resource.AddTestSweepers("aws_apprunner_connection", &resource.Sweeper{ + Name: "aws_apprunner_connection", + F: testSweepAppRunnerConnections, + }) +} + +func testSweepAppRunnerConnections(region string) error { + client, err := sharedClientForRegion(region) + + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + + conn := client.(*AWSClient).apprunnerconn + sweepResources := make([]*testSweepResource, 0) + ctx := context.Background() + + var errs *multierror.Error + + input := &apprunner.ListConnectionsInput{} + + err = conn.ListConnectionsPagesWithContext(ctx, input, func(page *apprunner.ListConnectionsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, c := range page.ConnectionSummaryList { + if c == nil { + continue + } + + name := aws.StringValue(c.ConnectionName) + + log.Printf("[INFO] Deleting App Runner Connection: %s", name) + + r := resourceAwsAppRunnerConnection() + d := r.Data(nil) + d.SetId(name) + d.Set("arn", c.ConnectionArn) + + sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) + } + + return !lastPage + }) + + if err != nil { + errs = multierror.Append(errs, fmt.Errorf("error listing App Runner Connections: %w", err)) + } + + if err = testSweepResourceOrchestrator(sweepResources); err != nil { + errs = multierror.Append(errs, fmt.Errorf("error sweeping App Runner Connections for %s: %w", region, err)) + } + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping App Runner Connections sweep for %s: %s", region, err) + return nil // In case we have completed some pages, but had errors + } + + return errs.ErrorOrNil() +} + +func TestAccAwsAppRunnerConnection_basic(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_connection.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerConnection_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerConnectionExists(resourceName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`connection/%s/.+`, rName))), + resource.TestCheckResourceAttr(resourceName, "connection_name", rName), + resource.TestCheckResourceAttr(resourceName, "provider_type", apprunner.ProviderTypeGithub), + resource.TestCheckResourceAttr(resourceName, "status", apprunner.ConnectionStatusPendingHandshake), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsAppRunnerConnection_disappears(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_connection.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerConnection_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerConnectionExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsAppRunnerConnection(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAwsAppRunnerConnection_tags(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_connection.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerConnectionConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerConnectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags_all.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAppRunnerConnectionConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerConnectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags_all.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags_all.key2", "value2"), + ), + }, + { + Config: testAccAppRunnerConnectionConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerConnectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags_all.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckAwsAppRunnerConnectionDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_apprunner_connection" { + continue + } + + conn := testAccProvider.Meta().(*AWSClient).apprunnerconn + + connection, err := finder.ConnectionSummaryByName(context.Background(), conn, rs.Primary.ID) + + if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + continue + } + + if err != nil { + return err + } + + if connection != nil { + return fmt.Errorf("App Runner Connection (%s) still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckAwsAppRunnerConnectionExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No App Runner Connection ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).apprunnerconn + + connection, err := finder.ConnectionSummaryByName(context.Background(), conn, rs.Primary.ID) + + if err != nil { + return err + } + + if connection == nil { + return fmt.Errorf("App Runner Connection (%s) not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccAppRunnerConnection_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_apprunner_connection" "test" { + connection_name = %q + provider_type = "GITHUB" +} +`, rName) +} + +func testAccAppRunnerConnectionConfigTags1(rName string, tagKey1 string, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_apprunner_connection" "test" { + connection_name = %[1]q + provider_type = "GITHUB" + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccAppRunnerConnectionConfigTags2(rName string, tagKey1 string, tagValue1 string, tagKey2 string, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_apprunner_connection" "test" { + connection_name = %[1]q + provider_type = "GITHUB" + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/website/docs/r/apprunner_connection.html.markdown b/website/docs/r/apprunner_connection.html.markdown new file mode 100644 index 00000000000..d1f33f805e6 --- /dev/null +++ b/website/docs/r/apprunner_connection.html.markdown @@ -0,0 +1,50 @@ +--- +subcategory: "App Runner" +layout: "aws" +page_title: "AWS: aws_apprunner_connection" +description: |- +Manage an App Runner Connection +--- + +# Resource: aws_apprunner_connection + +Manages an App Runner Connection. + +~> **NOTE:** After creation, you must complete the authentication handshake using the App Runner console. + +## Example Usage + +```terraform +resource "aws_apprunner_connection" "example" { + connection_name = "example" + provider_type = "GITHUB" + + tags = { + Name = "example-apprunner-connection" + } +} +``` + +## Argument Reference + +The following arguments supported: + +* `connection_name` - (Required) Name of the connection. +* `provider_type` - (Required) The source repository provider. Valid values: `GITHUB`. +* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - Name of the connection. +* `status` - The current state of the App Runner connection. When the state is `AVAILABLE`, you can use the connection to create an [`aws_apprunner_service` resource](/docs/providers/aws/r/apprunner_service). +* `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 + +App Runner Connections can be imported by using the `connection_name`, e.g. + +``` +$ terraform import aws_apprunner_connection.example example +``` From a73f88f434e35fa6a102ff611ed0b1b9071cbc5a Mon Sep 17 00:00:00 2001 From: angie pinilla Date: Mon, 17 May 2021 21:35:06 -0400 Subject: [PATCH 04/12] resource/apprunner_custom_domain_association: new resource (#359) * add custom domain association resource * CR/linter updates --- .../service/apprunner/finder/finder.go | 37 ++++ aws/internal/service/apprunner/id.go | 14 ++ aws/internal/service/apprunner/id_test.go | 78 +++++++ .../service/apprunner/waiter/status.go | 16 ++ .../service/apprunner/waiter/waiter.go | 29 +++ aws/provider.go | 1 + ...aws_apprunner_custom_domain_association.go | 203 ++++++++++++++++++ ...pprunner_custom_domain_association_test.go | 158 ++++++++++++++ ...er_custom_domain_association.html.markdown | 56 +++++ 9 files changed, 592 insertions(+) create mode 100644 aws/internal/service/apprunner/id.go create mode 100644 aws/internal/service/apprunner/id_test.go create mode 100644 aws/resource_aws_apprunner_custom_domain_association.go create mode 100644 aws/resource_aws_apprunner_custom_domain_association_test.go create mode 100644 website/docs/r/apprunner_custom_domain_association.html.markdown diff --git a/aws/internal/service/apprunner/finder/finder.go b/aws/internal/service/apprunner/finder/finder.go index 3d5084e7549..8da12ffbc30 100644 --- a/aws/internal/service/apprunner/finder/finder.go +++ b/aws/internal/service/apprunner/finder/finder.go @@ -43,3 +43,40 @@ func ConnectionSummaryByName(ctx context.Context, conn *apprunner.AppRunner, nam return cs, nil } + +func CustomDomain(ctx context.Context, conn *apprunner.AppRunner, domainName, serviceArn string) (*apprunner.CustomDomain, error) { + input := &apprunner.DescribeCustomDomainsInput{ + ServiceArn: aws.String(serviceArn), + } + + var customDomain *apprunner.CustomDomain + + err := conn.DescribeCustomDomainsPagesWithContext(ctx, input, func(page *apprunner.DescribeCustomDomainsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, cd := range page.CustomDomains { + if cd == nil { + continue + } + + if aws.StringValue(cd.DomainName) == domainName { + customDomain = cd + return false + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + if customDomain == nil { + return nil, nil + } + + return customDomain, nil +} diff --git a/aws/internal/service/apprunner/id.go b/aws/internal/service/apprunner/id.go new file mode 100644 index 00000000000..1674c942501 --- /dev/null +++ b/aws/internal/service/apprunner/id.go @@ -0,0 +1,14 @@ +package apprunner + +import ( + "fmt" + "strings" +) + +func CustomDomainAssociationParseID(id string) (string, string, error) { + idParts := strings.Split(id, ",") + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + return "", "", fmt.Errorf("unexpected format of ID (%s), expected domain_name,service_arn", id) + } + return idParts[0], idParts[1], nil +} diff --git a/aws/internal/service/apprunner/id_test.go b/aws/internal/service/apprunner/id_test.go new file mode 100644 index 00000000000..a6ba3b2a995 --- /dev/null +++ b/aws/internal/service/apprunner/id_test.go @@ -0,0 +1,78 @@ +package apprunner_test + +import ( + "fmt" + "testing" + + tfapprunner "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner" +) + +func TestCustomDomainAssociationParseID(t *testing.T) { + testCases := []struct { + TestName string + InputID string + ExpectedError bool + ExpectedPart0 string + ExpectedPart1 string + }{ + { + TestName: "empty ID", + InputID: "", + ExpectedError: true, + }, + { + TestName: "single part", + InputID: "example.com", + ExpectedError: true, + }, + { + TestName: "two parts", + InputID: fmt.Sprintf("%s,%s", "example.com", "arn:aws:apprunner:us-east-1:1234567890:service/example/0a03292a89764e5882c41d8f991c82fe"), //lintignore:AWSAT005 + ExpectedPart0: "example.com", + ExpectedPart1: "arn:aws:apprunner:us-east-1:1234567890:service/example/0a03292a89764e5882c41d8f991c82fe", //lintignore:AWSAT005 + }, + + { + TestName: "empty both parts", + InputID: ",", + ExpectedError: true, + }, + { + TestName: "empty first part", + InputID: ",arn:aws:apprunner:us-east-1:1234567890:service/example/0a03292a89764e5882c41d8f991c82fe", //lintignore:AWSAT005 + ExpectedError: true, + }, + { + TestName: "empty second part", + InputID: "example.com,", + ExpectedError: true, + }, + { + TestName: "three parts", + InputID: "example.com,arn:aws:apprunner:us-east-1:1234567890:service/example/0a03292a89764e5882c41d8f991c82fe,example", //lintignore:AWSAT005 + ExpectedError: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.TestName, func(t *testing.T) { + gotPart0, gotPart1, err := tfapprunner.CustomDomainAssociationParseID(testCase.InputID) + + if err == nil && testCase.ExpectedError { + t.Fatalf("expected error, got no error") + } + + if err != nil && !testCase.ExpectedError { + t.Fatalf("got unexpected error: %s", err) + } + + if gotPart0 != testCase.ExpectedPart0 { + t.Errorf("got part 0 %s, expected %s", gotPart0, testCase.ExpectedPart0) + } + + if gotPart1 != testCase.ExpectedPart1 { + t.Errorf("got part 1 %s, expected %s", gotPart1, testCase.ExpectedPart1) + } + }) + } +} diff --git a/aws/internal/service/apprunner/waiter/status.go b/aws/internal/service/apprunner/waiter/status.go index 887bc248206..1cec177266f 100644 --- a/aws/internal/service/apprunner/waiter/status.go +++ b/aws/internal/service/apprunner/waiter/status.go @@ -43,3 +43,19 @@ func ConnectionStatus(ctx context.Context, conn *apprunner.AppRunner, name strin return c, aws.StringValue(c.Status), nil } } + +func CustomDomainStatus(ctx context.Context, conn *apprunner.AppRunner, domainName, serviceArn string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + customDomain, err := finder.CustomDomain(ctx, conn, domainName, serviceArn) + + if err != nil { + return nil, "", err + } + + if customDomain == nil { + return nil, "", nil + } + + return customDomain, aws.StringValue(customDomain.Status), nil + } +} \ No newline at end of file diff --git a/aws/internal/service/apprunner/waiter/waiter.go b/aws/internal/service/apprunner/waiter/waiter.go index f290103a047..7db4882c36b 100644 --- a/aws/internal/service/apprunner/waiter/waiter.go +++ b/aws/internal/service/apprunner/waiter/waiter.go @@ -16,6 +16,9 @@ const ( AutoScalingConfigurationDeleteTimeout = 2 * time.Minute ConnectionDeleteTimeout = 5 * time.Minute + + CustomDomainAssociationCreateTimeout = 5 * time.Minute + CustomDomainAssociationDeleteTimeout = 5 * time.Minute ) func AutoScalingConfigurationActive(ctx context.Context, conn *apprunner.AppRunner, arn string) error { @@ -56,3 +59,29 @@ func ConnectionDeleted(ctx context.Context, conn *apprunner.AppRunner, name stri return err } + +func CustomDomainAssociationCreated(ctx context.Context, conn *apprunner.AppRunner, domainName, serviceArn string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{apprunner.CustomDomainAssociationStatusCreating}, + Target: []string{apprunner.CustomDomainAssociationStatusPendingCertificateDnsValidation}, + Refresh: CustomDomainStatus(ctx, conn, domainName, serviceArn), + Timeout: CustomDomainAssociationCreateTimeout, + } + + _, err := stateConf.WaitForState() + + return err +} + +func CustomDomainAssociationDeleted(ctx context.Context, conn *apprunner.AppRunner, domainName, serviceArn string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{apprunner.CustomDomainAssociationStatusDeleting}, + Target: []string{}, + Refresh: CustomDomainStatus(ctx, conn, domainName, serviceArn), + Timeout: CustomDomainAssociationDeleteTimeout, + } + + _, err := stateConf.WaitForState() + + return err +} \ No newline at end of file diff --git a/aws/provider.go b/aws/provider.go index 45cff168188..9e250c5844a 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -496,6 +496,7 @@ func Provider() *schema.Provider { "aws_appmesh_virtual_service": resourceAwsAppmeshVirtualService(), "aws_apprunner_auto_scaling_configuration_version": resourceAwsAppRunnerAutoScalingConfigurationVersion(), "aws_apprunner_connection": resourceAwsAppRunnerConnection(), + "aws_apprunner_custom_domain_association": resourceAwsAppRunnerCustomDomainAssociation(), "aws_appsync_api_key": resourceAwsAppsyncApiKey(), "aws_appsync_datasource": resourceAwsAppsyncDatasource(), "aws_appsync_function": resourceAwsAppsyncFunction(), diff --git a/aws/resource_aws_apprunner_custom_domain_association.go b/aws/resource_aws_apprunner_custom_domain_association.go new file mode 100644 index 00000000000..bc8de2e084b --- /dev/null +++ b/aws/resource_aws_apprunner_custom_domain_association.go @@ -0,0 +1,203 @@ +package aws + +import ( + "context" + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apprunner" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + tfapprunner "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/waiter" +) + +func resourceAwsAppRunnerCustomDomainAssociation() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceAwsAppRunnerCustomDomainAssociationCreate, + ReadWithoutTimeout: resourceAwsAppRunnerCustomDomainAssociationRead, + DeleteWithoutTimeout: resourceAwsAppRunnerCustomDomainAssociationDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "certification_validation_records": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + "value": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "dns_target": { + Type: schema.TypeString, + Computed: true, + }, + "domain_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "enable_www_subdomain": { + Type: schema.TypeBool, + Optional: true, + Default: true, + ForceNew: true, + }, + "service_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArn, + }, + }, + } +} + +func resourceAwsAppRunnerCustomDomainAssociationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + + domainName := d.Get("domain_name").(string) + serviceArn := d.Get("service_arn").(string) + + input := &apprunner.AssociateCustomDomainInput{ + DomainName: aws.String(domainName), + EnableWWWSubdomain: aws.Bool(d.Get("enable_www_subdomain").(bool)), + ServiceArn: aws.String(serviceArn), + } + + output, err := conn.AssociateCustomDomainWithContext(ctx, input) + + if err != nil { + return diag.FromErr(fmt.Errorf("error associating App Runner Custom Domain (%s) for Service (%s): %w", domainName, serviceArn, err)) + } + + if output == nil { + return diag.FromErr(fmt.Errorf("error associating App Runner Custom Domain (%s) for Service (%s): empty output", domainName, serviceArn)) + } + + d.SetId(fmt.Sprintf("%s,%s", aws.StringValue(output.CustomDomain.DomainName), aws.StringValue(output.ServiceArn))) + d.Set("dns_target", output.DNSTarget) + + if err := waiter.CustomDomainAssociationCreated(ctx, conn, domainName, serviceArn); err != nil { + return diag.FromErr(fmt.Errorf("error waiting for App Runner Custom Domain Association (%s) creation: %w", d.Id(), err)) + } + + return resourceAwsAppRunnerCustomDomainAssociationRead(ctx, d, meta) +} + +func resourceAwsAppRunnerCustomDomainAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + + domainName, serviceArn, err := tfapprunner.CustomDomainAssociationParseID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + customDomain, err := finder.CustomDomain(ctx, conn, domainName, serviceArn) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] App Runner Custom Domain Association (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if customDomain == nil { + if d.IsNewResource() { + return diag.FromErr(fmt.Errorf("error reading App Runner Custom Domain Associaton (%s): empty output after creation", d.Id())) + } + log.Printf("[WARN] App Runner Custom Domain Association (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err := d.Set("certificate_validation_records", flattenAppRunnerCustomDomainCertificateValidationRecords(customDomain.CertificateValidationRecords)); err != nil { + return diag.FromErr(fmt.Errorf("error setting certificate_validation_records: %w", err)) + } + + d.Set("domain_name", customDomain.DomainName) + d.Set("enable_www_subdomain", customDomain.EnableWWWSubdomain) + d.Set("service_arn", serviceArn) + + return nil +} + +func resourceAwsAppRunnerCustomDomainAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + + domainName, serviceArn, err := tfapprunner.CustomDomainAssociationParseID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + input := &apprunner.DisassociateCustomDomainInput{ + DomainName: aws.String(domainName), + ServiceArn: aws.String(serviceArn), + } + + _, err = conn.DisassociateCustomDomainWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error disassociating App Runner Custom Domain (%s) for Service (%s): %w", domainName, serviceArn, err)) + } + + if err := waiter.CustomDomainAssociationDeleted(ctx, conn, domainName, serviceArn); err != nil { + if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + return nil + } + + return diag.FromErr(fmt.Errorf("error waiting for App Runner Custom Domain Association (%s) deletion: %w", d.Id(), err)) + } + + return nil +} + +func flattenAppRunnerCustomDomainCertificateValidationRecords(records []*apprunner.CertificateValidationRecord) []interface{} { + var results []interface{} + + for _, record := range records { + if record == nil { + continue + } + + m := map[string]interface{}{ + "name": aws.StringValue(record.Name), + "status": aws.StringValue(record.Status), + "type": aws.StringValue(record.Type), + "value": aws.StringValue(record.Value), + } + + results = append(results, m) + } + + return results +} diff --git a/aws/resource_aws_apprunner_custom_domain_association_test.go b/aws/resource_aws_apprunner_custom_domain_association_test.go new file mode 100644 index 00000000000..a4cb95a3ab2 --- /dev/null +++ b/aws/resource_aws_apprunner_custom_domain_association_test.go @@ -0,0 +1,158 @@ +package aws + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/apprunner" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "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" + tfapprunner "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/finder" +) + +func TestAccAwsAppRunnerCustomDomainAssociation_basic(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_custom_domain_association.test" + serviceResourceName := "aws_apprunner_service.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerCustomDomainAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerCustomDomainAssociation_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerCustomDomainAssociationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "certificate_validation_records.#", "2"), + resource.TestCheckResourceAttrSet(resourceName, "dns_target"), + resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), + resource.TestCheckResourceAttr(resourceName, "enable_www_subdomain", "true"), + resource.TestCheckResourceAttr(resourceName, "status", apprunner.CustomDomainAssociationStatusPendingCertificateDnsValidation), + resource.TestCheckResourceAttrPair(resourceName, "service_arn", serviceResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsAppRunnerCustomDomainAssociation_disappears(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_custom_domain_association.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerCustomDomainAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerCustomDomainAssociation_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerCustomDomainAssociationExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsAppRunnerCustomDomainAssociation(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAwsAppRunnerCustomDomainAssociationDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_apprunner_connection" { + continue + } + + conn := testAccProvider.Meta().(*AWSClient).apprunnerconn + + domainName, serviceArn, err := tfapprunner.CustomDomainAssociationParseID(rs.Primary.ID) + + if err != nil { + return err + } + + customDomain, err := finder.CustomDomain(context.Background(), conn, domainName, serviceArn) + + if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + continue + } + + if err != nil { + return err + } + + if customDomain != nil { + return fmt.Errorf("App Runner Custom Domain Association (%s) still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckAwsAppRunnerCustomDomainAssociationExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No App Runner Custom Domain Association ID is set") + } + + domainName, serviceArn, err := tfapprunner.CustomDomainAssociationParseID(rs.Primary.ID) + + if err != nil { + return err + } + + conn := testAccProvider.Meta().(*AWSClient).apprunnerconn + + customDomain, err := finder.CustomDomain(context.Background(), conn, domainName, serviceArn) + + if err != nil { + return err + } + + if customDomain == nil { + return fmt.Errorf("App Runner Custom Domain Association (%s) not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccAppRunnerCustomDomainAssociation_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_apprunner_service" "test" { + service_name = %q + + source_configuration { + auto_deployments_enabled = false + image_repository { + image_configuration { + port = "8080" + } + image_identifier = "public.ecr.aws/a8a7m9a7/test-ecr-public:latest" + image_repository_type = "ECR_PUBLIC" + } + } +} + +resource "aws_apprunner_custom_domain_association" "test" { + domain_name = "hashicorp.com" + service_arn = aws_apprunner_service.test.arn +} +`, rName) +} diff --git a/website/docs/r/apprunner_custom_domain_association.html.markdown b/website/docs/r/apprunner_custom_domain_association.html.markdown new file mode 100644 index 00000000000..ac130d2e50e --- /dev/null +++ b/website/docs/r/apprunner_custom_domain_association.html.markdown @@ -0,0 +1,56 @@ +--- +subcategory: "App Runner" +layout: "aws" +page_title: "AWS: aws_apprunner_custom_domain_association" +description: |- + Manage an App Runner Custom Domain association +--- + +# Resource: aws_apprunner_custom_domain_association + +Manages an App Runner Custom Domain association. + +~> **NOTE:** After creation, you must use the information in the `certification_validation_records` attribute to add CNAME records to your Domain Name System (DNS). For each mapped domain name, add a mapping to the target App Runner subdomain (found in the `dns_target` attribute) and one or more certificate validation records. App Runner then performs DNS validation to verify that you own or control the domain name you associated. App Runner tracks domain validity in a certificate stored in AWS Certificate Manager (ACM). + +## Example Usage + +```terraform +resource "aws_apprunner_custom_domain_association" "example" { + domain_name = "example.com" + service_arn = aws_apprunner_service.example.arn +} +``` + +## Argument Reference + +The following arguments supported: + +* `domain_name` - (Required) The custom domain endpoint to association. Specify a base domain e.g. `example.com` or a subdomain e.g. `subdomain.example.com`. +* `enable_www_subdomain` (Optional) Whether to associate the subdomain with the App Runner service in addition to the base domain. Defaults to `true`. +* `service_arn` - (Required) The ARN of the App Runner service. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The `domain_name` and `service_arn` separated by a comma (`,`). +* `certificate_validation_records` - A set of certificate CNAME records used for this domain name. See [Certificate Validation Records](#certificate-validation-records) below for more details. +* `dns_target` - The App Runner subdomain of the App Runner service. The custom domain name is mapped to this target name. Attribute only available if resource created (not imported) with Terraform. + +### Certificate Validation Records + +The configuration block consists of the following arguments: + +* `name` - The certificate CNAME record name. +* `status` - The current state of the certificate CNAME record validation. It should change to `SUCCESS` after App Runner completes validation with your DNS. +* `type` - The record type, always `CNAME`. +* `value` - The certificate CNAME record value. + +## Import + +App Runner Custom Domain Associations can be imported by using the `domain_name` and `service_arn` separated by a comma (`,`), e.g. + +``` +$ terraform import aws_apprunner_custom_domain_association.example example.com,arn:aws:apprunner:us-east-1:123456789012:service/example- +app/8fe1e10304f84fd2b0df550fe98a71fa +``` From de83fe789304f5c622887dc7d8101e0a1e001098 Mon Sep 17 00:00:00 2001 From: angie pinilla Date: Tue, 18 May 2021 11:42:14 -0400 Subject: [PATCH 05/12] resource/apprunner_service: new resource (#361) * new resource: apprunner_service * cleanup tests and documentation * update timeouts and add PreCheck * additional tests * CR/linter updates * update to new sweeper method usage * linting fix * gofmt * updates after testing service * update auto_scaling_version resource with new defaults --- .../service/apprunner/waiter/status.go | 23 +- .../service/apprunner/waiter/waiter.go | 45 +- aws/provider.go | 1 + ...nner_auto_scaling_configuration_version.go | 28 +- ...auto_scaling_configuration_version_test.go | 14 +- aws/resource_aws_apprunner_connection_test.go | 5 +- aws/resource_aws_apprunner_service.go | 995 ++++++++++++++++++ aws/resource_aws_apprunner_service_test.go | 705 +++++++++++++ .../docs/r/apprunner_connection.html.markdown | 2 +- .../docs/r/apprunner_service.html.markdown | 198 ++++ 10 files changed, 1992 insertions(+), 24 deletions(-) create mode 100644 aws/resource_aws_apprunner_service.go create mode 100644 aws/resource_aws_apprunner_service_test.go create mode 100644 website/docs/r/apprunner_service.html.markdown diff --git a/aws/internal/service/apprunner/waiter/status.go b/aws/internal/service/apprunner/waiter/status.go index 1cec177266f..c88c10debcb 100644 --- a/aws/internal/service/apprunner/waiter/status.go +++ b/aws/internal/service/apprunner/waiter/status.go @@ -6,6 +6,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/apprunner" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/finder" ) func AutoScalingConfigurationStatus(ctx context.Context, conn *apprunner.AppRunner, arn string) resource.StateRefreshFunc { @@ -58,4 +59,24 @@ func CustomDomainStatus(ctx context.Context, conn *apprunner.AppRunner, domainNa return customDomain, aws.StringValue(customDomain.Status), nil } -} \ No newline at end of file +} + +func ServiceStatus(ctx context.Context, conn *apprunner.AppRunner, serviceArn string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + input := &apprunner.DescribeServiceInput{ + ServiceArn: aws.String(serviceArn), + } + + output, err := conn.DescribeServiceWithContext(ctx, input) + + if err != nil { + return nil, "", err + } + + if output == nil || output.Service == nil { + return nil, "", nil + } + + return output.Service, aws.StringValue(output.Service.Status), nil + } +} diff --git a/aws/internal/service/apprunner/waiter/waiter.go b/aws/internal/service/apprunner/waiter/waiter.go index 7db4882c36b..24e7eed4bbd 100644 --- a/aws/internal/service/apprunner/waiter/waiter.go +++ b/aws/internal/service/apprunner/waiter/waiter.go @@ -19,6 +19,10 @@ const ( CustomDomainAssociationCreateTimeout = 5 * time.Minute CustomDomainAssociationDeleteTimeout = 5 * time.Minute + + ServiceCreateTimeout = 20 * time.Minute + ServiceDeleteTimeout = 20 * time.Minute + ServiceUpdateTimeout = 20 * time.Minute ) func AutoScalingConfigurationActive(ctx context.Context, conn *apprunner.AppRunner, arn string) error { @@ -84,4 +88,43 @@ func CustomDomainAssociationDeleted(ctx context.Context, conn *apprunner.AppRunn _, err := stateConf.WaitForState() return err -} \ No newline at end of file +} + +func ServiceCreated(ctx context.Context, conn *apprunner.AppRunner, serviceArn string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{apprunner.ServiceStatusOperationInProgress}, + Target: []string{apprunner.ServiceStatusRunning}, + Refresh: ServiceStatus(ctx, conn, serviceArn), + Timeout: ServiceCreateTimeout, + } + + _, err := stateConf.WaitForState() + + return err +} + +func ServiceUpdated(ctx context.Context, conn *apprunner.AppRunner, serviceArn string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{apprunner.ServiceStatusOperationInProgress}, + Target: []string{apprunner.ServiceStatusRunning}, + Refresh: ServiceStatus(ctx, conn, serviceArn), + Timeout: ServiceUpdateTimeout, + } + + _, err := stateConf.WaitForState() + + return err +} + +func ServiceDeleted(ctx context.Context, conn *apprunner.AppRunner, serviceArn string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{apprunner.ServiceStatusRunning, apprunner.ServiceStatusOperationInProgress}, + Target: []string{apprunner.ServiceStatusDeleted}, + Refresh: ServiceStatus(ctx, conn, serviceArn), + Timeout: ServiceDeleteTimeout, + } + + _, err := stateConf.WaitForState() + + return err +} diff --git a/aws/provider.go b/aws/provider.go index 9e250c5844a..528bb3c3cbd 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -497,6 +497,7 @@ func Provider() *schema.Provider { "aws_apprunner_auto_scaling_configuration_version": resourceAwsAppRunnerAutoScalingConfigurationVersion(), "aws_apprunner_connection": resourceAwsAppRunnerConnection(), "aws_apprunner_custom_domain_association": resourceAwsAppRunnerCustomDomainAssociation(), + "aws_apprunner_service": resourceAwsAppRunnerService(), "aws_appsync_api_key": resourceAwsAppsyncApiKey(), "aws_appsync_datasource": resourceAwsAppsyncDatasource(), "aws_appsync_function": resourceAwsAppsyncFunction(), diff --git a/aws/resource_aws_apprunner_auto_scaling_configuration_version.go b/aws/resource_aws_apprunner_auto_scaling_configuration_version.go index 2eff20282b9..419a182ea3c 100644 --- a/aws/resource_aws_apprunner_auto_scaling_configuration_version.go +++ b/aws/resource_aws_apprunner_auto_scaling_configuration_version.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "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" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/waiter" ) @@ -44,22 +45,25 @@ func resourceAwsAppRunnerAutoScalingConfigurationVersion() *schema.Resource { Computed: true, }, "max_concurrency": { - Type: schema.TypeInt, - Optional: true, - Default: 100, - ForceNew: true, + Type: schema.TypeInt, + Optional: true, + Default: 100, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 200), }, "max_size": { - Type: schema.TypeInt, - Optional: true, - Default: 50, - ForceNew: true, + Type: schema.TypeInt, + Optional: true, + Default: 25, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 25), }, "min_size": { - Type: schema.TypeInt, - Optional: true, - Default: 1, - ForceNew: true, + Type: schema.TypeInt, + Optional: true, + Default: 1, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 25), }, "status": { Type: schema.TypeString, diff --git a/aws/resource_aws_apprunner_auto_scaling_configuration_version_test.go b/aws/resource_aws_apprunner_auto_scaling_configuration_version_test.go index 85f27cf6091..b873d7ef66b 100644 --- a/aws/resource_aws_apprunner_auto_scaling_configuration_version_test.go +++ b/aws/resource_aws_apprunner_auto_scaling_configuration_version_test.go @@ -96,7 +96,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_revision", "1"), resource.TestCheckResourceAttr(resourceName, "latest", "true"), resource.TestCheckResourceAttr(resourceName, "max_concurrency", "100"), - resource.TestCheckResourceAttr(resourceName, "max_size", "50"), + resource.TestCheckResourceAttr(resourceName, "max_size", "25"), resource.TestCheckResourceAttr(resourceName, "min_size", "1"), resource.TestCheckResourceAttr(resourceName, "status", "active"), ), @@ -169,7 +169,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_complex(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_revision", "1"), resource.TestCheckResourceAttr(resourceName, "latest", "true"), resource.TestCheckResourceAttr(resourceName, "max_concurrency", "100"), - resource.TestCheckResourceAttr(resourceName, "max_size", "50"), + resource.TestCheckResourceAttr(resourceName, "max_size", "25"), resource.TestCheckResourceAttr(resourceName, "min_size", "1"), resource.TestCheckResourceAttr(resourceName, "status", "active"), ), @@ -199,7 +199,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_MultipleVersions(t *test resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_revision", "1"), resource.TestCheckResourceAttr(resourceName, "latest", "true"), resource.TestCheckResourceAttr(resourceName, "max_concurrency", "100"), - resource.TestCheckResourceAttr(resourceName, "max_size", "50"), + resource.TestCheckResourceAttr(resourceName, "max_size", "25"), resource.TestCheckResourceAttr(resourceName, "min_size", "1"), resource.TestCheckResourceAttr(resourceName, "status", "active"), testAccMatchResourceAttrRegionalARN(otherResourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`autoscalingconfiguration/%s/2/.+`, rName))), @@ -207,7 +207,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_MultipleVersions(t *test resource.TestCheckResourceAttr(otherResourceName, "auto_scaling_configuration_revision", "2"), resource.TestCheckResourceAttr(otherResourceName, "latest", "true"), resource.TestCheckResourceAttr(otherResourceName, "max_concurrency", "100"), - resource.TestCheckResourceAttr(otherResourceName, "max_size", "50"), + resource.TestCheckResourceAttr(otherResourceName, "max_size", "25"), resource.TestCheckResourceAttr(otherResourceName, "min_size", "1"), resource.TestCheckResourceAttr(otherResourceName, "status", "active"), ), @@ -264,7 +264,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_UpdateMultipleVersions(t resource.TestCheckResourceAttr(resourceName, "auto_scaling_configuration_revision", "1"), resource.TestCheckResourceAttr(resourceName, "latest", "false"), resource.TestCheckResourceAttr(resourceName, "max_concurrency", "100"), - resource.TestCheckResourceAttr(resourceName, "max_size", "50"), + resource.TestCheckResourceAttr(resourceName, "max_size", "25"), resource.TestCheckResourceAttr(resourceName, "min_size", "1"), resource.TestCheckResourceAttr(resourceName, "status", "active"), testAccMatchResourceAttrRegionalARN(otherResourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`autoscalingconfiguration/%s/2/.+`, rName))), @@ -272,7 +272,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_UpdateMultipleVersions(t resource.TestCheckResourceAttr(otherResourceName, "auto_scaling_configuration_revision", "2"), resource.TestCheckResourceAttr(otherResourceName, "latest", "true"), resource.TestCheckResourceAttr(otherResourceName, "max_concurrency", "125"), - resource.TestCheckResourceAttr(otherResourceName, "max_size", "25"), + resource.TestCheckResourceAttr(otherResourceName, "max_size", "20"), resource.TestCheckResourceAttr(otherResourceName, "min_size", "1"), resource.TestCheckResourceAttr(otherResourceName, "status", "active"), ), @@ -460,7 +460,7 @@ resource "aws_apprunner_auto_scaling_configuration_version" "other" { auto_scaling_configuration_name = aws_apprunner_auto_scaling_configuration_version.test.auto_scaling_configuration_name max_concurrency = 125 - max_size = 25 + max_size = 20 } `, rName) } diff --git a/aws/resource_aws_apprunner_connection_test.go b/aws/resource_aws_apprunner_connection_test.go index ade3149ee24..538a237f8ea 100644 --- a/aws/resource_aws_apprunner_connection_test.go +++ b/aws/resource_aws_apprunner_connection_test.go @@ -19,8 +19,9 @@ import ( func init() { resource.AddTestSweepers("aws_apprunner_connection", &resource.Sweeper{ - Name: "aws_apprunner_connection", - F: testSweepAppRunnerConnections, + Name: "aws_apprunner_connection", + F: testSweepAppRunnerConnections, + Dependencies: []string{"aws_apprunner_service"}, }) } diff --git a/aws/resource_aws_apprunner_service.go b/aws/resource_aws_apprunner_service.go new file mode 100644 index 00000000000..6a62c162b4c --- /dev/null +++ b/aws/resource_aws_apprunner_service.go @@ -0,0 +1,995 @@ +package aws + +import ( + "context" + "fmt" + "log" + "regexp" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apprunner" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "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" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/waiter" +) + +func resourceAwsAppRunnerService() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceAwsAppRunnerServiceCreate, + ReadWithoutTimeout: resourceAwsAppRunnerServiceRead, + UpdateWithoutTimeout: resourceAwsAppRunnerServiceUpdate, + DeleteWithoutTimeout: resourceAwsAppRunnerServiceDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "auto_scaling_configuration_arn": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validateArn, + }, + + "encryption_configuration": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kms_key": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArn, + }, + }, + }, + }, + + "health_check_configuration": { + Type: schema.TypeList, + Optional: true, + Computed: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "healthy_threshold": { + Type: schema.TypeInt, + Optional: true, + Default: 1, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 20), + }, + "interval": { + Type: schema.TypeInt, + Optional: true, + Default: 5, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 20), + }, + "path": { + Type: schema.TypeString, + Optional: true, + Default: "/", + ForceNew: true, + ValidateFunc: validation.StringLenBetween(0, 51200), + }, + "protocol": { + Type: schema.TypeString, + Optional: true, + Default: apprunner.HealthCheckProtocolTcp, + ForceNew: true, + ValidateFunc: validation.StringInSlice(apprunner.HealthCheckProtocol_Values(), false), + }, + "timeout": { + Type: schema.TypeInt, + Optional: true, + Default: 2, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 20), + }, + "unhealthy_threshold": { + Type: schema.TypeInt, + Optional: true, + Default: 5, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 20), + }, + }, + }, + }, + + "instance_configuration": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cpu": { + Type: schema.TypeString, + Optional: true, + Default: "1024", + ValidateFunc: validation.StringMatch(regexp.MustCompile(`1024|2048|(1|2) vCPU`), ""), + }, + "instance_role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArn, + }, + "memory": { + Type: schema.TypeString, + Optional: true, + Default: "2048", + ValidateFunc: validation.StringMatch(regexp.MustCompile(`2048|3072|4096|(2|3|4) GB`), ""), + }, + }, + }, + }, + + "service_id": { + Type: schema.TypeString, + Computed: true, + }, + + "service_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "service_url": { + Type: schema.TypeString, + Computed: true, + }, + + "source_configuration": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "authentication_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "access_role_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, + }, + "connection_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, + }, + }, + }, + }, + "auto_deployments_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "code_repository": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "code_configuration": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "code_configuration_values": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "build_command": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 51200), + }, + "port": { + Type: schema.TypeString, + Optional: true, + Default: "8080", + ValidateFunc: validation.StringLenBetween(0, 51200), + }, + "runtime": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(apprunner.Runtime_Values(), false), + }, + "runtime_environment_variables": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 51200), + }, + }, + "start_command": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 51200), + }, + }, + }, + }, + "configuration_source": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(apprunner.ConfigurationSource_Values(), false), + }, + }, + }, + }, + "repository_url": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(0, 51200), + }, + "source_code_version": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(apprunner.SourceCodeVersionType_Values(), false), + }, + "value": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(0, 51200), + }, + }, + }, + }, + }, + }, + ExactlyOneOf: []string{"source_configuration.0.code_repository", "source_configuration.0.image_repository"}, + }, + "image_repository": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "image_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "port": { + Type: schema.TypeString, + Optional: true, + Default: "8080", + ValidateFunc: validation.StringLenBetween(0, 51200), + }, + "runtime_environment_variables": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 51200), + }, + }, + "start_command": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 51200), + }, + }, + }, + }, + "image_identifier": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`([0-9]{12}.dkr.ecr.[a-z\-]+-[0-9]{1}.amazonaws.com\/.*)|(^public\.ecr\.aws\/.+\/.+)`), ""), + }, + "image_repository_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(apprunner.ImageRepositoryType_Values(), false), + }, + }, + }, + ExactlyOneOf: []string{"source_configuration.0.image_repository", "source_configuration.0.code_repository"}, + }, + }, + }, + }, + + "status": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tagsSchema(), + + "tags_all": tagsSchemaComputed(), + }, + + CustomizeDiff: SetTagsDiff, + } +} + +func resourceAwsAppRunnerServiceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) + + serviceName := d.Get("service_name").(string) + + input := &apprunner.CreateServiceInput{ + ServiceName: aws.String(serviceName), + SourceConfiguration: expandAppRunnerServiceSourceConfiguration(d.Get("source_configuration").([]interface{})), + Tags: tags.IgnoreAws().ApprunnerTags(), + } + + if v, ok := d.GetOk("auto_scaling_configuration_arn"); ok { + input.AutoScalingConfigurationArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("encryption_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.EncryptionConfiguration = expandAppRunnerServiceEncryptionConfiguration(v.([]interface{})) + } + + if v, ok := d.GetOk("health_check_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.HealthCheckConfiguration = expandAppRunnerServiceHealthCheckConfiguration(v.([]interface{})) + } + + if v, ok := d.GetOk("instance_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.InstanceConfiguration = expandAppRunnerServiceInstanceConfiguration(v.([]interface{})) + } + + output, err := conn.CreateServiceWithContext(ctx, input) + + if err != nil { + return diag.FromErr(fmt.Errorf("error creating App Runner Service (%s): %w", serviceName, err)) + } + + if output == nil || output.Service == nil { + return diag.FromErr(fmt.Errorf("error creating App Runner Service (%s): empty output", serviceName)) + } + + d.SetId(aws.StringValue(output.Service.ServiceArn)) + + if err := waiter.ServiceCreated(ctx, conn, d.Id()); err != nil { + return diag.FromErr(fmt.Errorf("error waiting for App Runner Service (%s) creation: %w", d.Id(), err)) + } + + return resourceAwsAppRunnerServiceRead(ctx, d, meta) +} + +func resourceAwsAppRunnerServiceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + input := &apprunner.DescribeServiceInput{ + ServiceArn: aws.String(d.Id()), + } + + output, err := conn.DescribeServiceWithContext(ctx, input) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] App Runner Service (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error reading App Runner Service (%s): %w", d.Id(), err)) + } + + if output == nil || output.Service == nil { + return diag.FromErr(fmt.Errorf("error reading App Runner Service (%s): empty output", d.Id())) + } + + if aws.StringValue(output.Service.Status) == apprunner.ServiceStatusDeleted { + if d.IsNewResource() { + return diag.FromErr(fmt.Errorf("error reading App Runner Service (%s): %s after creation", d.Id(), aws.StringValue(output.Service.Status))) + } + log.Printf("[WARN] App Runner Service (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + service := output.Service + arn := aws.StringValue(service.ServiceArn) + + var autoScalingConfigArn string + if service.AutoScalingConfigurationSummary != nil { + autoScalingConfigArn = aws.StringValue(service.AutoScalingConfigurationSummary.AutoScalingConfigurationArn) + } + + d.Set("arn", arn) + d.Set("auto_scaling_configuration_arn", autoScalingConfigArn) + d.Set("service_id", service.ServiceId) + d.Set("service_name", service.ServiceName) + d.Set("service_url", service.ServiceUrl) + d.Set("status", service.Status) + if err := d.Set("encryption_configuration", flattenAppRunnerServiceEncryptionConfiguration(service.EncryptionConfiguration)); err != nil { + return diag.FromErr(fmt.Errorf("error setting encryption_configuration: %w", err)) + } + + if err := d.Set("health_check_configuration", flattenAppRunnerServiceHealthCheckConfiguration(service.HealthCheckConfiguration)); err != nil { + return diag.FromErr(fmt.Errorf("error setting health_check_configuration: %w", err)) + } + + if err := d.Set("instance_configuration", flattenAppRunnerServiceInstanceConfiguration(service.InstanceConfiguration)); err != nil { + return diag.FromErr(fmt.Errorf("error setting instance_configuration: %w", err)) + } + + if err := d.Set("source_configuration", flattenAppRunnerServiceSourceConfiguration(service.SourceConfiguration)); err != nil { + return diag.FromErr(fmt.Errorf("error setting source_configuration: %w", err)) + } + + tags, err := keyvaluetags.ApprunnerListTags(conn, arn) + + if err != nil { + return diag.FromErr(fmt.Errorf("error listing tags for App Runner Service (%s): %s", arn, err)) + } + + tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting tags: %w", err)) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting tags_all: %w", err)) + } + + return nil +} + +func resourceAwsAppRunnerServiceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + + if d.HasChanges( + "auto_scaling_configuration_arn", + "instance_configuration", + "source_configuration", + ) { + input := &apprunner.UpdateServiceInput{ + ServiceArn: aws.String(d.Id()), + } + + if d.HasChange("auto_scaling_configuration_arn") { + input.AutoScalingConfigurationArn = aws.String(d.Get("auto_scaling_configuration_arn").(string)) + } + + if d.HasChange("instance_configuration") { + input.InstanceConfiguration = expandAppRunnerServiceInstanceConfiguration(d.Get("instance_configuration").([]interface{})) + } + + if d.HasChange("source_configuration") { + input.SourceConfiguration = expandAppRunnerServiceSourceConfiguration(d.Get("source_configuration").([]interface{})) + } + + _, err := conn.UpdateServiceWithContext(ctx, input) + + if err != nil { + return diag.FromErr(fmt.Errorf("error updating App Runner Service (%s): %w", d.Id(), err)) + } + + if err := waiter.ServiceUpdated(ctx, conn, d.Id()); err != nil { + return diag.FromErr(fmt.Errorf("error waiting for App Runner Service (%s) to update: %w", d.Id(), err)) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := keyvaluetags.ApprunnerUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return diag.FromErr(fmt.Errorf("error updating App Runner Service (%s) tags: %s", d.Get("arn").(string), err)) + } + } + + return resourceAwsAppRunnerServiceRead(ctx, d, meta) +} + +func resourceAwsAppRunnerServiceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).apprunnerconn + + input := &apprunner.DeleteServiceInput{ + ServiceArn: aws.String(d.Id()), + } + + _, err := conn.DeleteServiceWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error deleting App Runner Service (%s): %w", d.Id(), err)) + } + + if err := waiter.ServiceDeleted(ctx, conn, d.Id()); err != nil { + if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + return nil + } + + return diag.FromErr(fmt.Errorf("error waiting for App Runner Service (%s) deletion: %w", d.Id(), err)) + } + + return nil +} + +func expandAppRunnerServiceEncryptionConfiguration(l []interface{}) *apprunner.EncryptionConfiguration { + if len(l) == 0 || l[0] == nil { + return nil + } + + tfMap, ok := l[0].(map[string]interface{}) + + if !ok { + return nil + } + + result := &apprunner.EncryptionConfiguration{} + + if v, ok := tfMap["kms_key"].(string); ok && v != "" { + result.KmsKey = aws.String(v) + } + + return result +} + +func expandAppRunnerServiceHealthCheckConfiguration(l []interface{}) *apprunner.HealthCheckConfiguration { + if len(l) == 0 || l[0] == nil { + return nil + } + + tfMap, ok := l[0].(map[string]interface{}) + + if !ok { + return nil + } + + result := &apprunner.HealthCheckConfiguration{} + + if v, ok := tfMap["healthy_threshold"].(int); ok { + result.HealthyThreshold = aws.Int64(int64(v)) + } + + if v, ok := tfMap["interval"].(int); ok { + result.Interval = aws.Int64(int64(v)) + } + + if v, ok := tfMap["path"].(string); ok { + result.Path = aws.String(v) + } + + if v, ok := tfMap["protocol"].(string); ok { + result.Protocol = aws.String(v) + } + + if v, ok := tfMap["timeout"].(int); ok { + result.Timeout = aws.Int64(int64(v)) + } + + if v, ok := tfMap["unhealthy_threshold"].(int); ok { + result.UnhealthyThreshold = aws.Int64(int64(v)) + } + + return result +} + +func expandAppRunnerServiceInstanceConfiguration(l []interface{}) *apprunner.InstanceConfiguration { + if len(l) == 0 || l[0] == nil { + return nil + } + + tfMap, ok := l[0].(map[string]interface{}) + + if !ok { + return nil + } + + result := &apprunner.InstanceConfiguration{} + + if v, ok := tfMap["cpu"].(string); ok { + result.Cpu = aws.String(v) + } + + if v, ok := tfMap["instance_role_arn"].(string); ok { + result.InstanceRoleArn = aws.String(v) + } + + if v, ok := tfMap["memory"].(string); ok { + result.Memory = aws.String(v) + } + + return result +} + +func expandAppRunnerServiceSourceConfiguration(l []interface{}) *apprunner.SourceConfiguration { + if len(l) == 0 || l[0] == nil { + return nil + } + + tfMap, ok := l[0].(map[string]interface{}) + + if !ok { + return nil + } + + result := &apprunner.SourceConfiguration{} + + if v, ok := tfMap["authentication_configuration"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + result.AuthenticationConfiguration = expandAppRunnerServiceAuthenticationConfiguration(v) + } + + if v, ok := tfMap["auto_deployments_enabled"].(bool); ok { + result.AutoDeploymentsEnabled = aws.Bool(v) + } + + if v, ok := tfMap["code_repository"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + result.CodeRepository = expandAppRunnerServiceCodeRepository(v) + } + + if v, ok := tfMap["image_repository"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + result.ImageRepository = expandAppRunnerServiceImageRepository(v) + } + + return result +} + +func expandAppRunnerServiceAuthenticationConfiguration(l []interface{}) *apprunner.AuthenticationConfiguration { + if len(l) == 0 || l[0] == nil { + return nil + } + + tfMap, ok := l[0].(map[string]interface{}) + + if !ok { + return nil + } + + result := &apprunner.AuthenticationConfiguration{} + + if v, ok := tfMap["access_role_arn"].(string); ok && v != "" { + result.AccessRoleArn = aws.String(v) + } + + return result +} + +func expandAppRunnerServiceImageConfiguration(l []interface{}) *apprunner.ImageConfiguration { + if len(l) == 0 || l[0] == nil { + return nil + } + + tfMap, ok := l[0].(map[string]interface{}) + + if !ok { + return nil + } + + result := &apprunner.ImageConfiguration{} + + if v, ok := tfMap["port"].(string); ok && v != "" { + result.Port = aws.String(v) + } + + return result +} + +func expandAppRunnerServiceCodeRepository(l []interface{}) *apprunner.CodeRepository { + if len(l) == 0 || l[0] == nil { + return nil + } + + tfMap, ok := l[0].(map[string]interface{}) + + if !ok { + return nil + } + + result := &apprunner.CodeRepository{} + + if v, ok := tfMap["source_code_version"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + result.SourceCodeVersion = expandAppRunnerServiceSourceCodeVersion(v) + } + + if v, ok := tfMap["code_configuration"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + result.CodeConfiguration = expandAppRunnerServiceCodeConfiguration(v) + } + + if v, ok := tfMap["repository_url"].(string); ok && v != "" { + result.RepositoryUrl = aws.String(v) + } + + return result +} + +func expandAppRunnerServiceImageRepository(l []interface{}) *apprunner.ImageRepository { + if len(l) == 0 || l[0] == nil { + return nil + } + + tfMap, ok := l[0].(map[string]interface{}) + + if !ok { + return nil + } + + result := &apprunner.ImageRepository{} + + if v, ok := tfMap["image_configuration"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + result.ImageConfiguration = expandAppRunnerServiceImageConfiguration(v) + } + + if v, ok := tfMap["image_identifier"].(string); ok && v != "" { + result.ImageIdentifier = aws.String(v) + } + + if v, ok := tfMap["image_repository_type"].(string); ok && v != "" { + result.ImageRepositoryType = aws.String(v) + } + + return result +} + +func expandAppRunnerServiceCodeConfiguration(l []interface{}) *apprunner.CodeConfiguration { + if len(l) == 0 || l[0] == nil { + return nil + } + + tfMap, ok := l[0].(map[string]interface{}) + + if !ok { + return nil + } + + result := &apprunner.CodeConfiguration{} + + if v, ok := tfMap["configuration_source"].(string); ok && v != "" { + result.ConfigurationSource = aws.String(v) + } + + if v, ok := tfMap["code_configuration_values"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + result.CodeConfigurationValues = expandAppRunnerServiceCodeConfigurationValues(v) + } + + return result +} + +func expandAppRunnerServiceCodeConfigurationValues(l []interface{}) *apprunner.CodeConfigurationValues { + if len(l) == 0 || l[0] == nil { + return nil + } + + tfMap, ok := l[0].(map[string]interface{}) + + if !ok { + return nil + } + + result := &apprunner.CodeConfigurationValues{} + + if v, ok := tfMap["build_command"].(string); ok && v != "" { + result.BuildCommand = aws.String(v) + } + + if v, ok := tfMap["port"].(string); ok && v != "" { + result.Port = aws.String(v) + } + + if v, ok := tfMap["runtime"].(string); ok && v != "" { + result.Runtime = aws.String(v) + } + + if v, ok := tfMap["start_command"].(string); ok && v != "" { + result.StartCommand = aws.String(v) + } + + return result +} + +func expandAppRunnerServiceSourceCodeVersion(l []interface{}) *apprunner.SourceCodeVersion { + if len(l) == 0 || l[0] == nil { + return nil + } + + tfMap, ok := l[0].(map[string]interface{}) + + if !ok { + return nil + } + + result := &apprunner.SourceCodeVersion{} + + if v, ok := tfMap["type"].(string); ok && v != "" { + result.Type = aws.String(v) + } + + if v, ok := tfMap["value"].(string); ok && v != "" { + result.Value = aws.String(v) + } + + return result +} + +func flattenAppRunnerServiceEncryptionConfiguration(config *apprunner.EncryptionConfiguration) []interface{} { + if config == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "kms_key": aws.StringValue(config.KmsKey), + } + + return []interface{}{m} +} + +func flattenAppRunnerServiceHealthCheckConfiguration(config *apprunner.HealthCheckConfiguration) []interface{} { + if config == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "healthy_threshold": aws.Int64Value(config.HealthyThreshold), + "interval": aws.Int64Value(config.Interval), + "path": aws.StringValue(config.Path), + "protocol": aws.StringValue(config.Protocol), + "timeout": aws.Int64Value(config.Timeout), + "unhealthy_threshold": aws.Int64Value(config.UnhealthyThreshold), + } + + return []interface{}{m} +} + +func flattenAppRunnerServiceInstanceConfiguration(config *apprunner.InstanceConfiguration) []interface{} { + if config == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "cpu": aws.StringValue(config.Cpu), + "instance_role_arn": aws.StringValue(config.InstanceRoleArn), + "memory": aws.StringValue(config.Memory), + } + + return []interface{}{m} +} + +func flattenAppRunnerServiceCodeRepository(r *apprunner.CodeRepository) []interface{} { + if r == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "code_configuration": flattenAppRunnerServiceCodeConfiguration(r.CodeConfiguration), + "repository_url": aws.StringValue(r.RepositoryUrl), + "source_code_version": flattenAppRunnerServiceSourceCodeVersion(r.SourceCodeVersion), + } + + return []interface{}{m} +} + +func flattenAppRunnerServiceCodeConfiguration(config *apprunner.CodeConfiguration) []interface{} { + if config == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "code_configuration_values": flattenAppRunnerServiceCodeConfigurationValues(config.CodeConfigurationValues), + "configuration_source": aws.StringValue(config.ConfigurationSource), + } + + return []interface{}{m} +} + +func flattenAppRunnerServiceCodeConfigurationValues(values *apprunner.CodeConfigurationValues) []interface{} { + if values == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "build_command": aws.StringValue(values.BuildCommand), + "port": aws.StringValue(values.Port), + "runtime": aws.StringValue(values.Runtime), + "runtime_environment_variables": aws.StringValueMap(values.RuntimeEnvironmentVariables), + "start_command": aws.StringValue(values.StartCommand), + } + + return []interface{}{m} +} + +func flattenAppRunnerServiceSourceCodeVersion(v *apprunner.SourceCodeVersion) []interface{} { + if v == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "type": aws.StringValue(v.Type), + "value": aws.StringValue(v.Value), + } + + return []interface{}{m} +} + +func flattenAppRunnerServiceSourceConfiguration(config *apprunner.SourceConfiguration) []interface{} { + if config == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "authentication_configuration": flattenAppRunnerServiceAuthenticationConfiguration(config.AuthenticationConfiguration), + "auto_deployments_enabled": aws.BoolValue(config.AutoDeploymentsEnabled), + "code_repository": flattenAppRunnerServiceCodeRepository(config.CodeRepository), + "image_repository": flattenAppRunnerServiceImageRepository(config.ImageRepository), + } + + return []interface{}{m} +} + +func flattenAppRunnerServiceAuthenticationConfiguration(config *apprunner.AuthenticationConfiguration) []interface{} { + if config == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "access_role_arn": aws.StringValue(config.AccessRoleArn), + "connection_arn": aws.StringValue(config.ConnectionArn), + } + + return []interface{}{m} +} + +func flattenAppRunnerServiceImageConfiguration(config *apprunner.ImageConfiguration) []interface{} { + if config == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "port": aws.StringValue(config.Port), + "runtime_environment_variables": aws.StringValueMap(config.RuntimeEnvironmentVariables), + "start_command": aws.StringValue(config.StartCommand), + } + + return []interface{}{m} +} + +func flattenAppRunnerServiceImageRepository(r *apprunner.ImageRepository) []interface{} { + if r == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "image_configuration": flattenAppRunnerServiceImageConfiguration(r.ImageConfiguration), + "image_identifier": aws.StringValue(r.ImageIdentifier), + "image_repository_type": aws.StringValue(r.ImageRepositoryType), + } + + return []interface{}{m} +} diff --git a/aws/resource_aws_apprunner_service_test.go b/aws/resource_aws_apprunner_service_test.go new file mode 100644 index 00000000000..434c3692655 --- /dev/null +++ b/aws/resource_aws_apprunner_service_test.go @@ -0,0 +1,705 @@ +package aws + +import ( + "context" + "fmt" + "log" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apprunner" + "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_apprunner_service", &resource.Sweeper{ + Name: "aws_apprunner_service", + F: testSweepAppRunnerServices, + }) +} + +func testSweepAppRunnerServices(region string) error { + client, err := sharedClientForRegion(region) + + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + + conn := client.(*AWSClient).apprunnerconn + sweepResources := make([]*testSweepResource, 0) + ctx := context.Background() + var errs *multierror.Error + + input := &apprunner.ListServicesInput{} + + err = conn.ListServicesPagesWithContext(ctx, input, func(page *apprunner.ListServicesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, service := range page.ServiceSummaryList { + if service == nil { + continue + } + + arn := aws.StringValue(service.ServiceArn) + + log.Printf("[INFO] Deleting App Runner Service: %s", arn) + + r := resourceAwsAppRunnerService() + d := r.Data(nil) + d.SetId(arn) + + sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) + } + + return !lastPage + }) + + if err != nil { + errs = multierror.Append(errs, fmt.Errorf("error listing App Runner Services: %w", err)) + } + + if err = testSweepResourceOrchestrator(sweepResources); err != nil { + errs = multierror.Append(errs, fmt.Errorf("error sweeping App Runner Services for %s: %w", region, err)) + } + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping App Runner Services sweep for %s: %s", region, err) + return nil // In case we have completed some pages, but had errors + } + + return errs.ErrorOrNil() +} + +func TestAccAwsAppRunnerService_ImageRepository_basic(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_service.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerService_imageRepository(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "service_name", rName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`service/%s/.+`, rName))), + resource.TestCheckResourceAttr(resourceName, "source_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "source_configuration.0.auto_deployments_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "source_configuration.0.image_repository.#", "1"), + resource.TestCheckResourceAttr(resourceName, "source_configuration.0.image_repository.0.image_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "source_configuration.0.image_repository.0.image_configuration.0.port", "8000"), + resource.TestCheckResourceAttr(resourceName, "source_configuration.0.image_repository.0.image_identifier", "public.ecr.aws/jg/hello:latest"), + resource.TestCheckResourceAttr(resourceName, "source_configuration.0.image_repository.0.image_repository_type", apprunner.ImageRepositoryTypeEcrPublic), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsAppRunnerService_ImageRepository_AutoScalingConfiguration(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_service.test" + autoScalingResourceName := "aws_apprunner_auto_scaling_configuration_version.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerService_imageRepository(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + ), + }, + { + Config: testAccAppRunnerService_imageRepository_autoScalingConfiguration(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "auto_scaling_configuration_arn", autoScalingResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsAppRunnerService_ImageRepository_EncryptionConfiguration(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_service.test" + kmsResourceName := "aws_kms_key.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerService_imageRepository_encryptionConfiguration(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "encryption_configuration.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "encryption_configuration.0.kms_key", kmsResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // Test resource recreation; EncryptionConfiguration (or lack thereof) Forces New resource + Config: testAccAppRunnerService_imageRepository(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "encryption_configuration.#", "0"), + ), + }, + }, + }) +} + +func TestAccAwsAppRunnerService_ImageRepository_HealthCheckConfiguration(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_service.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerService_imageRepository_healthCheckConfiguration(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "health_check_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check_configuration.0.healthy_threshold", "2"), + resource.TestCheckResourceAttr(resourceName, "health_check_configuration.0.interval", "5"), + resource.TestCheckResourceAttr(resourceName, "health_check_configuration.0.protocol", apprunner.HealthCheckProtocolTcp), + resource.TestCheckResourceAttr(resourceName, "health_check_configuration.0.timeout", "5"), + resource.TestCheckResourceAttr(resourceName, "health_check_configuration.0.unhealthy_threshold", "5"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // Test resource recreation; HealthConfiguration Forces New resource + Config: testAccAppRunnerService_imageRepository_updateHealthCheckConfiguration(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "health_check_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check_configuration.0.healthy_threshold", "2"), + resource.TestCheckResourceAttr(resourceName, "health_check_configuration.0.interval", "5"), + resource.TestCheckResourceAttr(resourceName, "health_check_configuration.0.protocol", apprunner.HealthCheckProtocolTcp), + resource.TestCheckResourceAttr(resourceName, "health_check_configuration.0.timeout", "10"), + resource.TestCheckResourceAttr(resourceName, "health_check_configuration.0.unhealthy_threshold", "4"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsAppRunnerService_ImageRepository_InstanceConfiguration(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_service.test" + roleResourceName := "aws_iam_role.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerService_imageRepository_instanceConfiguration(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.cpu", "1 vCPU"), + resource.TestCheckResourceAttrPair(resourceName, "instance_configuration.0.instance_role_arn", roleResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.memory", "3 GB"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAppRunnerService_imageRepository_updateInstanceConfiguration(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.cpu", "2 vCPU"), + resource.TestCheckResourceAttrPair(resourceName, "instance_configuration.0.instance_role_arn", roleResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.0.memory", "4096"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAppRunnerService_imageRepository(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_configuration.#", "0"), + ), + }, + }, + }) +} + +func TestAccAwsAppRunnerService_disappears(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_service.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerService_imageRepository(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsAppRunnerService(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAwsAppRunnerService_tags(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_apprunner_service.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAppRunner(t) }, + ErrorCheck: testAccErrorCheck(t, apprunner.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppRunnerServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppRunnerServiceConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAppRunnerServiceConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAppRunnerServiceConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppRunnerServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckAwsAppRunnerServiceDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_apprunner_service" { + continue + } + + conn := testAccProvider.Meta().(*AWSClient).apprunnerconn + + input := &apprunner.DescribeServiceInput{ + ServiceArn: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeServiceWithContext(context.Background(), input) + + if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) { + continue + } + + if err != nil { + return err + } + + if output != nil && output.Service != nil && aws.StringValue(output.Service.Status) != apprunner.ServiceStatusDeleted { + return fmt.Errorf("App Runner Service (%s) still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckAwsAppRunnerServiceExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No App Runner Service ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).apprunnerconn + + input := &apprunner.DescribeServiceInput{ + ServiceArn: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeServiceWithContext(context.Background(), input) + + if err != nil { + return err + } + + if output == nil || output.Service == nil { + return fmt.Errorf("App Runner Service (%s) not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccPreCheckAppRunner(t *testing.T) { + conn := testAccProvider.Meta().(*AWSClient).apprunnerconn + ctx := context.Background() + + input := &apprunner.ListServicesInput{} + + _, err := conn.ListServicesWithContext(ctx, input) + + if testAccPreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccAppRunnerService_imageRepository(rName string) string { + return fmt.Sprintf(` +resource "aws_apprunner_service" "test" { + service_name = %[1]q + source_configuration { + auto_deployments_enabled = false + image_repository { + image_configuration { + port = "8000" + } + image_identifier = "public.ecr.aws/jg/hello:latest" + image_repository_type = "ECR_PUBLIC" + } + } +} +`, rName) +} + +func testAccAppRunnerService_imageRepository_autoScalingConfiguration(rName string) string { + return fmt.Sprintf(` +resource "aws_apprunner_auto_scaling_configuration_version" "test" { + auto_scaling_configuration_name = %[1]q +} + +resource "aws_apprunner_service" "test" { + auto_scaling_configuration_arn = aws_apprunner_auto_scaling_configuration_version.test.arn + + service_name = %[1]q + + source_configuration { + auto_deployments_enabled = false + image_repository { + image_configuration { + port = "8000" + } + image_identifier = "public.ecr.aws/jg/hello:latest" + image_repository_type = "ECR_PUBLIC" + } + } +} +`, rName) +} + +func testAccAppRunnerService_imageRepository_encryptionConfiguration(rName string) string { + return fmt.Sprintf(` +resource "aws_kms_key" "test" { + deletion_window_in_days = 7 + description = %[1]q +} + +resource "aws_apprunner_service" "test" { + service_name = %[1]q + + encryption_configuration { + kms_key = aws_kms_key.test.arn + } + + source_configuration { + auto_deployments_enabled = false + image_repository { + image_configuration { + port = "8000" + } + image_identifier = "public.ecr.aws/jg/hello:latest" + image_repository_type = "ECR_PUBLIC" + } + } +} +`, rName) +} + +func testAccAppRunnerService_imageRepository_healthCheckConfiguration(rName string) string { + return fmt.Sprintf(` +resource "aws_apprunner_service" "test" { + service_name = %[1]q + + health_check_configuration { + healthy_threshold = 2 + timeout = 5 + } + + source_configuration { + auto_deployments_enabled = false + image_repository { + image_configuration { + port = "8000" + } + image_identifier = "public.ecr.aws/jg/hello:latest" + image_repository_type = "ECR_PUBLIC" + } + } +} +`, rName) +} + +func testAccAppRunnerService_imageRepository_updateHealthCheckConfiguration(rName string) string { + return fmt.Sprintf(` +resource "aws_apprunner_service" "test" { + service_name = %[1]q + + health_check_configuration { + healthy_threshold = 2 + timeout = 10 + unhealthy_threshold = 4 + } + + source_configuration { + auto_deployments_enabled = false + image_repository { + image_configuration { + port = "8000" + } + image_identifier = "public.ecr.aws/jg/hello:latest" + image_repository_type = "ECR_PUBLIC" + } + } +} +`, rName) +} + +func testAccAppRunnerIAMRole(rName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = <**Note:** Either `code_repository` or `image_repository` must be specified (but not both). + +* `authentication_configuration` - (Optional) Describes resources needed to authenticate access to some source repositories. See [Authentication Configuration](#authentication-configuration) below for more details. +* `auto_deployments_enabled` - (Optional) Whether continuous integration from the source repository is enabled for the App Runner service. If set to `true`, each repository change (source code commit or new image version) starts a deployment. Defaults to `true`. +* `code_repository` - (Optional) Description of a source code repository. See [Code Repository](#code-repository) below for more details. +* `image_repository` - (Optional) Description of a source image repository. See [Image Repository](#image-repository) below for more details. + +### Authentication Configuration + +The `authentication_configuration` block supports the following arguments: + +* `access_role_arn` - (Optional) ARN of the IAM role that grants the App Runner service access to a source repository. Required for ECR image repositories (but not for ECR Public) +* `connection_arn` - (Optional) ARN of the App Runner connection that enables the App Runner service to connect to a source repository. Required for GitHub code repositories. + +### Code Repository + +The `code_repository` block supports the following arguments: + +* `code_configuration` - (Optional) Configuration for building and running the service from a source code repository. See [Code Configuration](#code-configuration) below for more details. +* `repository_url` - (Required) The location of the repository that contains the source code. +* `source_code_version` - (Required) The version that should be used within the source code repository. See [Source Code Version](#source-code-version) below for more details. + +### Image Repository + +The `image_repository` block supports the following arguments: + +* `image_configuration` - (Optional) Configuration for running the identified image. See [Image Configuration](#image-configuration) below for more details. +* `image_identifier` - (Required) The identifier of an image. For an image in Amazon Elastic Container Registry (Amazon ECR), this is an image name. For the + image name format, see Pulling an image in the Amazon ECR User Guide. +* `image_repository_type` - (Required) The type of the image repository. This reflects the repository provider and whether the repository is private or public. Valid values: `ECR` , `ECR_PUBLIC`. + +### Code Configuration + +The `code_configuration` block supports the following arguments: + +* `code_configuration_values` - (Optional) Basic configuration for building and running the App Runner service. Use this parameter to quickly launch an App Runner service without providing an apprunner.yaml file in the source code repository (or ignoring the file if it exists). See [Code Configuration Values](#code-configuration-values) below for more details. +* `configuration_source` - (Required) The source of the App Runner configuration. Valid values: `REPOSITORY`, `API`. Values are interpreted as follows: + * `REPOSITORY` - App Runner reads configuration values from the apprunner.yaml file in the + source code repository and ignores the CodeConfigurationValues parameter. + * `API` - App Runner uses configuration values provided in the CodeConfigurationValues + parameter and ignores the apprunner.yaml file in the source code repository. + +### Code Configuration Values + +The `code_configuration_values` blocks supports the following arguments: + +* `build_command` - (Optional) The command App Runner runs to build your application. +* `port` - (Optional) The port that your application listens to in the container. Defaults to `"8080"`. +* `runtime` - (Required) A runtime environment type for building and running an App Runner service. Represents a programming language runtime. Valid values: `python3`, `nodejs12`. +* `runtime_environment_variables` - (Optional) Environment variables available to your running App Runner service. A map of key/value pairs. Keys with a prefix of `AWSAPPRUNNER` are reserved for system use and aren't valid. +* `start_command` - (Optional) The command App Runner runs to start your application. + +### Image Configuration + +The `image_configuration` block supports the following arguments: + +* `port` - (Optional) The port that your application listens to in the container. Defaults to `"8080"`. +* `runtime_environment_variables` - (Optional) Environment variables available to your running App Runner service. A map of key/value pairs. Keys with a prefix of `AWSAPPRUNNER` are reserved for system use and aren't valid. +* `start_command` - (Optional) A command App Runner runs to start the application in the source image. If specified, this command overrides the Docker image’s default start command. + +### Source Code Version + +The `source_code_version` block supports the following arguments: + +* `type` - (Required) The type of version identifier. For a git-based repository, branches represent versions. Valid values: `BRANCH`. +* `value`- (Required) A source code version. For a git-based repository, a branch name maps to a specific version. App Runner uses the most recent commit to the branch. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - ARN of the App Runner service. +* `service_id` - An alphanumeric ID that App Runner generated for this service. Unique within the AWS Region. +* `service_url` - A subdomain URL that App Runner generated for this service. You can use this URL to access your service web application. +* `status` - The current state of the App Runner service. +* `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 + +App Runner Services can be imported by using the `arn`, e.g. + +``` +$ terraform import aws_apprunner_service.example arn:aws:apprunner:us-east-1:1234567890:service/example/0a03292a89764e5882c41d8f991c82fe +``` From 162b2c697feea510851df9a4750befa541054205 Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Tue, 18 May 2021 15:52:12 -0400 Subject: [PATCH 06/12] additional updates from acctest errors --- aws/internal/service/apprunner/waiter/status.go | 10 ++++++++++ aws/internal/service/apprunner/waiter/waiter.go | 9 +++------ ...r_auto_scaling_configuration_version_test.go | 17 +++++++++-------- ...e_aws_apprunner_custom_domain_association.go | 7 ++++++- ..._apprunner_custom_domain_association_test.go | 14 ++++++++------ 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/aws/internal/service/apprunner/waiter/status.go b/aws/internal/service/apprunner/waiter/status.go index c88c10debcb..137e205a5c5 100644 --- a/aws/internal/service/apprunner/waiter/status.go +++ b/aws/internal/service/apprunner/waiter/status.go @@ -9,6 +9,16 @@ import ( "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/finder" ) +const ( + AutoScalingConfigurationStatusActive = "active" + AutoScalingConfigurationStatusInactive = "inactive" + + CustomDomainAssociationStatusActive = "active" + CustomDomainAssociationStatusCreating = "creating" + CustomDomainAssociationStatusDeleting = "deleting" + CustomDomainAssociationStatusPendingCertificateDnsValidation = "pending_certificate_dns_validation" +) + func AutoScalingConfigurationStatus(ctx context.Context, conn *apprunner.AppRunner, arn string) resource.StateRefreshFunc { return func() (interface{}, string, error) { input := &apprunner.DescribeAutoScalingConfigurationInput{ diff --git a/aws/internal/service/apprunner/waiter/waiter.go b/aws/internal/service/apprunner/waiter/waiter.go index 24e7eed4bbd..06e8e93e257 100644 --- a/aws/internal/service/apprunner/waiter/waiter.go +++ b/aws/internal/service/apprunner/waiter/waiter.go @@ -9,9 +9,6 @@ import ( ) const ( - AutoScalingConfigurationStatusActive = "active" - AutoScalingConfigurationStatusInactive = "inactive" - AutoScalingConfigurationCreateTimeout = 2 * time.Minute AutoScalingConfigurationDeleteTimeout = 2 * time.Minute @@ -66,8 +63,8 @@ func ConnectionDeleted(ctx context.Context, conn *apprunner.AppRunner, name stri func CustomDomainAssociationCreated(ctx context.Context, conn *apprunner.AppRunner, domainName, serviceArn string) error { stateConf := &resource.StateChangeConf{ - Pending: []string{apprunner.CustomDomainAssociationStatusCreating}, - Target: []string{apprunner.CustomDomainAssociationStatusPendingCertificateDnsValidation}, + Pending: []string{CustomDomainAssociationStatusCreating}, + Target: []string{CustomDomainAssociationStatusPendingCertificateDnsValidation}, Refresh: CustomDomainStatus(ctx, conn, domainName, serviceArn), Timeout: CustomDomainAssociationCreateTimeout, } @@ -79,7 +76,7 @@ func CustomDomainAssociationCreated(ctx context.Context, conn *apprunner.AppRunn func CustomDomainAssociationDeleted(ctx context.Context, conn *apprunner.AppRunner, domainName, serviceArn string) error { stateConf := &resource.StateChangeConf{ - Pending: []string{apprunner.CustomDomainAssociationStatusDeleting}, + Pending: []string{CustomDomainAssociationStatusActive, CustomDomainAssociationStatusDeleting}, Target: []string{}, Refresh: CustomDomainStatus(ctx, conn, domainName, serviceArn), Timeout: CustomDomainAssociationDeleteTimeout, diff --git a/aws/resource_aws_apprunner_auto_scaling_configuration_version_test.go b/aws/resource_aws_apprunner_auto_scaling_configuration_version_test.go index b873d7ef66b..696f49895a0 100644 --- a/aws/resource_aws_apprunner_auto_scaling_configuration_version_test.go +++ b/aws/resource_aws_apprunner_auto_scaling_configuration_version_test.go @@ -14,6 +14,7 @@ import ( "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/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/waiter" ) func init() { @@ -98,7 +99,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "max_concurrency", "100"), resource.TestCheckResourceAttr(resourceName, "max_size", "25"), resource.TestCheckResourceAttr(resourceName, "min_size", "1"), - resource.TestCheckResourceAttr(resourceName, "status", "active"), + resource.TestCheckResourceAttr(resourceName, "status", waiter.AutoScalingConfigurationStatusActive), ), }, { @@ -131,7 +132,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_complex(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "max_concurrency", "50"), resource.TestCheckResourceAttr(resourceName, "max_size", "10"), resource.TestCheckResourceAttr(resourceName, "min_size", "2"), - resource.TestCheckResourceAttr(resourceName, "status", "active"), + resource.TestCheckResourceAttr(resourceName, "status", waiter.AutoScalingConfigurationStatusActive), ), }, { @@ -151,7 +152,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_complex(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "max_concurrency", "150"), resource.TestCheckResourceAttr(resourceName, "max_size", "20"), resource.TestCheckResourceAttr(resourceName, "min_size", "5"), - resource.TestCheckResourceAttr(resourceName, "status", "active"), + resource.TestCheckResourceAttr(resourceName, "status", waiter.AutoScalingConfigurationStatusActive), ), }, { @@ -171,7 +172,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_complex(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "max_concurrency", "100"), resource.TestCheckResourceAttr(resourceName, "max_size", "25"), resource.TestCheckResourceAttr(resourceName, "min_size", "1"), - resource.TestCheckResourceAttr(resourceName, "status", "active"), + resource.TestCheckResourceAttr(resourceName, "status", waiter.AutoScalingConfigurationStatusActive), ), }, }, @@ -201,7 +202,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_MultipleVersions(t *test resource.TestCheckResourceAttr(resourceName, "max_concurrency", "100"), resource.TestCheckResourceAttr(resourceName, "max_size", "25"), resource.TestCheckResourceAttr(resourceName, "min_size", "1"), - resource.TestCheckResourceAttr(resourceName, "status", "active"), + resource.TestCheckResourceAttr(resourceName, "status", waiter.AutoScalingConfigurationStatusActive), testAccMatchResourceAttrRegionalARN(otherResourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`autoscalingconfiguration/%s/2/.+`, rName))), resource.TestCheckResourceAttr(otherResourceName, "auto_scaling_configuration_name", rName), resource.TestCheckResourceAttr(otherResourceName, "auto_scaling_configuration_revision", "2"), @@ -209,7 +210,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_MultipleVersions(t *test resource.TestCheckResourceAttr(otherResourceName, "max_concurrency", "100"), resource.TestCheckResourceAttr(otherResourceName, "max_size", "25"), resource.TestCheckResourceAttr(otherResourceName, "min_size", "1"), - resource.TestCheckResourceAttr(otherResourceName, "status", "active"), + resource.TestCheckResourceAttr(otherResourceName, "status", waiter.AutoScalingConfigurationStatusActive), ), }, { @@ -266,7 +267,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_UpdateMultipleVersions(t resource.TestCheckResourceAttr(resourceName, "max_concurrency", "100"), resource.TestCheckResourceAttr(resourceName, "max_size", "25"), resource.TestCheckResourceAttr(resourceName, "min_size", "1"), - resource.TestCheckResourceAttr(resourceName, "status", "active"), + resource.TestCheckResourceAttr(resourceName, "status", waiter.AutoScalingConfigurationStatusActive), testAccMatchResourceAttrRegionalARN(otherResourceName, "arn", "apprunner", regexp.MustCompile(fmt.Sprintf(`autoscalingconfiguration/%s/2/.+`, rName))), resource.TestCheckResourceAttr(otherResourceName, "auto_scaling_configuration_name", rName), resource.TestCheckResourceAttr(otherResourceName, "auto_scaling_configuration_revision", "2"), @@ -274,7 +275,7 @@ func TestAccAwsAppRunnerAutoScalingConfigurationVersion_UpdateMultipleVersions(t resource.TestCheckResourceAttr(otherResourceName, "max_concurrency", "125"), resource.TestCheckResourceAttr(otherResourceName, "max_size", "20"), resource.TestCheckResourceAttr(otherResourceName, "min_size", "1"), - resource.TestCheckResourceAttr(otherResourceName, "status", "active"), + resource.TestCheckResourceAttr(otherResourceName, "status", waiter.AutoScalingConfigurationStatusActive), ), }, { diff --git a/aws/resource_aws_apprunner_custom_domain_association.go b/aws/resource_aws_apprunner_custom_domain_association.go index bc8de2e084b..59a4063f201 100644 --- a/aws/resource_aws_apprunner_custom_domain_association.go +++ b/aws/resource_aws_apprunner_custom_domain_association.go @@ -27,7 +27,7 @@ func resourceAwsAppRunnerCustomDomainAssociation() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "certification_validation_records": { + "certificate_validation_records": { Type: schema.TypeSet, Computed: true, Elem: &schema.Resource{ @@ -73,6 +73,10 @@ func resourceAwsAppRunnerCustomDomainAssociation() *schema.Resource { ForceNew: true, ValidateFunc: validateArn, }, + "status": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -142,6 +146,7 @@ func resourceAwsAppRunnerCustomDomainAssociationRead(ctx context.Context, d *sch d.Set("domain_name", customDomain.DomainName) d.Set("enable_www_subdomain", customDomain.EnableWWWSubdomain) d.Set("service_arn", serviceArn) + d.Set("status", customDomain.Status) return nil } diff --git a/aws/resource_aws_apprunner_custom_domain_association_test.go b/aws/resource_aws_apprunner_custom_domain_association_test.go index a4cb95a3ab2..72133393ac7 100644 --- a/aws/resource_aws_apprunner_custom_domain_association_test.go +++ b/aws/resource_aws_apprunner_custom_domain_association_test.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" tfapprunner "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/apprunner/waiter" ) func TestAccAwsAppRunnerCustomDomainAssociation_basic(t *testing.T) { @@ -29,18 +30,19 @@ func TestAccAwsAppRunnerCustomDomainAssociation_basic(t *testing.T) { Config: testAccAppRunnerCustomDomainAssociation_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAwsAppRunnerCustomDomainAssociationExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "certificate_validation_records.#", "2"), + resource.TestCheckResourceAttr(resourceName, "certificate_validation_records.#", "3"), resource.TestCheckResourceAttrSet(resourceName, "dns_target"), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), + resource.TestCheckResourceAttr(resourceName, "domain_name", "hashicorp.com"), resource.TestCheckResourceAttr(resourceName, "enable_www_subdomain", "true"), - resource.TestCheckResourceAttr(resourceName, "status", apprunner.CustomDomainAssociationStatusPendingCertificateDnsValidation), + resource.TestCheckResourceAttr(resourceName, "status", waiter.CustomDomainAssociationStatusPendingCertificateDnsValidation), resource.TestCheckResourceAttrPair(resourceName, "service_arn", serviceResourceName, "arn"), ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"dns_target"}, }, }, }) From ff305f3e4c5c3f8f9b29d1499219705f8c876c79 Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Tue, 18 May 2021 15:57:45 -0400 Subject: [PATCH 07/12] format website docs --- .../apprunner_auto_scaling_configuration_version.html.markdown | 2 +- website/docs/r/apprunner_connection.html.markdown | 2 +- .../docs/r/apprunner_custom_domain_association.html.markdown | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/r/apprunner_auto_scaling_configuration_version.html.markdown b/website/docs/r/apprunner_auto_scaling_configuration_version.html.markdown index 81b511427ed..b7d70450b7f 100644 --- a/website/docs/r/apprunner_auto_scaling_configuration_version.html.markdown +++ b/website/docs/r/apprunner_auto_scaling_configuration_version.html.markdown @@ -3,7 +3,7 @@ subcategory: "App Runner" layout: "aws" page_title: "AWS: aws_apprunner_auto_scaling_configuration_version" description: |- - Manage an App Runner AutoScaling Configuration Version. + Manages an App Runner AutoScaling Configuration Version. --- # Resource: aws_apprunner_auto_scaling_configuration_version diff --git a/website/docs/r/apprunner_connection.html.markdown b/website/docs/r/apprunner_connection.html.markdown index 4510533a53b..e37933aed3d 100644 --- a/website/docs/r/apprunner_connection.html.markdown +++ b/website/docs/r/apprunner_connection.html.markdown @@ -3,7 +3,7 @@ subcategory: "App Runner" layout: "aws" page_title: "AWS: aws_apprunner_connection" description: |- -Manage an App Runner Connection + Manages an App Runner Connection. --- # Resource: aws_apprunner_connection diff --git a/website/docs/r/apprunner_custom_domain_association.html.markdown b/website/docs/r/apprunner_custom_domain_association.html.markdown index ac130d2e50e..c92b8f1bace 100644 --- a/website/docs/r/apprunner_custom_domain_association.html.markdown +++ b/website/docs/r/apprunner_custom_domain_association.html.markdown @@ -3,7 +3,7 @@ subcategory: "App Runner" layout: "aws" page_title: "AWS: aws_apprunner_custom_domain_association" description: |- - Manage an App Runner Custom Domain association + Manages an App Runner Custom Domain association. --- # Resource: aws_apprunner_custom_domain_association From 8d8711a6e7f96565495a20c4434b507e957d5f82 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 18 May 2021 17:01:04 -0700 Subject: [PATCH 08/12] Adds CHANGELOG --- .changelog/19432.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .changelog/19432.txt diff --git a/.changelog/19432.txt b/.changelog/19432.txt new file mode 100644 index 00000000000..446155c940a --- /dev/null +++ b/.changelog/19432.txt @@ -0,0 +1,15 @@ +```release-note:new-resource +aws_apprunner_auto_scaling_configuration_version +``` + +```release-note:new-resource +aws_apprunner_connection +``` + +```release-note:new-resource +aws_apprunner_custom_domain_association +``` + +```release-note:new-resource +aws_apprunner_service +``` From 6bec8a00580ff0685bd7707cb9400e42ec522173 Mon Sep 17 00:00:00 2001 From: angie pinilla Date: Tue, 18 May 2021 20:06:37 -0400 Subject: [PATCH 09/12] Update website/docs/r/apprunner_connection.html.markdown --- website/docs/r/apprunner_connection.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/apprunner_connection.html.markdown b/website/docs/r/apprunner_connection.html.markdown index e37933aed3d..9652032932d 100644 --- a/website/docs/r/apprunner_connection.html.markdown +++ b/website/docs/r/apprunner_connection.html.markdown @@ -37,7 +37,7 @@ The following arguments supported: In addition to all arguments above, the following attributes are exported: -* `arn` - Name of the connection. +* `arn` - ARN of the connection. * `status` - The current state of the App Runner connection. When the state is `AVAILABLE`, you can use the connection to create an [`aws_apprunner_service` resource](apprunner_service.html). * `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). From f31a3155a9937a06a53e5544b09f0839c246706c Mon Sep 17 00:00:00 2001 From: angie pinilla Date: Tue, 18 May 2021 20:19:25 -0400 Subject: [PATCH 10/12] Update code_configuration to Optional --- aws/resource_aws_apprunner_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_apprunner_service.go b/aws/resource_aws_apprunner_service.go index 6a62c162b4c..cdf2bd1102d 100644 --- a/aws/resource_aws_apprunner_service.go +++ b/aws/resource_aws_apprunner_service.go @@ -193,7 +193,7 @@ func resourceAwsAppRunnerService() *schema.Resource { Schema: map[string]*schema.Schema{ "code_configuration": { Type: schema.TypeList, - Required: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ From 50d3b883f0fdf75b8ba3e45495fa4e48b8f0127a Mon Sep 17 00:00:00 2001 From: angie pinilla Date: Tue, 18 May 2021 20:19:54 -0400 Subject: [PATCH 11/12] Update code_configuration_values to Optional --- aws/resource_aws_apprunner_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_apprunner_service.go b/aws/resource_aws_apprunner_service.go index cdf2bd1102d..e953e03f08f 100644 --- a/aws/resource_aws_apprunner_service.go +++ b/aws/resource_aws_apprunner_service.go @@ -199,7 +199,7 @@ func resourceAwsAppRunnerService() *schema.Resource { Schema: map[string]*schema.Schema{ "code_configuration_values": { Type: schema.TypeList, - Required: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ From 8f8dfed0a83dd0456e54b4d8d1a8b17361108192 Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Tue, 18 May 2021 20:21:13 -0400 Subject: [PATCH 12/12] fix misspelling --- aws/resource_aws_apprunner_custom_domain_association.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_apprunner_custom_domain_association.go b/aws/resource_aws_apprunner_custom_domain_association.go index 59a4063f201..a06c3082534 100644 --- a/aws/resource_aws_apprunner_custom_domain_association.go +++ b/aws/resource_aws_apprunner_custom_domain_association.go @@ -132,7 +132,7 @@ func resourceAwsAppRunnerCustomDomainAssociationRead(ctx context.Context, d *sch if customDomain == nil { if d.IsNewResource() { - return diag.FromErr(fmt.Errorf("error reading App Runner Custom Domain Associaton (%s): empty output after creation", d.Id())) + return diag.FromErr(fmt.Errorf("error reading App Runner Custom Domain Association (%s): empty output after creation", d.Id())) } log.Printf("[WARN] App Runner Custom Domain Association (%s) not found, removing from state", d.Id()) d.SetId("")