diff --git a/.changelog/11936.txt b/.changelog/11936.txt new file mode 100644 index 000000000000..0657ce4442c2 --- /dev/null +++ b/.changelog/11936.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_amplify_backend_environment +``` \ No newline at end of file diff --git a/aws/internal/service/amplify/finder/finder.go b/aws/internal/service/amplify/finder/finder.go index 8f67fbb18f1d..46c91eac8872 100644 --- a/aws/internal/service/amplify/finder/finder.go +++ b/aws/internal/service/amplify/finder/finder.go @@ -34,3 +34,32 @@ func AppByID(conn *amplify.Amplify, id string) (*amplify.App, error) { return output.App, nil } + +func BackendEnvironmentByAppIDAndEnvironmentName(conn *amplify.Amplify, appID, environmentName string) (*amplify.BackendEnvironment, error) { + input := &lify.GetBackendEnvironmentInput{ + AppId: aws.String(appID), + EnvironmentName: aws.String(environmentName), + } + + output, err := conn.GetBackendEnvironment(input) + + if tfawserr.ErrCodeEquals(err, amplify.ErrCodeNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.BackendEnvironment == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.BackendEnvironment, nil +} diff --git a/aws/internal/service/amplify/id.go b/aws/internal/service/amplify/id.go new file mode 100644 index 000000000000..a6c3210b0f25 --- /dev/null +++ b/aws/internal/service/amplify/id.go @@ -0,0 +1,25 @@ +package amplify + +import ( + "fmt" + "strings" +) + +const backendEnvironmentResourceIDSeparator = "/" + +func BackendEnvironmentCreateResourceID(appID, environmentName string) string { + parts := []string{appID, environmentName} + id := strings.Join(parts, backendEnvironmentResourceIDSeparator) + + return id +} + +func BackendEnvironmentParseResourceID(id string) (string, string, error) { + parts := strings.Split(id, backendEnvironmentResourceIDSeparator) + + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { + return parts[0], parts[1], nil + } + + return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected APPID%[2]sENVIRONMENTNAME", id, backendEnvironmentResourceIDSeparator) +} diff --git a/aws/provider.go b/aws/provider.go index a66754c01232..847612d67a57 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -451,6 +451,7 @@ func Provider() *schema.Provider { "aws_ami_from_instance": resourceAwsAmiFromInstance(), "aws_ami_launch_permission": resourceAwsAmiLaunchPermission(), "aws_amplify_app": resourceAwsAmplifyApp(), + "aws_amplify_backend_environment": resourceAwsAmplifyBackendEnvironment(), "aws_api_gateway_account": resourceAwsApiGatewayAccount(), "aws_api_gateway_api_key": resourceAwsApiGatewayApiKey(), "aws_api_gateway_authorizer": resourceAwsApiGatewayAuthorizer(), diff --git a/aws/resource_aws_amplify_backend_environment.go b/aws/resource_aws_amplify_backend_environment.go new file mode 100644 index 000000000000..d6bbc90950f5 --- /dev/null +++ b/aws/resource_aws_amplify_backend_environment.go @@ -0,0 +1,151 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/amplify" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + tfamplify "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/amplify" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/amplify/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func resourceAwsAmplifyBackendEnvironment() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsAmplifyBackendEnvironmentCreate, + Read: resourceAwsAmplifyBackendEnvironmentRead, + Delete: resourceAwsAmplifyBackendEnvironmentDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "app_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "deployment_artifacts": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[0-9A-Za-z-]{1,100}$`), "should be not be more than 100 alphanumeric or hyphen characters"), + }, + + "environment_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[a-z]{2,10}$`), "should be between 2 and 10 characters (only lowercase alphabetic)"), + }, + + "stack_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[0-9A-Za-z-]{1,100}$`), "should be not be more than 100 alphanumeric or hyphen characters"), + }, + }, + } +} + +func resourceAwsAmplifyBackendEnvironmentCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).amplifyconn + + appID := d.Get("app_id").(string) + environmentName := d.Get("environment_name").(string) + id := tfamplify.BackendEnvironmentCreateResourceID(appID, environmentName) + + input := &lify.CreateBackendEnvironmentInput{ + AppId: aws.String(appID), + EnvironmentName: aws.String(environmentName), + } + + if v, ok := d.GetOk("deployment_artifacts"); ok { + input.DeploymentArtifacts = aws.String(v.(string)) + } + + if v, ok := d.GetOk("stack_name"); ok { + input.StackName = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Creating Amplify Backend Environment: %s", input) + _, err := conn.CreateBackendEnvironment(input) + + if err != nil { + return fmt.Errorf("error creating Amplify Backend Environment (%s): %w", id, err) + } + + d.SetId(id) + + return resourceAwsAmplifyBackendEnvironmentRead(d, meta) +} + +func resourceAwsAmplifyBackendEnvironmentRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).amplifyconn + + appID, environmentName, err := tfamplify.BackendEnvironmentParseResourceID(d.Id()) + + if err != nil { + return fmt.Errorf("error parsing Amplify Backend Environment ID: %w", err) + } + + backendEnvironment, err := finder.BackendEnvironmentByAppIDAndEnvironmentName(conn, appID, environmentName) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Amplify Backend Environment (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading Amplify Backend Environment (%s): %w", d.Id(), err) + } + + d.Set("app_id", appID) + d.Set("arn", backendEnvironment.BackendEnvironmentArn) + d.Set("deployment_artifacts", backendEnvironment.DeploymentArtifacts) + d.Set("environment_name", backendEnvironment.EnvironmentName) + d.Set("stack_name", backendEnvironment.StackName) + + return nil +} + +func resourceAwsAmplifyBackendEnvironmentDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).amplifyconn + + appID, environmentName, err := tfamplify.BackendEnvironmentParseResourceID(d.Id()) + + if err != nil { + return fmt.Errorf("error parsing Amplify Backend Environment ID: %w", err) + } + + log.Printf("[DEBUG] Deleting Amplify Backend Environment: %s", d.Id()) + _, err = conn.DeleteBackendEnvironment(&lify.DeleteBackendEnvironmentInput{ + AppId: aws.String(appID), + EnvironmentName: aws.String(environmentName), + }) + + if tfawserr.ErrCodeEquals(err, amplify.ErrCodeNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Amplify Backend Environment (%s): %w", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_amplify_backend_environment_test.go b/aws/resource_aws_amplify_backend_environment_test.go new file mode 100644 index 000000000000..8d3b590463ba --- /dev/null +++ b/aws/resource_aws_amplify_backend_environment_test.go @@ -0,0 +1,196 @@ +package aws + +import ( + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/service/amplify" + "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" + tfamplify "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/amplify" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/amplify/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func testAccAWSAmplifyBackendEnvironment_basic(t *testing.T) { + var env amplify.BackendEnvironment + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_backend_environment.test" + + environmentName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyBackendEnvironmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyBackendEnvironmentConfigBasic(rName, environmentName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyBackendEnvironmentExists(resourceName, &env), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "amplify", regexp.MustCompile(`apps/[^/]+/backendenvironments/.+`)), + resource.TestCheckResourceAttrSet(resourceName, "deployment_artifacts"), + resource.TestCheckResourceAttr(resourceName, "environment_name", environmentName), + resource.TestCheckResourceAttrSet(resourceName, "stack_name"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAWSAmplifyBackendEnvironment_disappears(t *testing.T) { + var env amplify.BackendEnvironment + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_backend_environment.test" + + environmentName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyBackendEnvironmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyBackendEnvironmentConfigBasic(rName, environmentName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyBackendEnvironmentExists(resourceName, &env), + testAccCheckResourceDisappears(testAccProvider, resourceAwsAmplifyBackendEnvironment(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccAWSAmplifyBackendEnvironment_DeploymentArtifacts_StackName(t *testing.T) { + var env amplify.BackendEnvironment + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_backend_environment.test" + + environmentName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyBackendEnvironmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyBackendEnvironmentConfigDeploymentArtifactsAndStackName(rName, environmentName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyBackendEnvironmentExists(resourceName, &env), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "amplify", regexp.MustCompile(`apps/[^/]+/backendenvironments/.+`)), + resource.TestCheckResourceAttr(resourceName, "deployment_artifacts", rName), + resource.TestCheckResourceAttr(resourceName, "environment_name", environmentName), + resource.TestCheckResourceAttr(resourceName, "stack_name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +// testAccAWSAmplifyBackendEnvironmentConfigDeploymentArtifactsAndStackName(rname, environmentName) + +func testAccCheckAWSAmplifyBackendEnvironmentExists(resourceName string, v *amplify.BackendEnvironment) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Amplify Backend Environment ID is set") + } + + appID, environmentName, err := tfamplify.BackendEnvironmentParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + conn := testAccProvider.Meta().(*AWSClient).amplifyconn + + backendEnvironment, err := finder.BackendEnvironmentByAppIDAndEnvironmentName(conn, appID, environmentName) + + if err != nil { + return err + } + + *v = *backendEnvironment + + return nil + } +} + +func testAccCheckAWSAmplifyBackendEnvironmentDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).amplifyconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_amplify_backend_environment" { + continue + } + + appID, environmentName, err := tfamplify.BackendEnvironmentParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = finder.BackendEnvironmentByAppIDAndEnvironmentName(conn, appID, environmentName) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Amplify BackendEnvironment %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccAWSAmplifyBackendEnvironmentConfigBasic(rName string, environmentName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = %[1]q +} + +resource "aws_amplify_backend_environment" "test" { + app_id = aws_amplify_app.test.id + environment_name = %[2]q +} +`, rName, environmentName) +} + +func testAccAWSAmplifyBackendEnvironmentConfigDeploymentArtifactsAndStackName(rName string, environmentName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = %[1]q +} + +resource "aws_amplify_backend_environment" "test" { + app_id = aws_amplify_app.test.id + environment_name = %[2]q + + deployment_artifacts = %[1]q + stack_name = %[1]q +} +`, rName, environmentName) +} diff --git a/aws/resource_aws_amplify_test.go b/aws/resource_aws_amplify_test.go index 4907084c10ed..46f5fab62790 100644 --- a/aws/resource_aws_amplify_test.go +++ b/aws/resource_aws_amplify_test.go @@ -22,6 +22,11 @@ func TestAccAWSAmplify_serial(t *testing.T) { "Name": testAccAWSAmplifyApp_Name, "Repository": testAccAWSAmplifyApp_Repository, }, + "BackendEnvironment": { + "basic": testAccAWSAmplifyBackendEnvironment_basic, + "disappears": testAccAWSAmplifyBackendEnvironment_disappears, + "DeploymentArtifacts_StackName": testAccAWSAmplifyBackendEnvironment_DeploymentArtifacts_StackName, + }, } for group, m := range testCases { diff --git a/website/docs/r/amplify_app.html.markdown b/website/docs/r/amplify_app.html.markdown index 3da86a708933..5847a16116c6 100644 --- a/website/docs/r/amplify_app.html.markdown +++ b/website/docs/r/amplify_app.html.markdown @@ -170,7 +170,7 @@ A `custom_rule` block supports the following arguments: ## Attributes Reference -The following attributes are exported: +In addition to all arguments above, the following attributes are exported: * `arn` - The Amazon Resource Name (ARN) of the Amplify app. * `default_domain` - The default domain for the Amplify app. @@ -190,7 +190,7 @@ A `production_branch` block supports the following attributes: Amplify App can be imported using Amplify App ID (appId), e.g. ``` -$ terraform import aws_amplify_app.app d2ypk4k47z8u6 +$ terraform import aws_amplify_app.example d2ypk4k47z8u6 ``` App ID can be obtained from App ARN (e.g. `arn:aws:amplify:us-east-1:12345678:apps/d2ypk4k47z8u6`). diff --git a/website/docs/r/amplify_backend_environment.html.markdown b/website/docs/r/amplify_backend_environment.html.markdown new file mode 100644 index 000000000000..83a7c1f1d680 --- /dev/null +++ b/website/docs/r/amplify_backend_environment.html.markdown @@ -0,0 +1,51 @@ +--- +subcategory: "Amplify Console" +layout: "aws" +page_title: "AWS: aws_amplify_backend_environment" +description: |- + Provides an Amplify Backend Environment resource. +--- + +# Resource: aws_amplify_backend_environment + +Provides an Amplify Backend Environment resource. + +## Example Usage + +```terraform +resource "aws_amplify_app" "example" { + name = "example" +} + +resource "aws_amplify_backend_environment" "example" { + app_id = aws_amplify_app.example.id + environment_name = "example" + + deployment_artifacts = "app-example-deployment" + stack_name = "amplify-app-example" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `app_id` - (Required) The unique ID for an Amplify app. +* `environment_name` - (Required) The name for the backend environment. +* `deployment_artifacts` - (Optional) The name of deployment artifacts. +* `stack_name` - (Optional) The AWS CloudFormation stack name of a backend environment. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The Amazon Resource Name (ARN) for a backend environment that is part of an Amplify app. +* `id` - The unique ID of the Amplify backend environment. + +## Import + +Amplify backend environment can be imported using `app_id` and `environment_name`, e.g. + +``` +$ terraform import aws_amplify_backend_environment.example d2ypk4k47z8u6/example +```