From d5fa36e432e65d5af5b8fc33b8954a3076c4ed84 Mon Sep 17 00:00:00 2001 From: Keisuke Nishida Date: Thu, 6 Feb 2020 21:49:23 +0900 Subject: [PATCH 01/30] Add aws_amplify_app resource --- aws/provider.go | 1 + aws/resource_aws_amplify_app.go | 647 +++++++++++++++++++ aws/resource_aws_amplify_app_test.go | 783 +++++++++++++++++++++++ website/allowed-subcategories.txt | 1 + website/docs/r/amplify_app.html.markdown | 187 ++++++ 5 files changed, 1619 insertions(+) create mode 100644 aws/resource_aws_amplify_app.go create mode 100644 aws/resource_aws_amplify_app_test.go create mode 100644 website/docs/r/amplify_app.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 9e6ef2c3179..7862d1bf677 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -447,6 +447,7 @@ func Provider() *schema.Provider { "aws_ami_copy": resourceAwsAmiCopy(), "aws_ami_from_instance": resourceAwsAmiFromInstance(), "aws_ami_launch_permission": resourceAwsAmiLaunchPermission(), + "aws_amplify_app": resourceAwsAmplifyApp(), "aws_api_gateway_account": resourceAwsApiGatewayAccount(), "aws_api_gateway_api_key": resourceAwsApiGatewayApiKey(), "aws_api_gateway_authorizer": resourceAwsApiGatewayAuthorizer(), diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go new file mode 100644 index 00000000000..e2aafd320b6 --- /dev/null +++ b/aws/resource_aws_amplify_app.go @@ -0,0 +1,647 @@ +package aws + +import ( + "encoding/base64" + "fmt" + "log" + "regexp" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/amplify" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" +) + +func resourceAwsAmplifyApp() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsAmplifyAppCreate, + Read: resourceAwsAmplifyAppRead, + Update: resourceAwsAmplifyAppUpdate, + Delete: resourceAwsAmplifyAppDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "auto_branch_creation_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "auto_branch_creation_patterns": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "basic_auth_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enable_basic_auth": { + Type: schema.TypeBool, + Optional: true, + }, + "password": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "username": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + }, + }, + }, + "build_spec": { + Type: schema.TypeString, + Optional: true, + }, + "enable_auto_branch_creation": { + Type: schema.TypeBool, + Optional: true, + }, + "enable_auto_build": { + Type: schema.TypeBool, + Optional: true, + }, + "enable_pull_request_preview": { + Type: schema.TypeBool, + Optional: true, + }, + "environment_variables": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "framework": { + Type: schema.TypeString, + Optional: true, + }, + "pull_request_environment_name": { + Type: schema.TypeString, + Optional: true, + }, + "stage": { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // stage is "NONE" by default + if old == "NONE" && new == "" { + return true + } + return false + }, + ValidateFunc: validation.StringInSlice([]string{ + amplify.StageProduction, + amplify.StageBeta, + amplify.StageDevelopment, + amplify.StageExperimental, + amplify.StagePullRequest, + }, false), + }, + }, + }, + }, + "basic_auth_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enable_basic_auth": { + Type: schema.TypeBool, + Optional: true, + }, + "password": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "username": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + }, + }, + }, + "build_spec": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "custom_rules": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "condition": { + Type: schema.TypeString, + Optional: true, + }, + "source": { + Type: schema.TypeString, + Required: true, + }, + "status": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "200", + "301", + "302", + "404", + }, false), + }, + "target": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "default_domain": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "enable_branch_auto_build": { + Type: schema.TypeBool, + Optional: true, + }, + "environment_variables": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "iam_service_role_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(3, 1024), + validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9_-]+$`), "should only contains letters, numbers, _ and -"), + ), + }, + "platform": { + Type: schema.TypeString, + Optional: true, + Default: amplify.PlatformWeb, + ValidateFunc: validation.StringInSlice([]string{ + amplify.PlatformWeb, + }, false), + }, + "repository": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "oauth_token": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "access_token": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "tags": tagsSchema(), + }, + } +} + +func resourceAwsAmplifyAppCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).amplifyconn + log.Print("[DEBUG] Creating Amplify App") + + params := &lify.CreateAppInput{ + Name: aws.String(d.Get("name").(string)), + } + + if v, ok := d.GetOk("auto_branch_creation_config"); ok { + config, patterns, enable := expandAmplifyAutoBranchCreationConfig(v.([]interface{})) + params.AutoBranchCreationConfig = config + params.AutoBranchCreationPatterns = patterns + params.EnableAutoBranchCreation = enable + } + + if v, ok := d.GetOk("basic_auth_config"); ok { + enable, credentials := expandAmplifyBasicAuthConfig(v.([]interface{})) + params.EnableBasicAuth = enable + params.BasicAuthCredentials = credentials + } + + if v, ok := d.GetOk("build_spec"); ok { + params.BuildSpec = aws.String(v.(string)) + } + + if v, ok := d.GetOk("custom_rules"); ok { + params.CustomRules = expandAmplifyCustomRules(v.([]interface{})) + } + + if v, ok := d.GetOk("description"); ok { + params.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("enable_branch_auto_build"); ok { + params.EnableBranchAutoBuild = aws.Bool(v.(bool)) + } + + if v, ok := d.GetOk("environment_variables"); ok { + params.EnvironmentVariables = stringMapToPointers(v.(map[string]interface{})) + } + + if v, ok := d.GetOk("iam_service_role_arn"); ok { + params.IamServiceRoleArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("platform"); ok { + params.Platform = aws.String(v.(string)) + } + + if v, ok := d.GetOk("repository"); ok { + params.Repository = aws.String(v.(string)) + } + + if v, ok := d.GetOk("access_token"); ok { + params.AccessToken = aws.String(v.(string)) + } + + if v, ok := d.GetOk("oauth_token"); ok { + params.OauthToken = aws.String(v.(string)) + } + + if v := d.Get("tags").(map[string]interface{}); len(v) > 0 { + params.Tags = keyvaluetags.New(v).IgnoreAws().AmplifyTags() + } + + resp, err := conn.CreateApp(params) + if err != nil { + return fmt.Errorf("Error creating Amplify App: %s", err) + } + + d.SetId(*resp.App.AppId) + + return resourceAwsAmplifyAppRead(d, meta) +} + +func resourceAwsAmplifyAppRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).amplifyconn + log.Printf("[DEBUG] Reading Amplify App: %s", d.Id()) + + resp, err := conn.GetApp(&lify.GetAppInput{ + AppId: aws.String(d.Id()), + }) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == amplify.ErrCodeNotFoundException { + log.Printf("[WARN] Amplify App (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + return err + } + + d.Set("arn", resp.App.AppArn) + if err := d.Set("auto_branch_creation_config", flattenAmplifyAutoBranchCreationConfig(resp.App.AutoBranchCreationConfig, resp.App.AutoBranchCreationPatterns, resp.App.EnableAutoBranchCreation)); err != nil { + return fmt.Errorf("error setting auto_branch_creation_config: %s", err) + } + if err := d.Set("basic_auth_config", flattenAmplifyBasicAuthConfig(resp.App.EnableBasicAuth, resp.App.BasicAuthCredentials)); err != nil { + return fmt.Errorf("error setting basic_auth_config: %s", err) + } + d.Set("build_spec", resp.App.BuildSpec) + if err := d.Set("custom_rules", flattenAmplifyCustomRules(resp.App.CustomRules)); err != nil { + return fmt.Errorf("error setting custom_rules: %s", err) + } + d.Set("default_domain", resp.App.DefaultDomain) + d.Set("description", resp.App.Description) + d.Set("enable_branch_auto_build", resp.App.EnableBranchAutoBuild) + if err := d.Set("environment_variables", aws.StringValueMap(resp.App.EnvironmentVariables)); err != nil { + return fmt.Errorf("error setting environment_variables: %s", err) + } + d.Set("iam_service_role_arn", resp.App.IamServiceRoleArn) + d.Set("name", resp.App.Name) + d.Set("platform", resp.App.Platform) + d.Set("repository", resp.App.Repository) + if err := d.Set("tags", keyvaluetags.AmplifyKeyValueTags(resp.App.Tags).IgnoreAws().Map()); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + return nil +} + +func resourceAwsAmplifyAppUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).amplifyconn + log.Printf("[DEBUG] Updating Amplify App: %s", d.Id()) + + params := &lify.UpdateAppInput{ + AppId: aws.String(d.Id()), + } + + if d.HasChange("auto_branch_creation_config") { + v := d.Get("auto_branch_creation_config") + config, patterns, enable := expandAmplifyAutoBranchCreationConfig(v.([]interface{})) + params.AutoBranchCreationConfig = config + params.AutoBranchCreationPatterns = patterns + params.EnableAutoBranchCreation = enable + } + + if d.HasChange("basic_auth_config") { + enable, credentials := expandAmplifyBasicAuthConfig(d.Get("basic_auth_config").([]interface{})) + params.EnableBasicAuth = enable + params.BasicAuthCredentials = credentials + } + + if d.HasChange("build_spec") { + params.BuildSpec = aws.String(d.Get("build_spec").(string)) + } + + if d.HasChange("custom_rules") { + params.CustomRules = expandAmplifyCustomRules(d.Get("custom_rules").([]interface{})) + } + + if d.HasChange("description") { + params.Description = aws.String(d.Get("description").(string)) + } + + if d.HasChange("enable_branch_auto_build") { + params.EnableBranchAutoBuild = aws.Bool(d.Get("enable_branch_auto_build").(bool)) + } + + if d.HasChange("environment_variables") { + v := d.Get("environment_variables") + params.EnvironmentVariables = expandAmplifyEnvironmentVariables(v.(map[string]interface{})) + } + + if d.HasChange("iam_service_role_arn") { + params.IamServiceRoleArn = aws.String(d.Get("iam_service_role_arn").(string)) + } + + if d.HasChange("name") { + params.Name = aws.String(d.Get("name").(string)) + } + + if d.HasChange("platform") { + params.Platform = aws.String(d.Get("platform").(string)) + } + + if d.HasChange("repository") { + params.Repository = aws.String(d.Get("repository").(string)) + } + + if v, ok := d.GetOk("access_token"); ok { + params.AccessToken = aws.String(v.(string)) + } + + if v, ok := d.GetOk("oauth_token"); ok { + params.OauthToken = aws.String(v.(string)) + } + + _, err := conn.UpdateApp(params) + if err != nil { + return fmt.Errorf("Error updating Amplify App: %s", err) + } + + if d.HasChange("tags") { + o, n := d.GetChange("tags") + if err := keyvaluetags.AmplifyUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating tags: %s", err) + } + } + + return resourceAwsAmplifyAppRead(d, meta) +} + +func resourceAwsAmplifyAppDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).amplifyconn + log.Printf("[DEBUG] Deleting Amplify App: %s", d.Id()) + + err := deleteAmplifyApp(conn, d.Id()) + if err != nil { + return fmt.Errorf("Error deleting Amplify App: %s", err) + } + + return nil +} + +func deleteAmplifyApp(conn *amplify.Amplify, appId string) error { + params := &lify.DeleteAppInput{ + AppId: aws.String(appId), + } + + _, err := conn.DeleteApp(params) + return err +} + +func expandAmplifyEnvironmentVariables(envs map[string]interface{}) map[string]*string { + if len(envs) == 0 { + empty := "" + return map[string]*string{"": &empty} + } else { + return stringMapToPointers(envs) + } +} + +func expandAmplifyAutoBranchCreationConfig(v []interface{}) (*amplify.AutoBranchCreationConfig, []*string, *bool) { + config := &lify.AutoBranchCreationConfig{} + patterns := make([]*string, 0) + enable := aws.Bool(false) + + if len(v) == 0 { + return config, patterns, enable + } + + e := v[0].(map[string]interface{}) + + if ev, ok := e["auto_branch_creation_patterns"]; ok && len(ev.([]interface{})) > 0 { + patterns = expandStringList(ev.([]interface{})) + } + + if ev, ok := e["basic_auth_config"]; ok { + enable, credentials := expandAmplifyBasicAuthConfig(ev.([]interface{})) + config.EnableBasicAuth = enable + config.BasicAuthCredentials = credentials + } + + if ev, ok := e["build_spec"].(string); ok && ev != "" { + config.BuildSpec = aws.String(ev) + } + + if ev, ok := e["enable_auto_branch_creation"].(bool); ok { + enable = aws.Bool(ev) + } + + if ev, ok := e["enable_auto_build"].(bool); ok { + config.EnableAutoBuild = aws.Bool(ev) + } + + if ev, ok := e["enable_pull_request_preview"].(bool); ok { + config.EnablePullRequestPreview = aws.Bool(ev) + } + + if ev, ok := e["environment_variables"].(map[string]interface{}); ok { + config.EnvironmentVariables = expandAmplifyEnvironmentVariables(ev) + } + + if ev, ok := e["framework"].(string); ok { + config.Framework = aws.String(ev) + } + + if ev, ok := e["pull_request_environment_name"].(string); ok { + config.PullRequestEnvironmentName = aws.String(ev) + } + + if ev, ok := e["stage"].(string); ok { + config.Stage = aws.String(ev) + } + + return config, patterns, enable +} + +func flattenAmplifyAutoBranchCreationConfig(config *amplify.AutoBranchCreationConfig, patterns []*string, enable *bool) []map[string]interface{} { + value := make(map[string]interface{}) + + if !aws.BoolValue(enable) { + return nil + } + + value["enable_auto_branch_creation"] = aws.BoolValue(enable) + value["auto_branch_creation_patterns"] = patterns + + if config != nil { + value["basic_auth_config"] = flattenAmplifyBasicAuthConfig(config.EnableBasicAuth, config.BasicAuthCredentials) + value["build_spec"] = aws.StringValue(config.BuildSpec) + value["enable_auto_build"] = aws.BoolValue(config.EnableAutoBuild) + value["enable_pull_request_preview"] = aws.BoolValue(config.EnablePullRequestPreview) + value["environment_variables"] = aws.StringValueMap(config.EnvironmentVariables) + value["framework"] = aws.StringValue(config.Framework) + value["pull_request_environment_name"] = aws.StringValue(config.PullRequestEnvironmentName) + value["stage"] = aws.StringValue(config.Stage) + } + + return []map[string]interface{}{value} +} + +func expandAmplifyBasicAuthConfig(v []interface{}) (*bool, *string) { + enable := false + credentials := "" + + if len(v) == 0 { + return aws.Bool(enable), aws.String(credentials) + } + + config := v[0].(map[string]interface{}) + + if ev, ok := config["enable_basic_auth"].(bool); ok { + enable = ev + } + + // build basic_auth_credentials from raw username and password + username, ok1 := config["username"].(string) + password, ok2 := config["password"].(string) + if ok1 && ok2 { + credentials = encodeAmplifyBasicAuthCredentials(username, password) + } + + return aws.Bool(enable), aws.String(credentials) +} + +func flattenAmplifyBasicAuthConfig(enableBasicAuth *bool, basicAuthCredentials *string) []map[string]interface{} { + value := make(map[string]interface{}) + + if !aws.BoolValue(enableBasicAuth) { + return nil + } + + value["enable_basic_auth"] = aws.BoolValue(enableBasicAuth) + + if basicAuthCredentials != nil { + // Decode BasicAuthCredentials to username and password + username, password, _ := decodeAmplifyBasicAuthCredentials(aws.StringValue(basicAuthCredentials)) + value["username"] = username + value["password"] = password + } + + return []map[string]interface{}{value} +} + +func encodeAmplifyBasicAuthCredentials(username string, password string) string { + data := fmt.Sprintf("%s:%s", username, password) + return base64.StdEncoding.EncodeToString([]byte(data)) +} + +func decodeAmplifyBasicAuthCredentials(encoded string) (string, string, error) { + data, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return "", "", err + } + s := strings.SplitN(string(data), ":", 2) + return s[0], s[1], nil +} + +func expandAmplifyCustomRules(l []interface{}) []*amplify.CustomRule { + rules := make([]*amplify.CustomRule, 0) + + for _, v := range l { + e := v.(map[string]interface{}) + + rule := &lify.CustomRule{} + + if ev, ok := e["condition"].(string); ok && ev != "" { + rule.Condition = aws.String(ev) + } + + if ev, ok := e["source"].(string); ok { + rule.Source = aws.String(ev) + } + + if ev, ok := e["status"].(string); ok && ev != "" { + rule.Status = aws.String(ev) + } + + if ev, ok := e["target"].(string); ok { + rule.Target = aws.String(ev) + } + + rules = append(rules, rule) + } + + return rules +} + +func flattenAmplifyCustomRules(rules []*amplify.CustomRule) []map[string]interface{} { + values := make([]map[string]interface{}, 0) + + for _, rule := range rules { + value := make(map[string]interface{}) + value["condition"] = aws.StringValue(rule.Condition) + value["source"] = aws.StringValue(rule.Source) + value["status"] = aws.StringValue(rule.Status) + value["target"] = aws.StringValue(rule.Target) + values = append(values, value) + } + + return values +} diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go new file mode 100644 index 00000000000..143531c0fc6 --- /dev/null +++ b/aws/resource_aws_amplify_app_test.go @@ -0,0 +1,783 @@ +package aws + +import ( + "fmt" + "os" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/amplify" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccAWSAmplifyApp_basic(t *testing.T) { + var app amplify.App + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfig_Required(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestMatchResourceAttr(resourceName, "arn", regexp.MustCompile("^arn:[^:]+:amplify:[^:]+:[^:]+:apps/[^/]+$")), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "repository", ""), + resource.TestCheckResourceAttr(resourceName, "platform", "WEB"), + resource.TestMatchResourceAttr(resourceName, "default_domain", regexp.MustCompile(`\.amplifyapp\.com$`)), + resource.TestCheckResourceAttr(resourceName, "build_spec", ""), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "basic_auth_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "custom_rules.#", "0"), + resource.TestCheckResourceAttr(resourceName, "environment_variables.#", "0"), + resource.TestCheckResourceAttr(resourceName, "enable_branch_auto_build", "false"), + resource.TestCheckResourceAttr(resourceName, "iam_service_role_arn", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAmplifyApp_rename(t *testing.T) { + resourceName := "aws_amplify_app.test" + + // name is not unique and can be renamed + rName1 := acctest.RandomWithPrefix("tf-acc-test") + rName2 := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfig_Required(rName1), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", rName1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAmplifyAppConfig_Required(rName2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", rName2), + ), + }, + }, + }) +} + +func TestAccAWSAmplifyApp_description(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + // once set, description cannot be removed. + description1 := acctest.RandomWithPrefix("tf-acc-test") + description2 := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigDescription(rName, description1), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "description", description1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAmplifyAppConfigDescription(rName, description2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "description", description2), + ), + }, + }, + }) +} + +func TestAccAWSAmplifyApp_repository(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigRepository(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr(resourceName, "repository", regexp.MustCompile("^https://github.com")), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"access_token"}, + }, + }, + }) +} + +func TestAccAWSAmplifyApp_buildSpec(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + // once set, build_spec cannot be removed. + buildSpec1 := "version: 0.1" + buildSpec2 := "version: 0.2" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigBuildSpec(rName, buildSpec1), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "build_spec", buildSpec1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAmplifyAppConfigBuildSpec(rName, buildSpec2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "build_spec", buildSpec2), + ), + }, + }, + }) +} + +func TestAccAWSAmplifyApp_customRules(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigCustomRules1(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "custom_rules.#", "1"), + resource.TestCheckResourceAttr(resourceName, "custom_rules.0.source", "/<*>"), + resource.TestCheckResourceAttr(resourceName, "custom_rules.0.status", "404"), + resource.TestCheckResourceAttr(resourceName, "custom_rules.0.target", "/index.html"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAmplifyAppConfigCustomRules2(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "custom_rules.#", "2"), + resource.TestCheckResourceAttr(resourceName, "custom_rules.0.source", "/documents"), + resource.TestCheckResourceAttr(resourceName, "custom_rules.0.status", "302"), + resource.TestCheckResourceAttr(resourceName, "custom_rules.0.target", "/documents/us"), + resource.TestCheckResourceAttr(resourceName, "custom_rules.0.condition", ""), + resource.TestCheckResourceAttr(resourceName, "custom_rules.1.source", "/<*>"), + resource.TestCheckResourceAttr(resourceName, "custom_rules.1.status", "200"), + resource.TestCheckResourceAttr(resourceName, "custom_rules.1.target", "/index.html"), + ), + }, + }, + }) +} + +func TestAccAWSAmplifyApp_environmentVariables(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigEnvironmentVariables1(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "1"), + resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR1", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAmplifyAppConfigEnvironmentVariables2(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "2"), + resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR1", "2"), + resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR2", "2"), + ), + }, + { + Config: testAccAWSAmplifyAppConfig_Required(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "0"), + ), + }, + }, + }) +} + +func TestAccAWSAmplifyApp_autoBranchCreationConfig(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigAutoBranchCreationConfig(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_auto_branch_creation", "true"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.auto_branch_creation_patterns.#", "2"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.auto_branch_creation_patterns.0", "*"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.auto_branch_creation_patterns.1", "*/**"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.build_spec", ""), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.framework", ""), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.stage", "NONE"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_auto_build", "false"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_pull_request_preview", "false"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.pull_request_environment_name", ""), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.environment_variables.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAmplifyAppConfigAutoBranchCreationConfigModified(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_auto_branch_creation", "true"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.auto_branch_creation_patterns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.auto_branch_creation_patterns.0", "feature/*"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.build_spec", "version: 0.1"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.framework", "React"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.stage", "DEVELOPMENT"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_config.0.enable_basic_auth", "true"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_config.0.username", "username"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_config.0.password", "password"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_auto_build", "true"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_pull_request_preview", "true"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.pull_request_environment_name", "env"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.environment_variables.%", "1"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.environment_variables.ENVVAR1", "1"), + ), + }, + { + Config: testAccAWSAmplifyAppConfigAutoBranchCreationConfigModified2(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_auto_branch_creation", "true"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.environment_variables.%", "0"), + ), + }, + { + Config: testAccAWSAmplifyAppConfig_Required(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.#", "0"), + ), + }, + }, + }) +} + +func TestAccAWSAmplifyApp_basicAuthConfig(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + username1 := "username1" + password1 := "password1" + username2 := "username2" + password2 := "password2" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigBasicAuthConfig(rName, username1, password1), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "basic_auth_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "basic_auth_config.0.enable_basic_auth", "true"), + resource.TestCheckResourceAttr(resourceName, "basic_auth_config.0.username", username1), + resource.TestCheckResourceAttr(resourceName, "basic_auth_config.0.password", password1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAmplifyAppConfigBasicAuthConfig(rName, username2, password2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "basic_auth_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "basic_auth_config.0.enable_basic_auth", "true"), + resource.TestCheckResourceAttr(resourceName, "basic_auth_config.0.username", username2), + resource.TestCheckResourceAttr(resourceName, "basic_auth_config.0.password", password2), + ), + }, + { + Config: testAccAWSAmplifyAppConfig_Required(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "basic_auth_config.#", "0"), + ), + }, + }, + }) +} + +func TestAccAWSAmplifyApp_enableBranchAutoBuild(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigEnableBranchAutoBuild(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "enable_branch_auto_build", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAmplifyAppConfig_Required(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "enable_branch_auto_build", "false"), + ), + }, + }, + }) +} + +func TestAccAWSAmplifyApp_iamServiceRoleArn(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + roleName1 := acctest.RandomWithPrefix("tf-acc-test") + roleName2 := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigIAMServiceRoleArn(rName, roleName1), + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr(resourceName, "iam_service_role_arn", regexp.MustCompile("^arn:[^:]+:iam:[^:]*:[^:]+:role/"+roleName1)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAmplifyAppConfigIAMServiceRoleArn(rName, roleName2), + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr(resourceName, "iam_service_role_arn", regexp.MustCompile("^arn:[^:]+:iam:[^:]*:[^:]+:role/"+roleName2)), + ), + }, + }, + }) +} + +func TestAccAWSAmplifyApp_tags(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigTags1(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.TAG1", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAmplifyAppConfigTags2(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.TAG1", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.TAG2", "2"), + ), + }, + { + Config: testAccAWSAmplifyAppConfig_Required(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + }, + }) +} + +func testAccCheckAWSAmplifyAppExists(resourceName string, app *amplify.App) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).amplifyconn + + output, err := conn.GetApp(&lify.GetAppInput{ + AppId: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + + if output == nil || output.App == nil { + return fmt.Errorf("Amplify App (%s) not found", rs.Primary.ID) + } + + *app = *output.App + + return nil + } +} + +func testAccCheckAWSAmplifyAppDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_amplify_app" { + continue + } + + conn := testAccProvider.Meta().(*AWSClient).amplifyconn + + _, err := conn.GetApp(&lify.GetAppInput{ + AppId: aws.String(rs.Primary.ID), + }) + + if isAWSErr(err, amplify.ErrCodeNotFoundException, "") { + continue + } + + if err != nil { + return err + } + } + + return nil +} + +func testAccAWSAmplifyAppConfig_Required(rName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" +} +`, rName) +} + +func testAccAWSAmplifyAppConfigDescription(rName string, description string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" + + description = "%s" +} +`, rName, description) +} + +func testAccAWSAmplifyAppConfigRepository(rName string) string { + repository := os.Getenv("AMPLIFY_GITHUB_REPOSITORY") + accessToken := os.Getenv("AMPLIFY_GITHUB_ACCESS_TOKEN") + + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" + + repository = "%s" + access_token = "%s" +} +`, rName, repository, accessToken) +} + +func testAccAWSAmplifyAppConfigBuildSpec(rName string, buildSpec string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" + + build_spec = "%s" +} +`, rName, buildSpec) +} + +func testAccAWSAmplifyAppConfigCustomRules1(rName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" + + custom_rules { + source = "/<*>" + status = "404" + target = "/index.html" + } +} +`, rName) +} + +func testAccAWSAmplifyAppConfigCustomRules2(rName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" + + custom_rules { + source = "/documents" + status = "302" + target = "/documents/us" + condition = "" + } + + custom_rules { + source = "/<*>" + status = "200" + target = "/index.html" + } +} +`, rName) +} + +func testAccAWSAmplifyAppConfigEnvironmentVariables1(rName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" + + environment_variables = { + ENVVAR1 = "1" + } +} +`, rName) +} + +func testAccAWSAmplifyAppConfigEnvironmentVariables2(rName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" + + environment_variables = { + ENVVAR1 = "2", + ENVVAR2 = "2" + } +} +`, rName) +} + +func testAccAWSAmplifyAppConfigAutoBranchCreationConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" + + auto_branch_creation_config { + enable_auto_branch_creation = true + + auto_branch_creation_patterns = [ + "*", + "*/**", + ] + } +} +`, rName) +} + +func testAccAWSAmplifyAppConfigAutoBranchCreationConfigModified(rName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" + + auto_branch_creation_config { + enable_auto_branch_creation = true + + auto_branch_creation_patterns = [ + "feature/*", + ] + + build_spec = "version: 0.1" + framework = "React" + stage = "DEVELOPMENT" + + basic_auth_config { + enable_basic_auth = true + username = "username" + password = "password" + } + + enable_auto_build = true + + enable_pull_request_preview = true + pull_request_environment_name = "env" + + environment_variables = { + ENVVAR1 = "1" + } + } +} +`, rName) +} + +func testAccAWSAmplifyAppConfigAutoBranchCreationConfigModified2(rName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" + + auto_branch_creation_config { + enable_auto_branch_creation = true + + auto_branch_creation_patterns = [ + "feature/*", + ] + + build_spec = "version: 0.1" + framework = "React" + stage = "DEVELOPMENT" + + enable_auto_build = false + + enable_pull_request_preview = false + pull_request_environment_name = "env" + } +} +`, rName) +} + +func testAccAWSAmplifyAppConfigBasicAuthConfig(rName string, username, password string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" + + basic_auth_config { + enable_basic_auth = true + username = "%s" + password = "%s" + } +} +`, rName, username, password) +} + +func testAccAWSAmplifyAppConfigEnableBranchAutoBuild(rName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" + + enable_branch_auto_build = true +} +`, rName) +} + +func testAccAWSAmplifyAppConfigIAMServiceRoleArn(rName string, roleName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = "%s" + + iam_service_role_arn = aws_iam_role.role.arn +} + +resource "aws_iam_role" "role" { + name = "%s" + + assume_role_policy = < **Note:** When you create/update an Amplify App from Terraform, you may end up with the error "BadRequestException: You should at least provide one valid token" because of authentication issues. See the section "Repository with Tokens" below. + +## Example Usage + +```hcl +resource "aws_amplify_app" "app" { + name = "app" + repository = "https://github.com/example/app" + + // The default build_spec added by the Amplify Console for React. + build_spec = <<-EOT + version: 0.1 + frontend: + phases: + preBuild: + commands: + - yarn install + build: + commands: + - yarn run build + artifacts: + baseDirectory: build + files: + - '**/*' + cache: + paths: + - node_modules/**/* + EOT + + // The default custom_rules added by the Amplify Console. + custom_rules { + source = "/<*>" + status = "404" + target = "/index.html" + } +} +``` + +### Repository with Tokens + +If you create a new Amplify App with the `repository` argument, you also need to set `oauth_token` or `access_token` for authentication. For GitHub, get a [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) and set `access_token` as follows: + +```hcl +resource "aws_amplify_app" "app" { + name = "app" + repository = "https://github.com/example/app" + + // GitHub personal access token + access_token = "..." +``` + +You can omit `access_token` if you import an existing Amplify App created by the Amplify Console (using OAuth for authentication). + +### Auto Branch Creation + +```hcl +resource "aws_amplify_app" "app" { + name = "app" + + auto_branch_creation_config { + // Enable auto branch creation. + enable_auto_branch_creation = true + + // The default patterns added by the Amplify Console. + auto_branch_creation_patterns = [ + "*", + "*/**", + ] + + // Enable auto build for the created branch. + enable_auto_build = true + } +``` + +### Basic Authentication + +```hcl +resource "aws_amplify_app" "app" { + name = "app" + + basic_auth_config { + // Enable basic authentication. + enable_basic_auth = true + + username = "username" + password = "password" + } +} +``` + +### Rewrites and redirects + +```hcl +resource "aws_amplify_app" "app" { + name = "app" + + // Reverse Proxy Rewrite for API requests + // https://docs.aws.amazon.com/amplify/latest/userguide/redirects.html#reverse-proxy-rewrite + custom_rules { + source = "/api/<*>" + status = "200" + target = "https://api.example.com/api/<*>" + } + + // Redirects for Single Page Web Apps (SPA) + // https://docs.aws.amazon.com/amplify/latest/userguide/redirects.html#redirects-for-single-page-web-apps-spa + custom_rules { + source = "" + status = "200" + target = "/index.html" + } +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Name for the Amplify App. +* `access_token` - (Optional) Personal Access token for 3rd party source control system for an Amplify App, used to create webhook and read-only deploy key. Token is not stored. +* `auto_branch_creation_config` - (Optional) Automated branch creation config for the Amplify App. An `auto_branch_creation_config` block is documented below. +* `basic_auth_config` - (Optional) Basic Authentication config for the Amplify App. A `basic_auth_config` block is documented below. +* `build_spec` - (Optional) BuildSpec content for Amplify App. +* `custom_rules` - (Optional) Custom redirect / rewrite rules for the Amplify App. A `custom_rules` block is documented below. +* `description` - (Optional) Description for the Amplify App. +* `enable_branch_auto_build` - (Optional) Enables auto-building of branches for the Amplify App. +* `environment_variables` - (Optional) Environment Variables for the Amplify App. +* `iam_service_role_arn` - (Optional) IAM service role ARN for the Amplify App. +* `oauth_token` - (Optional) OAuth token for 3rd party source control system for an Amplify App, used to create webhook and read-only deploy key. OAuth token is not stored. +* `platform` - (Optional) Platform for the Amplify App. +* `repository` - (Optional) Repository for the Amplify App. +* `tags` - (Optional) Key-value mapping of resource tags. + +An `auto_branch_creation_config` block supports the following arguments: + +* `enable_auto_branch_creation` - (Optional) Enables automated branch creation for the Amplify App. +* `auto_branch_creation_patterns` - (Optional) Automated branch creation glob patterns for the Amplify App. +* `basic_auth_config` - (Optional) Basic Authentication config for the auto created branch. A `basic_auth_config` block is documented below. +* `build_spec` - (Optional) BuildSpec for the auto created branch. +* `enable_auto_build` - (Optional) Enables auto building for the auto created branch. +* `enable_basic_auth` - (Optional) Enables Basic Auth for the auto created branch. +* `enable_pull_request_preview` - (Optional) Enables Pull Request Preview for auto created branch. +* `environment_variables` - (Optional) Environment Variables for the auto created branch. +* `framework` - (Optional) Framework for the auto created branch. +* `pull_request_environment_name` - (Optional) The Amplify Environment name for the pull request. +* `stage` - (Optional) Stage for the branch. Possible values: "PRODUCTION", "BETA", "DEVELOPMENT", "EXPERIMENTAL", or "PULL_REQUEST". + +An `basic_auth_config` block supports the following arguments: + +* `enable_basic_auth` - (Optional) Enables Basic Authorization. +* `username` - (Optional) Basic Authorization username. +* `password` - (Optional) Basic Authorization password. + +A `custom_rules` block supports the following arguments: + +* `source` - (Required) The source pattern for a URL rewrite or redirect rule. +* `target` - (Required) The target pattern for a URL rewrite or redirect rule. +* `condition` - (Optional) The condition for a URL rewrite or redirect rule, e.g. country code. +* `status` - (Optional) The status code for a URL rewrite or redirect rule. + +## Attribute Reference + +The following attributes are exported: + +* `arn` - ARN for the Amplify App. +* `default_domain` - Default domain for the Amplify App. + +## Import + +Amplify App can be imported using Amplify App ID (appId), e.g. + +``` +$ terraform import aws_amplify_app.app d2ypk4k47z8u6 +``` + +App ID can be obtained from App ARN (e.g. `arn:aws:amplify:us-east-1:12345678:apps/d2ypk4k47z8u6`). From 3a88502453e110cd2cab651e8cfee136857dab46 Mon Sep 17 00:00:00 2001 From: Keisuke Nishida Date: Fri, 7 Feb 2020 10:57:11 +0900 Subject: [PATCH 02/30] Ensure that the number of tags is 0 --- aws/resource_aws_amplify_app_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 143531c0fc6..60a3db5d270 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -40,6 +40,7 @@ func TestAccAWSAmplifyApp_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "environment_variables.#", "0"), resource.TestCheckResourceAttr(resourceName, "enable_branch_auto_build", "false"), resource.TestCheckResourceAttr(resourceName, "iam_service_role_arn", ""), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { From c8bd2b38f3734a72ccbd5460e139e349ad62f28a Mon Sep 17 00:00:00 2001 From: Keisuke Nishida Date: Fri, 7 Feb 2020 10:57:52 +0900 Subject: [PATCH 03/30] Add a note why we ignore access_token state --- aws/resource_aws_amplify_app_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 60a3db5d270..0b461ca489d 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -135,9 +135,11 @@ func TestAccAWSAmplifyApp_repository(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + // access_token is ignored because AWS does not store access_token and oauth_token + // See https://docs.aws.amazon.com/sdk-for-go/api/service/amplify/#CreateAppInput ImportStateVerifyIgnore: []string{"access_token"}, }, }, From 0bbbcfc755e5b29c7e6f92b0e0843ddef653398f Mon Sep 17 00:00:00 2001 From: Keisuke Nishida Date: Fri, 13 Mar 2020 22:37:45 +0900 Subject: [PATCH 04/30] Fixes for 'make website-lint' --- website/docs/r/amplify_app.html.markdown | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/website/docs/r/amplify_app.html.markdown b/website/docs/r/amplify_app.html.markdown index 03430406ab3..955d428f83b 100644 --- a/website/docs/r/amplify_app.html.markdown +++ b/website/docs/r/amplify_app.html.markdown @@ -16,7 +16,7 @@ Provides an Amplify App resource, a fullstack serverless app hosted on the [AWS ```hcl resource "aws_amplify_app" "app" { - name = "app" + name = "app" repository = "https://github.com/example/app" // The default build_spec added by the Amplify Console for React. @@ -54,11 +54,12 @@ If you create a new Amplify App with the `repository` argument, you also need to ```hcl resource "aws_amplify_app" "app" { - name = "app" + name = "app" repository = "https://github.com/example/app" // GitHub personal access token access_token = "..." +} ``` You can omit `access_token` if you import an existing Amplify App created by the Amplify Console (using OAuth for authentication). @@ -82,6 +83,7 @@ resource "aws_amplify_app" "app" { // Enable auto build for the created branch. enable_auto_build = true } +} ``` ### Basic Authentication @@ -121,6 +123,7 @@ resource "aws_amplify_app" "app" { status = "200" target = "/index.html" } +} ``` ## Argument Reference From e444048dd9fea4da44e60bf527b55a5bf8f7aed3 Mon Sep 17 00:00:00 2001 From: Ashish Mohite Date: Mon, 2 Nov 2020 12:56:33 +0530 Subject: [PATCH 05/30] [FIX] Upgrade to v2 --- aws/resource_aws_amplify_app.go | 4 ++-- aws/resource_aws_amplify_app_test.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index e2aafd320b6..e6c05abf265 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -10,8 +10,8 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/amplify" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "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" ) diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 0b461ca489d..93099f153f1 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -8,9 +8,9 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/amplify" - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "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 TestAccAWSAmplifyApp_basic(t *testing.T) { From c60fdeb6e2bdb7f3a03658d05bf2554be85c013c Mon Sep 17 00:00:00 2001 From: Ashish Mohite Date: Sat, 7 Nov 2020 11:26:29 +0530 Subject: [PATCH 06/30] Fix linting error --- aws/resource_aws_amplify_app.go | 3 ++- aws/resource_aws_amplify_app_test.go | 27 ++++++++++++++------------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index e6c05abf265..214c613e8fc 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -310,6 +310,7 @@ func resourceAwsAmplifyAppCreate(d *schema.ResourceData, meta interface{}) error func resourceAwsAmplifyAppRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).amplifyconn + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig log.Printf("[DEBUG] Reading Amplify App: %s", d.Id()) resp, err := conn.GetApp(&lify.GetAppInput{ @@ -345,7 +346,7 @@ func resourceAwsAmplifyAppRead(d *schema.ResourceData, meta interface{}) error { d.Set("name", resp.App.Name) d.Set("platform", resp.App.Platform) d.Set("repository", resp.App.Repository) - if err := d.Set("tags", keyvaluetags.AmplifyKeyValueTags(resp.App.Tags).IgnoreAws().Map()); err != nil { + if err := d.Set("tags", keyvaluetags.AmplifyKeyValueTags(resp.App.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { return fmt.Errorf("error setting tags: %s", err) } diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 93099f153f1..1fbc5653634 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -27,7 +27,7 @@ func TestAccAWSAmplifyApp_basic(t *testing.T) { Config: testAccAWSAmplifyAppConfig_Required(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestMatchResourceAttr(resourceName, "arn", regexp.MustCompile("^arn:[^:]+:amplify:[^:]+:[^:]+:apps/[^/]+$")), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "amplify", regexp.MustCompile(`apps/.+`)), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "description", ""), resource.TestCheckResourceAttr(resourceName, "repository", ""), @@ -420,7 +420,7 @@ func TestAccAWSAmplifyApp_iamServiceRoleArn(t *testing.T) { { Config: testAccAWSAmplifyAppConfigIAMServiceRoleArn(rName, roleName1), Check: resource.ComposeTestCheckFunc( - resource.TestMatchResourceAttr(resourceName, "iam_service_role_arn", regexp.MustCompile("^arn:[^:]+:iam:[^:]*:[^:]+:role/"+roleName1)), + testAccMatchResourceAttrGlobalARN(resourceName, "iam_service_role_arn", "iam", regexp.MustCompile("role/"+roleName1)), ), }, { @@ -431,7 +431,7 @@ func TestAccAWSAmplifyApp_iamServiceRoleArn(t *testing.T) { { Config: testAccAWSAmplifyAppConfigIAMServiceRoleArn(rName, roleName2), Check: resource.ComposeTestCheckFunc( - resource.TestMatchResourceAttr(resourceName, "iam_service_role_arn", regexp.MustCompile("^arn:[^:]+:iam:[^:]*:[^:]+:role/"+roleName2)), + testAccMatchResourceAttrGlobalARN(resourceName, "iam_service_role_arn", "iam", regexp.MustCompile("role/"+roleName2)), ), }, }, @@ -637,10 +637,10 @@ resource "aws_amplify_app" "test" { auto_branch_creation_config { enable_auto_branch_creation = true - auto_branch_creation_patterns = [ - "*", - "*/**", - ] + auto_branch_creation_patterns = [ + "*", + "*/**", + ] } } `, rName) @@ -654,9 +654,9 @@ resource "aws_amplify_app" "test" { auto_branch_creation_config { enable_auto_branch_creation = true - auto_branch_creation_patterns = [ - "feature/*", - ] + auto_branch_creation_patterns = [ + "feature/*", + ] build_spec = "version: 0.1" framework = "React" @@ -678,6 +678,7 @@ resource "aws_amplify_app" "test" { } } } + `, rName) } @@ -689,9 +690,9 @@ resource "aws_amplify_app" "test" { auto_branch_creation_config { enable_auto_branch_creation = true - auto_branch_creation_patterns = [ - "feature/*", - ] + auto_branch_creation_patterns = [ + "feature/*", + ] build_spec = "version: 0.1" framework = "React" From 7a3fca3f76ce74c9fef07b207ccbba0611ecddf0 Mon Sep 17 00:00:00 2001 From: Ashish Mohite Date: Sat, 7 Nov 2020 11:50:14 +0530 Subject: [PATCH 07/30] Fix documentation linting --- website/docs/r/amplify_app.html.markdown | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/website/docs/r/amplify_app.html.markdown b/website/docs/r/amplify_app.html.markdown index 955d428f83b..c13f3876816 100644 --- a/website/docs/r/amplify_app.html.markdown +++ b/website/docs/r/amplify_app.html.markdown @@ -19,7 +19,7 @@ resource "aws_amplify_app" "app" { name = "app" repository = "https://github.com/example/app" - // The default build_spec added by the Amplify Console for React. + # The default build_spec added by the Amplify Console for React. build_spec = <<-EOT version: 0.1 frontend: @@ -39,7 +39,7 @@ resource "aws_amplify_app" "app" { - node_modules/**/* EOT - // The default custom_rules added by the Amplify Console. + # The default custom_rules added by the Amplify Console. custom_rules { source = "/<*>" status = "404" @@ -57,7 +57,7 @@ resource "aws_amplify_app" "app" { name = "app" repository = "https://github.com/example/app" - // GitHub personal access token + # GitHub personal access token access_token = "..." } ``` @@ -71,16 +71,16 @@ resource "aws_amplify_app" "app" { name = "app" auto_branch_creation_config { - // Enable auto branch creation. + # Enable auto branch creation. enable_auto_branch_creation = true - // The default patterns added by the Amplify Console. + # The default patterns added by the Amplify Console. auto_branch_creation_patterns = [ "*", "*/**", ] - // Enable auto build for the created branch. + # Enable auto build for the created branch. enable_auto_build = true } } @@ -93,7 +93,7 @@ resource "aws_amplify_app" "app" { name = "app" basic_auth_config { - // Enable basic authentication. + # Enable basic authentication. enable_basic_auth = true username = "username" @@ -108,16 +108,16 @@ resource "aws_amplify_app" "app" { resource "aws_amplify_app" "app" { name = "app" - // Reverse Proxy Rewrite for API requests - // https://docs.aws.amazon.com/amplify/latest/userguide/redirects.html#reverse-proxy-rewrite + # Reverse Proxy Rewrite for API requests + # https://docs.aws.amazon.com/amplify/latest/userguide/redirects.html#reverse-proxy-rewrite custom_rules { source = "/api/<*>" status = "200" target = "https://api.example.com/api/<*>" } - // Redirects for Single Page Web Apps (SPA) - // https://docs.aws.amazon.com/amplify/latest/userguide/redirects.html#redirects-for-single-page-web-apps-spa + # Redirects for Single Page Web Apps (SPA) + # https://docs.aws.amazon.com/amplify/latest/userguide/redirects.html#redirects-for-single-page-web-apps-spa custom_rules { source = "" status = "200" From 153c41132da48fbd7e0ba15eb56b5125fe14a3f1 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 18 May 2021 11:19:36 -0400 Subject: [PATCH 08/30] Add CHANGELOG. --- .changelog/15966.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/15966.txt diff --git a/.changelog/15966.txt b/.changelog/15966.txt new file mode 100644 index 00000000000..feafab97221 --- /dev/null +++ b/.changelog/15966.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_amplify_app +``` \ No newline at end of file From 5754c4a7abbd8728d3c3829bd1b3695f7414f2f8 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 18 May 2021 17:16:50 -0400 Subject: [PATCH 09/30] r/aws_amplify_app: Standard 'name' and 'tags' handling. --- aws/internal/service/amplify/finder/finder.go | 36 ++ aws/resource_aws_amplify_app.go | 586 ++++++++++-------- aws/resource_aws_amplify_app_test.go | 238 ++++--- website/docs/r/amplify_app.html.markdown | 8 +- 4 files changed, 540 insertions(+), 328 deletions(-) create mode 100644 aws/internal/service/amplify/finder/finder.go diff --git a/aws/internal/service/amplify/finder/finder.go b/aws/internal/service/amplify/finder/finder.go new file mode 100644 index 00000000000..142893ee72f --- /dev/null +++ b/aws/internal/service/amplify/finder/finder.go @@ -0,0 +1,36 @@ +package finder + +import ( + "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/resource" +) + +func AppByID(conn *amplify.Amplify, id string) (*amplify.App, error) { + input := &lify.GetAppInput{ + AppId: aws.String(id), + } + + output, err := conn.GetApp(input) + + if tfawserr.ErrCodeEquals(err, amplify.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.App == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.App, nil +} diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index 214c613e8fc..cca1028013e 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -1,18 +1,18 @@ package aws import ( - "encoding/base64" "fmt" "log" - "regexp" - "strings" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "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" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/naming" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/amplify/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func resourceAwsAmplifyApp() *schema.Resource { @@ -25,76 +25,78 @@ func resourceAwsAmplifyApp() *schema.Resource { State: schema.ImportStatePassthrough, }, + CustomizeDiff: SetTagsDiff, + Schema: map[string]*schema.Schema{ + "access_token": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "arn": { Type: schema.TypeString, Computed: true, }, + "auto_branch_creation_config": { Type: schema.TypeList, Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "auto_branch_creation_patterns": { - Type: schema.TypeList, - Optional: true, - MinItems: 1, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "basic_auth_config": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "enable_basic_auth": { - Type: schema.TypeBool, - Optional: true, - }, - "password": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, - ValidateFunc: validation.StringLenBetween(1, 255), - }, - "username": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringLenBetween(1, 255), - }, - }, - }, + "basic_auth_credentials": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringLenBetween(1, 2000), }, + "build_spec": { - Type: schema.TypeString, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 25000), + }, + + "enable_auto_build": { + Type: schema.TypeBool, Optional: true, }, - "enable_auto_branch_creation": { + + "enable_basic_auth": { Type: schema.TypeBool, Optional: true, }, - "enable_auto_build": { + + "enable_performance_mode": { Type: schema.TypeBool, Optional: true, }, + "enable_pull_request_preview": { Type: schema.TypeBool, Optional: true, }, + "environment_variables": { Type: schema.TypeMap, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "framework": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 255), }, + "pull_request_environment_name": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 255), }, + "stage": { Type: schema.TypeString, Optional: true, @@ -103,61 +105,58 @@ func resourceAwsAmplifyApp() *schema.Resource { if old == "NONE" && new == "" { return true } - return false + return old == new }, - ValidateFunc: validation.StringInSlice([]string{ - amplify.StageProduction, - amplify.StageBeta, - amplify.StageDevelopment, - amplify.StageExperimental, - amplify.StagePullRequest, - }, false), + ValidateFunc: validation.StringInSlice(amplify.Stage_Values(), false), }, }, }, }, - "basic_auth_config": { - Type: schema.TypeList, + + "auto_branch_creation_patterns": { + Type: schema.TypeSet, Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "enable_basic_auth": { - Type: schema.TypeBool, - Optional: true, - }, - "password": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, - ValidateFunc: validation.StringLenBetween(1, 255), - }, - "username": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringLenBetween(1, 255), - }, - }, - }, + Elem: &schema.Schema{Type: schema.TypeString}, }, + + "basic_auth_credentials": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringLenBetween(1, 2000), + }, + "build_spec": { Type: schema.TypeString, Optional: true, - Computed: true, + //TODO + //Computed: true, + ValidateFunc: validation.StringLenBetween(1, 25000), }, - "custom_rules": { + + "custom_headers": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 25000), + }, + + "custom_rule": { Type: schema.TypeList, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "condition": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 2048), }, + "source": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 2048), }, + "status": { Type: schema.TypeString, Optional: true, @@ -166,188 +165,271 @@ func resourceAwsAmplifyApp() *schema.Resource { "301", "302", "404", + "404-200", }, false), }, + "target": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 2048), }, }, }, }, + "default_domain": { Type: schema.TypeString, Computed: true, }, + "description": { - Type: schema.TypeString, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 1000), + }, + + "enable_auto_branch_creation": { + Type: schema.TypeBool, Optional: true, }, + + "enable_basic_auth": { + Type: schema.TypeBool, + Optional: true, + }, + "enable_branch_auto_build": { Type: schema.TypeBool, Optional: true, }, + + "enable_branch_auto_deletion": { + Type: schema.TypeBool, + Optional: true, + }, + "environment_variables": { Type: schema.TypeMap, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "iam_service_role_arn": { Type: schema.TypeString, Optional: true, ValidateFunc: validateArn, }, + "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.All( - validation.StringLenBetween(3, 1024), - validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9_-]+$`), "should only contains letters, numbers, _ and -"), - ), + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 255), + ConflictsWith: []string{"name_prefix"}, + }, + + "name_prefix": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"name"}, + }, + + "oauth_token": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringLenBetween(1, 1000), }, + "platform": { Type: schema.TypeString, Optional: true, - Default: amplify.PlatformWeb, - ValidateFunc: validation.StringInSlice([]string{ - amplify.PlatformWeb, - }, false), + //TODO + //Default: amplify.PlatformWeb, + ValidateFunc: validation.StringInSlice(amplify.Platform_Values(), false), }, + + "production_branch": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "branch_name": { + Type: schema.TypeBool, + Computed: true, + }, + + "last_deploy_time": { + Type: schema.TypeString, + Computed: true, + }, + + "status": { + Type: schema.TypeString, + Computed: true, + }, + + "thumbnail_url": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + "repository": { Type: schema.TypeString, Optional: true, - ForceNew: true, + //TODO + //ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 1000), }, - "oauth_token": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, - }, - "access_token": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, - }, - "tags": tagsSchema(), + + "tags": tagsSchema(), + "tags_all": tagsSchemaComputed(), }, } } func resourceAwsAmplifyAppCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).amplifyconn - log.Print("[DEBUG] Creating Amplify App") + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) - params := &lify.CreateAppInput{ - Name: aws.String(d.Get("name").(string)), - } + name := naming.Generate(d.Get("name").(string), d.Get("name_prefix").(string)) - if v, ok := d.GetOk("auto_branch_creation_config"); ok { - config, patterns, enable := expandAmplifyAutoBranchCreationConfig(v.([]interface{})) - params.AutoBranchCreationConfig = config - params.AutoBranchCreationPatterns = patterns - params.EnableAutoBranchCreation = enable + input := &lify.CreateAppInput{ + Name: aws.String(name), } - if v, ok := d.GetOk("basic_auth_config"); ok { - enable, credentials := expandAmplifyBasicAuthConfig(v.([]interface{})) - params.EnableBasicAuth = enable - params.BasicAuthCredentials = credentials - } + /* + if v, ok := d.GetOk("auto_branch_creation_config"); ok { + config, patterns, enable := expandAmplifyAutoBranchCreationConfig(v.([]interface{})) + params.AutoBranchCreationConfig = config + params.AutoBranchCreationPatterns = patterns + params.EnableAutoBranchCreation = enable + } - if v, ok := d.GetOk("build_spec"); ok { - params.BuildSpec = aws.String(v.(string)) - } + if v, ok := d.GetOk("basic_auth_config"); ok { + enable, credentials := expandAmplifyBasicAuthConfig(v.([]interface{})) + params.EnableBasicAuth = enable + params.BasicAuthCredentials = credentials + } - if v, ok := d.GetOk("custom_rules"); ok { - params.CustomRules = expandAmplifyCustomRules(v.([]interface{})) - } + if v, ok := d.GetOk("build_spec"); ok { + params.BuildSpec = aws.String(v.(string)) + } - if v, ok := d.GetOk("description"); ok { - params.Description = aws.String(v.(string)) - } + if v, ok := d.GetOk("custom_rules"); ok { + params.CustomRules = expandAmplifyCustomRules(v.([]interface{})) + } - if v, ok := d.GetOk("enable_branch_auto_build"); ok { - params.EnableBranchAutoBuild = aws.Bool(v.(bool)) - } + if v, ok := d.GetOk("description"); ok { + params.Description = aws.String(v.(string)) + } - if v, ok := d.GetOk("environment_variables"); ok { - params.EnvironmentVariables = stringMapToPointers(v.(map[string]interface{})) - } + if v, ok := d.GetOk("enable_branch_auto_build"); ok { + params.EnableBranchAutoBuild = aws.Bool(v.(bool)) + } - if v, ok := d.GetOk("iam_service_role_arn"); ok { - params.IamServiceRoleArn = aws.String(v.(string)) - } + if v, ok := d.GetOk("environment_variables"); ok { + params.EnvironmentVariables = stringMapToPointers(v.(map[string]interface{})) + } - if v, ok := d.GetOk("platform"); ok { - params.Platform = aws.String(v.(string)) - } + if v, ok := d.GetOk("iam_service_role_arn"); ok { + params.IamServiceRoleArn = aws.String(v.(string)) + } - if v, ok := d.GetOk("repository"); ok { - params.Repository = aws.String(v.(string)) - } + if v, ok := d.GetOk("platform"); ok { + params.Platform = aws.String(v.(string)) + } - if v, ok := d.GetOk("access_token"); ok { - params.AccessToken = aws.String(v.(string)) - } + if v, ok := d.GetOk("repository"); ok { + params.Repository = aws.String(v.(string)) + } - if v, ok := d.GetOk("oauth_token"); ok { - params.OauthToken = aws.String(v.(string)) - } + if v, ok := d.GetOk("access_token"); ok { + params.AccessToken = aws.String(v.(string)) + } + + if v, ok := d.GetOk("oauth_token"); ok { + params.OauthToken = aws.String(v.(string)) + } + */ - if v := d.Get("tags").(map[string]interface{}); len(v) > 0 { - params.Tags = keyvaluetags.New(v).IgnoreAws().AmplifyTags() + if len(tags) > 0 { + input.Tags = tags.IgnoreAws().AmplifyTags() } - resp, err := conn.CreateApp(params) + log.Printf("[DEBUG] Creating Amplify App: %s", input) + output, err := conn.CreateApp(input) + if err != nil { - return fmt.Errorf("Error creating Amplify App: %s", err) + return fmt.Errorf("error creating Amplify App (%s): %w", name, err) } - d.SetId(*resp.App.AppId) + d.SetId(aws.StringValue(output.App.AppId)) return resourceAwsAmplifyAppRead(d, meta) } func resourceAwsAmplifyAppRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).amplifyconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig - log.Printf("[DEBUG] Reading Amplify App: %s", d.Id()) - resp, err := conn.GetApp(&lify.GetAppInput{ - AppId: aws.String(d.Id()), - }) - if err != nil { - if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == amplify.ErrCodeNotFoundException { - log.Printf("[WARN] Amplify App (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - return err - } + app, err := finder.AppByID(conn, d.Id()) - d.Set("arn", resp.App.AppArn) - if err := d.Set("auto_branch_creation_config", flattenAmplifyAutoBranchCreationConfig(resp.App.AutoBranchCreationConfig, resp.App.AutoBranchCreationPatterns, resp.App.EnableAutoBranchCreation)); err != nil { - return fmt.Errorf("error setting auto_branch_creation_config: %s", err) - } - if err := d.Set("basic_auth_config", flattenAmplifyBasicAuthConfig(resp.App.EnableBasicAuth, resp.App.BasicAuthCredentials)); err != nil { - return fmt.Errorf("error setting basic_auth_config: %s", err) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Amplify App (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil } - d.Set("build_spec", resp.App.BuildSpec) - if err := d.Set("custom_rules", flattenAmplifyCustomRules(resp.App.CustomRules)); err != nil { - return fmt.Errorf("error setting custom_rules: %s", err) + + if err != nil { + return fmt.Errorf("error reading Amplify App (%s): %w", d.Id(), err) } - d.Set("default_domain", resp.App.DefaultDomain) - d.Set("description", resp.App.Description) - d.Set("enable_branch_auto_build", resp.App.EnableBranchAutoBuild) - if err := d.Set("environment_variables", aws.StringValueMap(resp.App.EnvironmentVariables)); err != nil { - return fmt.Errorf("error setting environment_variables: %s", err) + + d.Set("arn", app.AppArn) + + d.Set("name", app.Name) + d.Set("name_prefix", naming.NamePrefixFromName(aws.StringValue(app.Name))) + + /* + if err := d.Set("auto_branch_creation_config", flattenAmplifyAutoBranchCreationConfig(resp.App.AutoBranchCreationConfig, resp.App.AutoBranchCreationPatterns, resp.App.EnableAutoBranchCreation)); err != nil { + return fmt.Errorf("error setting auto_branch_creation_config: %s", err) + } + if err := d.Set("basic_auth_config", flattenAmplifyBasicAuthConfig(resp.App.EnableBasicAuth, resp.App.BasicAuthCredentials)); err != nil { + return fmt.Errorf("error setting basic_auth_config: %s", err) + } + d.Set("build_spec", resp.App.BuildSpec) + if err := d.Set("custom_rules", flattenAmplifyCustomRules(resp.App.CustomRules)); err != nil { + return fmt.Errorf("error setting custom_rules: %s", err) + } + d.Set("default_domain", resp.App.DefaultDomain) + d.Set("description", resp.App.Description) + d.Set("enable_branch_auto_build", resp.App.EnableBranchAutoBuild) + if err := d.Set("environment_variables", aws.StringValueMap(resp.App.EnvironmentVariables)); err != nil { + return fmt.Errorf("error setting environment_variables: %s", err) + } + d.Set("iam_service_role_arn", resp.App.IamServiceRoleArn) + d.Set("name", resp.App.Name) + d.Set("platform", resp.App.Platform) + d.Set("repository", resp.App.Repository) + */ + + tags := keyvaluetags.AmplifyKeyValueTags(app.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) + + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) } - d.Set("iam_service_role_arn", resp.App.IamServiceRoleArn) - d.Set("name", resp.App.Name) - d.Set("platform", resp.App.Platform) - d.Set("repository", resp.App.Repository) - if err := d.Set("tags", keyvaluetags.AmplifyKeyValueTags(resp.App.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %s", err) + + if err := d.Set("tags_all", tags.Map()); err != nil { + return fmt.Errorf("error setting tags_all: %w", err) } return nil @@ -355,80 +437,82 @@ func resourceAwsAmplifyAppRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsAmplifyAppUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).amplifyconn - log.Printf("[DEBUG] Updating Amplify App: %s", d.Id()) - params := &lify.UpdateAppInput{ - AppId: aws.String(d.Id()), - } + /* - if d.HasChange("auto_branch_creation_config") { - v := d.Get("auto_branch_creation_config") - config, patterns, enable := expandAmplifyAutoBranchCreationConfig(v.([]interface{})) - params.AutoBranchCreationConfig = config - params.AutoBranchCreationPatterns = patterns - params.EnableAutoBranchCreation = enable - } + params := &lify.UpdateAppInput{ + AppId: aws.String(d.Id()), + } - if d.HasChange("basic_auth_config") { - enable, credentials := expandAmplifyBasicAuthConfig(d.Get("basic_auth_config").([]interface{})) - params.EnableBasicAuth = enable - params.BasicAuthCredentials = credentials - } + if d.HasChange("auto_branch_creation_config") { + v := d.Get("auto_branch_creation_config") + config, patterns, enable := expandAmplifyAutoBranchCreationConfig(v.([]interface{})) + params.AutoBranchCreationConfig = config + params.AutoBranchCreationPatterns = patterns + params.EnableAutoBranchCreation = enable + } - if d.HasChange("build_spec") { - params.BuildSpec = aws.String(d.Get("build_spec").(string)) - } + if d.HasChange("basic_auth_config") { + enable, credentials := expandAmplifyBasicAuthConfig(d.Get("basic_auth_config").([]interface{})) + params.EnableBasicAuth = enable + params.BasicAuthCredentials = credentials + } - if d.HasChange("custom_rules") { - params.CustomRules = expandAmplifyCustomRules(d.Get("custom_rules").([]interface{})) - } + if d.HasChange("build_spec") { + params.BuildSpec = aws.String(d.Get("build_spec").(string)) + } - if d.HasChange("description") { - params.Description = aws.String(d.Get("description").(string)) - } + if d.HasChange("custom_rules") { + params.CustomRules = expandAmplifyCustomRules(d.Get("custom_rules").([]interface{})) + } - if d.HasChange("enable_branch_auto_build") { - params.EnableBranchAutoBuild = aws.Bool(d.Get("enable_branch_auto_build").(bool)) - } + if d.HasChange("description") { + params.Description = aws.String(d.Get("description").(string)) + } - if d.HasChange("environment_variables") { - v := d.Get("environment_variables") - params.EnvironmentVariables = expandAmplifyEnvironmentVariables(v.(map[string]interface{})) - } + if d.HasChange("enable_branch_auto_build") { + params.EnableBranchAutoBuild = aws.Bool(d.Get("enable_branch_auto_build").(bool)) + } - if d.HasChange("iam_service_role_arn") { - params.IamServiceRoleArn = aws.String(d.Get("iam_service_role_arn").(string)) - } + if d.HasChange("environment_variables") { + v := d.Get("environment_variables") + params.EnvironmentVariables = expandAmplifyEnvironmentVariables(v.(map[string]interface{})) + } - if d.HasChange("name") { - params.Name = aws.String(d.Get("name").(string)) - } + if d.HasChange("iam_service_role_arn") { + params.IamServiceRoleArn = aws.String(d.Get("iam_service_role_arn").(string)) + } - if d.HasChange("platform") { - params.Platform = aws.String(d.Get("platform").(string)) - } + if d.HasChange("name") { + params.Name = aws.String(d.Get("name").(string)) + } - if d.HasChange("repository") { - params.Repository = aws.String(d.Get("repository").(string)) - } + if d.HasChange("platform") { + params.Platform = aws.String(d.Get("platform").(string)) + } - if v, ok := d.GetOk("access_token"); ok { - params.AccessToken = aws.String(v.(string)) - } + if d.HasChange("repository") { + params.Repository = aws.String(d.Get("repository").(string)) + } - if v, ok := d.GetOk("oauth_token"); ok { - params.OauthToken = aws.String(v.(string)) - } + if v, ok := d.GetOk("access_token"); ok { + params.AccessToken = aws.String(v.(string)) + } - _, err := conn.UpdateApp(params) - if err != nil { - return fmt.Errorf("Error updating Amplify App: %s", err) - } + if v, ok := d.GetOk("oauth_token"); ok { + params.OauthToken = aws.String(v.(string)) + } + + _, err := conn.UpdateApp(params) + if err != nil { + return fmt.Errorf("Error updating Amplify App: %s", err) + } + */ - if d.HasChange("tags") { - o, n := d.GetChange("tags") + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") if err := keyvaluetags.AmplifyUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating tags: %s", err) + return fmt.Errorf("error updating tags: %w", err) } } @@ -437,25 +521,24 @@ func resourceAwsAmplifyAppUpdate(d *schema.ResourceData, meta interface{}) error func resourceAwsAmplifyAppDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).amplifyconn - log.Printf("[DEBUG] Deleting Amplify App: %s", d.Id()) - err := deleteAmplifyApp(conn, d.Id()) - if err != nil { - return fmt.Errorf("Error deleting Amplify App: %s", err) - } + log.Printf("[DEBUG] Deleting Amplify App (%s)", d.Id()) + _, err := conn.DeleteApp(&lify.DeleteAppInput{ + AppId: aws.String(d.Id()), + }) - return nil -} + if tfawserr.ErrCodeEquals(err, amplify.ErrCodeResourceNotFoundException) { + return nil + } -func deleteAmplifyApp(conn *amplify.Amplify, appId string) error { - params := &lify.DeleteAppInput{ - AppId: aws.String(appId), + if err != nil { + return fmt.Errorf("error deleting Amplify App (%s): %w", d.Id(), err) } - _, err := conn.DeleteApp(params) - return err + return nil } +/* func expandAmplifyEnvironmentVariables(envs map[string]interface{}) map[string]*string { if len(envs) == 0 { empty := "" @@ -646,3 +729,4 @@ func flattenAmplifyCustomRules(rules []*amplify.CustomRule) []map[string]interfa return values } +*/ diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 1fbc5653634..825f6b811a4 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -6,13 +6,17 @@ import ( "regexp" "testing" - "github.com/aws/aws-sdk-go/aws" "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" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/naming" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/amplify/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) +// TODO sweeper + func TestAccAWSAmplifyApp_basic(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") @@ -20,11 +24,12 @@ func TestAccAWSAmplifyApp_basic(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfig_Required(rName), + Config: testAccAWSAmplifyAppConfigName(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "amplify", regexp.MustCompile(`apps/.+`)), @@ -52,6 +57,105 @@ func TestAccAWSAmplifyApp_basic(t *testing.T) { }) } +func TestAccAWSAmplifyApp_Name_Generated(t *testing.T) { + var app amplify.App + resourceName := "aws_amplify_app.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigNameGenerated(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAmplifyApp_NamePrefix(t *testing.T) { + var app amplify.App + resourceName := "aws_amplify_app.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigNamePrefix("tf-acc-test-prefix-"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + naming.TestCheckResourceAttrNameFromPrefix(resourceName, "name", "tf-acc-test-prefix-"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", "tf-acc-test-prefix-"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAmplifyApp_Tags(t *testing.T) { + var app amplify.App + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAmplifyAppConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSAmplifyAppConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + func TestAccAWSAmplifyApp_rename(t *testing.T) { resourceName := "aws_amplify_app.test" @@ -61,11 +165,12 @@ func TestAccAWSAmplifyApp_rename(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfig_Required(rName1), + Config: testAccAWSAmplifyAppConfigName(rName1), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", rName1), ), @@ -76,7 +181,7 @@ func TestAccAWSAmplifyApp_rename(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfig_Required(rName2), + Config: testAccAWSAmplifyAppConfigName(rName2), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", rName2), ), @@ -95,6 +200,7 @@ func TestAccAWSAmplifyApp_description(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ @@ -125,6 +231,7 @@ func TestAccAWSAmplifyApp_repository(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ @@ -156,6 +263,7 @@ func TestAccAWSAmplifyApp_buildSpec(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ @@ -186,6 +294,7 @@ func TestAccAWSAmplifyApp_customRules(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ @@ -226,6 +335,7 @@ func TestAccAWSAmplifyApp_environmentVariables(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ @@ -250,7 +360,7 @@ func TestAccAWSAmplifyApp_environmentVariables(t *testing.T) { ), }, { - Config: testAccAWSAmplifyAppConfig_Required(rName), + Config: testAccAWSAmplifyAppConfigName(rName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "0"), ), @@ -265,6 +375,7 @@ func TestAccAWSAmplifyApp_autoBranchCreationConfig(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ @@ -319,7 +430,7 @@ func TestAccAWSAmplifyApp_autoBranchCreationConfig(t *testing.T) { ), }, { - Config: testAccAWSAmplifyAppConfig_Required(rName), + Config: testAccAWSAmplifyAppConfigName(rName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.#", "0"), ), @@ -339,6 +450,7 @@ func TestAccAWSAmplifyApp_basicAuthConfig(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ @@ -366,7 +478,7 @@ func TestAccAWSAmplifyApp_basicAuthConfig(t *testing.T) { ), }, { - Config: testAccAWSAmplifyAppConfig_Required(rName), + Config: testAccAWSAmplifyAppConfigName(rName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "basic_auth_config.#", "0"), ), @@ -381,6 +493,7 @@ func TestAccAWSAmplifyApp_enableBranchAutoBuild(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ @@ -396,7 +509,7 @@ func TestAccAWSAmplifyApp_enableBranchAutoBuild(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfig_Required(rName), + Config: testAccAWSAmplifyAppConfigName(rName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "enable_branch_auto_build", "false"), ), @@ -414,6 +527,7 @@ func TestAccAWSAmplifyApp_iamServiceRoleArn(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ @@ -438,103 +552,77 @@ func TestAccAWSAmplifyApp_iamServiceRoleArn(t *testing.T) { }) } -func TestAccAWSAmplifyApp_tags(t *testing.T) { - rName := acctest.RandomWithPrefix("tf-acc-test") - resourceName := "aws_amplify_app.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSAmplifyAppDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSAmplifyAppConfigTags1(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.TAG1", "1"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccAWSAmplifyAppConfigTags2(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.TAG1", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.TAG2", "2"), - ), - }, - { - Config: testAccAWSAmplifyAppConfig_Required(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), - ), - }, - }, - }) -} - -func testAccCheckAWSAmplifyAppExists(resourceName string, app *amplify.App) resource.TestCheckFunc { +func testAccCheckAWSAmplifyAppExists(n string, v *amplify.App) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[resourceName] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", resourceName) + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Amplify App ID is set") } conn := testAccProvider.Meta().(*AWSClient).amplifyconn - output, err := conn.GetApp(&lify.GetAppInput{ - AppId: aws.String(rs.Primary.ID), - }) + output, err := finder.AppByID(conn, rs.Primary.ID) + if err != nil { return err } - if output == nil || output.App == nil { - return fmt.Errorf("Amplify App (%s) not found", rs.Primary.ID) - } - - *app = *output.App + *v = *output return nil } } func testAccCheckAWSAmplifyAppDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).amplifyconn + for _, rs := range s.RootModule().Resources { if rs.Type != "aws_amplify_app" { continue } - conn := testAccProvider.Meta().(*AWSClient).amplifyconn - - _, err := conn.GetApp(&lify.GetAppInput{ - AppId: aws.String(rs.Primary.ID), - }) + _, err := finder.AppByID(conn, rs.Primary.ID) - if isAWSErr(err, amplify.ErrCodeNotFoundException, "") { + if tfresource.NotFound(err) { continue } if err != nil { return err } + + return fmt.Errorf("Amplify App %s still exists", rs.Primary.ID) } return nil } -func testAccAWSAmplifyAppConfig_Required(rName string) string { +func testAccAWSAmplifyAppConfigName(rName string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" + name = %[1]q } `, rName) } +func testAccAWSAmplifyAppConfigNameGenerated() string { + return ` +resource "aws_amplify_app" "test" {} +` +} + +func testAccAWSAmplifyAppConfigNamePrefix(namePrefix string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name_prefix = %[1]q +} +`, namePrefix) +} + func testAccAWSAmplifyAppConfigDescription(rName string, description string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { @@ -761,27 +849,27 @@ POLICY `, rName, roleName) } -func testAccAWSAmplifyAppConfigTags1(rName string) string { +func testAccAWSAmplifyAppConfigTags1(rName, tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" + name = %[1]q tags = { - TAG1 = "1", + %[2]q = %[3]q } } -`, rName) +`, rName, tagKey1, tagValue1) } -func testAccAWSAmplifyAppConfigTags2(rName string) string { +func testAccAWSAmplifyAppConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" + name = %[1]q tags = { - TAG1 = "2", - TAG2 = "2", + %[2]q = %[3]q + %[4]q = %[5]q } } -`, rName) +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) } diff --git a/website/docs/r/amplify_app.html.markdown b/website/docs/r/amplify_app.html.markdown index c13f3876816..ec5d63afc6a 100644 --- a/website/docs/r/amplify_app.html.markdown +++ b/website/docs/r/amplify_app.html.markdown @@ -130,7 +130,9 @@ resource "aws_amplify_app" "app" { The following arguments are supported: -* `name` - (Required) Name for the Amplify App. +* `name` - (Optional) Name of the Amplify App. If omitted, Terraform will assign a random, unique name. Conflicts with `name_prefix`. +* `name_prefix` - (Optional) Creates a unique name beginning with the specified prefix. Conflicts with `name`. + * `access_token` - (Optional) Personal Access token for 3rd party source control system for an Amplify App, used to create webhook and read-only deploy key. Token is not stored. * `auto_branch_creation_config` - (Optional) Automated branch creation config for the Amplify App. An `auto_branch_creation_config` block is documented below. * `basic_auth_config` - (Optional) Basic Authentication config for the Amplify App. A `basic_auth_config` block is documented below. @@ -143,7 +145,8 @@ The following arguments are supported: * `oauth_token` - (Optional) OAuth token for 3rd party source control system for an Amplify App, used to create webhook and read-only deploy key. OAuth token is not stored. * `platform` - (Optional) Platform for the Amplify App. * `repository` - (Optional) Repository for the Amplify App. -* `tags` - (Optional) Key-value mapping of resource tags. +* `tags` - (Optional) Key-value mapping 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. + An `auto_branch_creation_config` block supports the following arguments: @@ -178,6 +181,7 @@ The following attributes are exported: * `arn` - ARN for the Amplify App. * `default_domain` - Default domain for the Amplify App. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). ## Import From 4f7eda019ab9964752c0e0804237b225e2b5ede1 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 19 May 2021 11:33:59 -0400 Subject: [PATCH 10/30] r/aws_amplify_app: Standard attribute handling. --- aws/resource_aws_amplify_app.go | 240 ++++++++++++++++++++++++++++++-- 1 file changed, 230 insertions(+), 10 deletions(-) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index cca1028013e..2499b1dae3f 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "log" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/amplify" @@ -100,13 +101,14 @@ func resourceAwsAmplifyApp() *schema.Resource { "stage": { Type: schema.TypeString, Optional: true, - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // stage is "NONE" by default - if old == "NONE" && new == "" { - return true - } - return old == new - }, + //TODO + // DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // // stage is "NONE" by default + // if old == "NONE" && new == "" { + // return true + // } + // return old == new + // }, ValidateFunc: validation.StringInSlice(amplify.Stage_Values(), false), }, }, @@ -259,7 +261,7 @@ func resourceAwsAmplifyApp() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "branch_name": { - Type: schema.TypeBool, + Type: schema.TypeString, Computed: true, }, @@ -274,7 +276,7 @@ func resourceAwsAmplifyApp() *schema.Resource { }, "thumbnail_url": { - Type: schema.TypeBool, + Type: schema.TypeString, Computed: true, }, }, @@ -306,6 +308,70 @@ func resourceAwsAmplifyAppCreate(d *schema.ResourceData, meta interface{}) error Name: aws.String(name), } + if v, ok := d.GetOk("access_token"); ok { + input.AccessToken = aws.String(v.(string)) + } + + if v, ok := d.GetOk("auto_branch_creation_config"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.AutoBranchCreationConfig = expandAmplifyAutoBranchCreationConfig(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("auto_branch_creation_patterns"); ok && v.(*schema.Set).Len() > 0 { + input.AutoBranchCreationPatterns = expandStringSet(v.(*schema.Set)) + } + + if v, ok := d.GetOk("basic_auth_credentials"); ok { + input.BasicAuthCredentials = aws.String(v.(string)) + } + + if v, ok := d.GetOk("build_spec"); ok { + input.BuildSpec = aws.String(v.(string)) + } + + if v, ok := d.GetOk("custom_headers"); ok { + input.CustomHeaders = aws.String(v.(string)) + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("enable_auto_branch_creation"); ok { + input.EnableAutoBranchCreation = aws.Bool(v.(bool)) + } + + if v, ok := d.GetOk("enable_basic_auth"); ok { + input.EnableBasicAuth = aws.Bool(v.(bool)) + } + + if v, ok := d.GetOk("enable_branch_auto_build"); ok { + input.EnableBranchAutoBuild = aws.Bool(v.(bool)) + } + + if v, ok := d.GetOk("enable_branch_auto_deletion"); ok { + input.EnableBranchAutoDeletion = aws.Bool(v.(bool)) + } + + if v, ok := d.GetOk("environment_variables"); ok && len(v.(map[string]interface{})) > 0 { + input.EnvironmentVariables = expandStringMap(v.(map[string]interface{})) + } + + if v, ok := d.GetOk("iam_service_role_arn"); ok { + input.IamServiceRoleArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("oauth_token"); ok { + input.OauthToken = aws.String(v.(string)) + } + + if v, ok := d.GetOk("platform"); ok { + input.Platform = aws.String(v.(string)) + } + + if v, ok := d.GetOk("repository"); ok { + input.Platform = aws.String(v.(string)) + } + /* if v, ok := d.GetOk("auto_branch_creation_config"); ok { config, patterns, enable := expandAmplifyAutoBranchCreationConfig(v.([]interface{})) @@ -395,9 +461,37 @@ func resourceAwsAmplifyAppRead(d *schema.ResourceData, meta interface{}) error { } d.Set("arn", app.AppArn) - + if app.AutoBranchCreationConfig != nil { + if err := d.Set("auto_branch_creation_config", []interface{}{flattenAmplifyAutoBranchCreationConfig(app.AutoBranchCreationConfig)}); err != nil { + return fmt.Errorf("error setting auto_branch_creation_config: %w", err) + } + } else { + d.Set("auto_branch_creation_config", nil) + } + d.Set("auto_branch_creation_patterns", aws.StringValueSlice(app.AutoBranchCreationPatterns)) + d.Set("basic_auth_credentials", app.BasicAuthCredentials) + d.Set("build_spec", app.BuildSpec) + d.Set("custom_headers", app.CustomHeaders) + + d.Set("default_domain", app.DefaultDomain) + d.Set("description", app.Description) + d.Set("enable_auto_branch_creation", app.EnableAutoBranchCreation) + d.Set("enable_basic_auth", app.EnableBasicAuth) + d.Set("enable_branch_auto_build", app.EnableBranchAutoBuild) + d.Set("enable_branch_auto_deletion", app.EnableBranchAutoDeletion) + d.Set("environment_variables", aws.StringValueMap(app.EnvironmentVariables)) + d.Set("iam_service_role_arn", app.IamServiceRoleArn) d.Set("name", app.Name) d.Set("name_prefix", naming.NamePrefixFromName(aws.StringValue(app.Name))) + d.Set("platform", app.Platform) + if app.ProductionBranch != nil { + if err := d.Set("production_branch", []interface{}{flattenAmplifyProductionBranch(app.ProductionBranch)}); err != nil { + return fmt.Errorf("error setting production_branch: %w", err) + } + } else { + d.Set("production_branch", nil) + } + d.Set("repository", app.Repository) /* if err := d.Set("auto_branch_creation_config", flattenAmplifyAutoBranchCreationConfig(resp.App.AutoBranchCreationConfig, resp.App.AutoBranchCreationPatterns, resp.App.EnableAutoBranchCreation)); err != nil { @@ -538,6 +632,132 @@ func resourceAwsAmplifyAppDelete(d *schema.ResourceData, meta interface{}) error return nil } +func expandAmplifyAutoBranchCreationConfig(tfMap map[string]interface{}) *amplify.AutoBranchCreationConfig { + if tfMap == nil { + return nil + } + + apiObject := &lify.AutoBranchCreationConfig{} + + if v, ok := tfMap["basic_auth_credentials"].(string); ok && v != "" { + apiObject.BasicAuthCredentials = aws.String(v) + } + + if v, ok := tfMap["build_spec"].(string); ok && v != "" { + apiObject.BuildSpec = aws.String(v) + } + + if v, ok := tfMap["enable_auto_build"].(bool); ok && v { + apiObject.EnableAutoBuild = aws.Bool(v) + } + + if v, ok := tfMap["enable_basic_auth"].(bool); ok && v { + apiObject.EnableBasicAuth = aws.Bool(v) + } + + if v, ok := tfMap["enable_performance_mode"].(bool); ok && v { + apiObject.EnablePerformanceMode = aws.Bool(v) + } + + if v, ok := tfMap["enable_pull_request_preview"].(bool); ok && v { + apiObject.EnablePullRequestPreview = aws.Bool(v) + } + + if v, ok := tfMap["environment_variables"].(map[string]interface{}); ok && len(v) > 0 { + apiObject.EnvironmentVariables = expandStringMap(v) + } + + if v, ok := tfMap["framework"].(string); ok && v != "" { + apiObject.Framework = aws.String(v) + } + + if v, ok := tfMap["pull_request_environment_name"].(string); ok && v != "" { + apiObject.PullRequestEnvironmentName = aws.String(v) + } + + if v, ok := tfMap["stage"].(string); ok && v != "" { + apiObject.Stage = aws.String(v) + } + + return apiObject +} + +func flattenAmplifyAutoBranchCreationConfig(apiObject *amplify.AutoBranchCreationConfig) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.BasicAuthCredentials; v != nil { + tfMap["basic_auth_credentials"] = aws.StringValue(v) + } + + if v := apiObject.BuildSpec; v != nil { + tfMap["build_spec"] = aws.StringValue(v) + } + + if v := apiObject.EnableAutoBuild; v != nil { + tfMap["enable_auto_build"] = aws.BoolValue(v) + } + + if v := apiObject.EnableBasicAuth; v != nil { + tfMap["enable_basic_auth"] = aws.BoolValue(v) + } + + if v := apiObject.EnablePerformanceMode; v != nil { + tfMap["enable_performance_mode"] = aws.BoolValue(v) + } + + if v := apiObject.EnablePullRequestPreview; v != nil { + tfMap["enable_pull_request_preview"] = aws.BoolValue(v) + } + + if v := apiObject.EnvironmentVariables; v != nil { + tfMap["environment_variables"] = aws.StringValueMap(v) + } + + if v := apiObject.Framework; v != nil { + tfMap["framework"] = aws.StringValue(v) + } + + if v := apiObject.PullRequestEnvironmentName; v != nil { + tfMap["pull_request_environment_name"] = aws.StringValue(v) + } + + if v := apiObject.Stage; v != nil { + tfMap["stage"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenAmplifyProductionBranch(apiObject *amplify.ProductionBranch) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.BranchName; v != nil { + tfMap["branch_name"] = aws.StringValue(v) + } + + if v := apiObject.LastDeployTime; v != nil { + tfMap["last_deploy_time"] = aws.TimeValue(v).Format(time.RFC3339) + } + + if v := apiObject.Status; v != nil { + tfMap["status"] = aws.StringValue(v) + } + + if v := apiObject.ThumbnailUrl; v != nil { + tfMap["thumbnail_url"] = aws.StringValue(v) + } + + return tfMap +} + /* func expandAmplifyEnvironmentVariables(envs map[string]interface{}) map[string]*string { if len(envs) == 0 { From bcec1a5beff68188b38397ec906a60af6bdee4bc Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 19 May 2021 14:57:05 -0400 Subject: [PATCH 11/30] r/aws_amplify_app: Get simple tests working. Acceptance test output: % make testacc TEST=./aws TESTARGS='-run=TestAccAWSAmplifyApp_Name\|TestAccAWSAmplifyApp_basic\|TestAccAWSAmplifyApp_disappears\|TestAccAWSAmplifyApp_Tags' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSAmplifyApp_Name\|TestAccAWSAmplifyApp_basic\|TestAccAWSAmplifyApp_disappears\|TestAccAWSAmplifyApp_Tags -timeout 180m === RUN TestAccAWSAmplifyApp_basic === PAUSE TestAccAWSAmplifyApp_basic === RUN TestAccAWSAmplifyApp_Name_Generated === PAUSE TestAccAWSAmplifyApp_Name_Generated === RUN TestAccAWSAmplifyApp_NamePrefix === PAUSE TestAccAWSAmplifyApp_NamePrefix === RUN TestAccAWSAmplifyApp_Tags === PAUSE TestAccAWSAmplifyApp_Tags === CONT TestAccAWSAmplifyApp_basic === CONT TestAccAWSAmplifyApp_Tags === CONT TestAccAWSAmplifyApp_Name_Generated === CONT TestAccAWSAmplifyApp_NamePrefix --- PASS: TestAccAWSAmplifyApp_Name_Generated (14.40s) --- PASS: TestAccAWSAmplifyApp_NamePrefix (14.90s) --- PASS: TestAccAWSAmplifyApp_basic (19.53s) --- PASS: TestAccAWSAmplifyApp_Tags (30.27s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 33.242s --- aws/internal/service/amplify/finder/finder.go | 2 +- aws/resource_aws_amplify_app.go | 113 +++++++++++++++++- aws/resource_aws_amplify_app_test.go | 108 +++++++++-------- 3 files changed, 168 insertions(+), 55 deletions(-) diff --git a/aws/internal/service/amplify/finder/finder.go b/aws/internal/service/amplify/finder/finder.go index 142893ee72f..8f67fbb18f1 100644 --- a/aws/internal/service/amplify/finder/finder.go +++ b/aws/internal/service/amplify/finder/finder.go @@ -14,7 +14,7 @@ func AppByID(conn *amplify.Amplify, id string) (*amplify.App, error) { output, err := conn.GetApp(input) - if tfawserr.ErrCodeEquals(err, amplify.ErrCodeResourceNotFoundException) { + if tfawserr.ErrCodeEquals(err, amplify.ErrCodeNotFoundException) { return nil, &resource.NotFoundError{ LastError: err, LastRequest: input, diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index 2499b1dae3f..bcd1e94e019 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -248,10 +248,9 @@ func resourceAwsAmplifyApp() *schema.Resource { }, "platform": { - Type: schema.TypeString, - Optional: true, - //TODO - //Default: amplify.PlatformWeb, + Type: schema.TypeString, + Optional: true, + Default: amplify.PlatformWeb, ValidateFunc: validation.StringInSlice(amplify.Platform_Values(), false), }, @@ -332,6 +331,10 @@ func resourceAwsAmplifyAppCreate(d *schema.ResourceData, meta interface{}) error input.CustomHeaders = aws.String(v.(string)) } + if v, ok := d.GetOk("custom_rule"); ok && len(v.([]interface{})) > 0 { + input.CustomRules = expandAmplifyCustomRules(v.([]interface{})) + } + if v, ok := d.GetOk("description"); ok { input.Description = aws.String(v.(string)) } @@ -472,7 +475,9 @@ func resourceAwsAmplifyAppRead(d *schema.ResourceData, meta interface{}) error { d.Set("basic_auth_credentials", app.BasicAuthCredentials) d.Set("build_spec", app.BuildSpec) d.Set("custom_headers", app.CustomHeaders) - + if err := d.Set("custom_rule", flattenAmplifyCustomRules(app.CustomRules)); err != nil { + return fmt.Errorf("error setting custom_rule: %w", err) + } d.Set("default_domain", app.DefaultDomain) d.Set("description", app.Description) d.Set("enable_auto_branch_creation", app.EnableAutoBranchCreation) @@ -621,7 +626,7 @@ func resourceAwsAmplifyAppDelete(d *schema.ResourceData, meta interface{}) error AppId: aws.String(d.Id()), }) - if tfawserr.ErrCodeEquals(err, amplify.ErrCodeResourceNotFoundException) { + if tfawserr.ErrCodeEquals(err, amplify.ErrCodeNotFoundException) { return nil } @@ -732,6 +737,102 @@ func flattenAmplifyAutoBranchCreationConfig(apiObject *amplify.AutoBranchCreatio return tfMap } +func expandAmplifyCustomRule(tfMap map[string]interface{}) *amplify.CustomRule { + if tfMap == nil { + return nil + } + + apiObject := &lify.CustomRule{} + + if v, ok := tfMap["condition"].(string); ok && v != "" { + apiObject.Condition = aws.String(v) + } + + if v, ok := tfMap["source"].(string); ok && v != "" { + apiObject.Source = aws.String(v) + } + + if v, ok := tfMap["status"].(string); ok && v != "" { + apiObject.Status = aws.String(v) + } + + if v, ok := tfMap["target"].(string); ok && v != "" { + apiObject.Target = aws.String(v) + } + + return apiObject +} + +func expandAmplifyCustomRules(tfList []interface{}) []*amplify.CustomRule { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*amplify.CustomRule + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandAmplifyCustomRule(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func flattenAmplifyCustomRule(apiObject *amplify.CustomRule) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Condition; v != nil { + tfMap["condition"] = aws.StringValue(v) + } + + if v := apiObject.Source; v != nil { + tfMap["source"] = aws.StringValue(v) + } + + if v := apiObject.Status; v != nil { + tfMap["status"] = aws.StringValue(v) + } + + if v := apiObject.Target; v != nil { + tfMap["target"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenAmplifyCustomRules(apiObjects []*amplify.CustomRule) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenAmplifyCustomRule(apiObject)) + } + + return tfList +} + func flattenAmplifyProductionBranch(apiObject *amplify.ProductionBranch) map[string]interface{} { if apiObject == nil { return nil diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 825f6b811a4..602fec007d6 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -1,6 +1,7 @@ package aws import ( + "encoding/base64" "fmt" "os" "regexp" @@ -23,28 +24,37 @@ func TestAccAWSAmplifyApp_basic(t *testing.T) { resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { Config: testAccAWSAmplifyAppConfigName(rName), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckNoResourceAttr(resourceName, "access_token"), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "amplify", regexp.MustCompile(`apps/.+`)), - resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "description", ""), - resource.TestCheckResourceAttr(resourceName, "repository", ""), - resource.TestCheckResourceAttr(resourceName, "platform", "WEB"), - resource.TestMatchResourceAttr(resourceName, "default_domain", regexp.MustCompile(`\.amplifyapp\.com$`)), - resource.TestCheckResourceAttr(resourceName, "build_spec", ""), resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.#", "0"), - resource.TestCheckResourceAttr(resourceName, "basic_auth_config.#", "0"), - resource.TestCheckResourceAttr(resourceName, "custom_rules.#", "0"), - resource.TestCheckResourceAttr(resourceName, "environment_variables.#", "0"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_patterns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "basic_auth_credentials", ""), + resource.TestCheckResourceAttr(resourceName, "build_spec", ""), + resource.TestCheckResourceAttr(resourceName, "custom_headers", ""), + resource.TestCheckResourceAttr(resourceName, "custom_rule.#", "0"), + resource.TestMatchResourceAttr(resourceName, "default_domain", regexp.MustCompile(`\.amplifyapp\.com$`)), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "enable_auto_branch_creation", "false"), + resource.TestCheckResourceAttr(resourceName, "enable_basic_auth", "false"), resource.TestCheckResourceAttr(resourceName, "enable_branch_auto_build", "false"), + resource.TestCheckResourceAttr(resourceName, "enable_branch_auto_deletion", "false"), + resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "0"), resource.TestCheckResourceAttr(resourceName, "iam_service_role_arn", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "name_prefix", ""), + resource.TestCheckNoResourceAttr(resourceName, "oauth_token"), + resource.TestCheckResourceAttr(resourceName, "platform", "WEB"), + resource.TestCheckResourceAttr(resourceName, "production_branch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "repository", ""), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, @@ -62,7 +72,7 @@ func TestAccAWSAmplifyApp_Name_Generated(t *testing.T) { resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, @@ -89,7 +99,7 @@ func TestAccAWSAmplifyApp_NamePrefix(t *testing.T) { resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, @@ -117,7 +127,7 @@ func TestAccAWSAmplifyApp_Tags(t *testing.T) { resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, @@ -164,7 +174,7 @@ func TestAccAWSAmplifyApp_rename(t *testing.T) { rName2 := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, @@ -199,7 +209,7 @@ func TestAccAWSAmplifyApp_description(t *testing.T) { description2 := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, @@ -230,7 +240,7 @@ func TestAccAWSAmplifyApp_repository(t *testing.T) { resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, @@ -262,7 +272,7 @@ func TestAccAWSAmplifyApp_buildSpec(t *testing.T) { buildSpec2 := "version: 0.2" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, @@ -293,7 +303,7 @@ func TestAccAWSAmplifyApp_customRules(t *testing.T) { resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, @@ -334,7 +344,7 @@ func TestAccAWSAmplifyApp_environmentVariables(t *testing.T) { resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, @@ -374,7 +384,7 @@ func TestAccAWSAmplifyApp_autoBranchCreationConfig(t *testing.T) { resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, @@ -439,28 +449,26 @@ func TestAccAWSAmplifyApp_autoBranchCreationConfig(t *testing.T) { }) } -func TestAccAWSAmplifyApp_basicAuthConfig(t *testing.T) { +func TestAccAWSAmplifyApp_BasicAuthCredentials(t *testing.T) { + var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" - username1 := "username1" - password1 := "password1" - username2 := "username2" - password2 := "password2" + credentials1 := base64.StdEncoding.EncodeToString([]byte("username1:password1")) + credentials2 := base64.StdEncoding.EncodeToString([]byte("username2:password2")) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigBasicAuthConfig(rName, username1, password1), + Config: testAccAWSAmplifyAppConfigBasicAuthCredentials(rName, credentials1), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "basic_auth_config.#", "1"), - resource.TestCheckResourceAttr(resourceName, "basic_auth_config.0.enable_basic_auth", "true"), - resource.TestCheckResourceAttr(resourceName, "basic_auth_config.0.username", username1), - resource.TestCheckResourceAttr(resourceName, "basic_auth_config.0.password", password1), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "basic_auth_credentials", credentials1), + resource.TestCheckResourceAttr(resourceName, "enable_basic_auth", "true"), ), }, { @@ -469,18 +477,19 @@ func TestAccAWSAmplifyApp_basicAuthConfig(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfigBasicAuthConfig(rName, username2, password2), + Config: testAccAWSAmplifyAppConfigBasicAuthCredentials(rName, credentials2), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "basic_auth_config.#", "1"), - resource.TestCheckResourceAttr(resourceName, "basic_auth_config.0.enable_basic_auth", "true"), - resource.TestCheckResourceAttr(resourceName, "basic_auth_config.0.username", username2), - resource.TestCheckResourceAttr(resourceName, "basic_auth_config.0.password", password2), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "basic_auth_credentials", credentials2), + resource.TestCheckResourceAttr(resourceName, "enable_basic_auth", "true"), ), }, { Config: testAccAWSAmplifyAppConfigName(rName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "basic_auth_config.#", "0"), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "basic_auth_credentials", ""), + resource.TestCheckResourceAttr(resourceName, "enable_basic_auth", "false"), ), }, }, @@ -492,7 +501,7 @@ func TestAccAWSAmplifyApp_enableBranchAutoBuild(t *testing.T) { resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, @@ -526,7 +535,7 @@ func TestAccAWSAmplifyApp_iamServiceRoleArn(t *testing.T) { roleName2 := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), Providers: testAccProviders, CheckDestroy: testAccCheckAWSAmplifyAppDestroy, @@ -601,6 +610,12 @@ func testAccCheckAWSAmplifyAppDestroy(s *terraform.State) error { return nil } +func testAccPreCheckAWSAmplify(t *testing.T) { + if testAccGetPartition() == "aws-us-gov" { + t.Skip("AWS Amplify is not supported in GovCloud partition") + } +} + func testAccAWSAmplifyAppConfigName(rName string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { @@ -795,18 +810,15 @@ resource "aws_amplify_app" "test" { `, rName) } -func testAccAWSAmplifyAppConfigBasicAuthConfig(rName string, username, password string) string { +func testAccAWSAmplifyAppConfigBasicAuthCredentials(rName, basicAuthCredentials string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" + name = %[1]q - basic_auth_config { - enable_basic_auth = true - username = "%s" - password = "%s" - } + basic_auth_credentials = %[2]q + enable_basic_auth = true } -`, rName, username, password) +`, rName, basicAuthCredentials) } func testAccAWSAmplifyAppConfigEnableBranchAutoBuild(rName string) string { From e638796e76fffff2c404906b08ef57c85c1b42c0 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 19 May 2021 15:59:32 -0400 Subject: [PATCH 12/30] Suppress diff for 'basic_auth_credentials' if basic auth isn't enabled. --- aws/resource_aws_amplify_app.go | 87 +++++++++++++++++----------- aws/resource_aws_amplify_app_test.go | 3 +- 2 files changed, 54 insertions(+), 36 deletions(-) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index bcd1e94e019..58e17bb7627 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -126,6 +126,14 @@ func resourceAwsAmplifyApp() *schema.Resource { Optional: true, Sensitive: true, ValidateFunc: validation.StringLenBetween(1, 2000), + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // These credentials are ignored if basic auth is not enabled. + if d.Get("enable_basic_auth").(bool) { + return old == new + } + + return true + }, }, "build_spec": { @@ -372,7 +380,7 @@ func resourceAwsAmplifyAppCreate(d *schema.ResourceData, meta interface{}) error } if v, ok := d.GetOk("repository"); ok { - input.Platform = aws.String(v.(string)) + input.Repository = aws.String(v.(string)) } /* @@ -537,76 +545,85 @@ func resourceAwsAmplifyAppRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsAmplifyAppUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).amplifyconn - /* - - params := &lify.UpdateAppInput{ + if d.HasChangesExcept("tags", "tags_all") { + input := &lify.UpdateAppInput{ AppId: aws.String(d.Id()), } + if d.HasChange("access_token") { + input.AccessToken = aws.String(d.Get("access_token").(string)) + } + if d.HasChange("auto_branch_creation_config") { - v := d.Get("auto_branch_creation_config") - config, patterns, enable := expandAmplifyAutoBranchCreationConfig(v.([]interface{})) - params.AutoBranchCreationConfig = config - params.AutoBranchCreationPatterns = patterns - params.EnableAutoBranchCreation = enable + input.AutoBranchCreationConfig = expandAmplifyAutoBranchCreationConfig(d.Get("auto_branch_creation_config").([]interface{})[0].(map[string]interface{})) } - if d.HasChange("basic_auth_config") { - enable, credentials := expandAmplifyBasicAuthConfig(d.Get("basic_auth_config").([]interface{})) - params.EnableBasicAuth = enable - params.BasicAuthCredentials = credentials + if d.HasChange("auto_branch_creation_patterns") { + input.AutoBranchCreationPatterns = expandStringSet(d.Get("auto_branch_creation_patterns").(*schema.Set)) + } + + if d.HasChange("basic_auth_credentials") { + input.BasicAuthCredentials = aws.String(d.Get("basic_auth_credentials").(string)) } if d.HasChange("build_spec") { - params.BuildSpec = aws.String(d.Get("build_spec").(string)) + input.BuildSpec = aws.String(d.Get("build_spec").(string)) + } + + if d.HasChange("custom_headers") { + input.CustomHeaders = aws.String(d.Get("custom_headers").(string)) } - if d.HasChange("custom_rules") { - params.CustomRules = expandAmplifyCustomRules(d.Get("custom_rules").([]interface{})) + if d.HasChange("custom_rule") { + input.CustomRules = expandAmplifyCustomRules(d.Get("custom_rule").([]interface{})) } if d.HasChange("description") { - params.Description = aws.String(d.Get("description").(string)) + input.Description = aws.String(d.Get("description").(string)) + } + + if d.HasChange("enable_auto_branch_creation") { + input.EnableAutoBranchCreation = aws.Bool(d.Get("enable_auto_branch_creation").(bool)) + } + + if d.HasChange("enable_basic_auth") { + input.EnableBasicAuth = aws.Bool(d.Get("enable_basic_auth").(bool)) } if d.HasChange("enable_branch_auto_build") { - params.EnableBranchAutoBuild = aws.Bool(d.Get("enable_branch_auto_build").(bool)) + input.EnableBranchAutoBuild = aws.Bool(d.Get("enable_branch_auto_build").(bool)) + } + + if d.HasChange("enable_branch_auto_deletion") { + input.EnableBranchAutoDeletion = aws.Bool(d.Get("enable_branch_auto_deletion").(bool)) } if d.HasChange("environment_variables") { - v := d.Get("environment_variables") - params.EnvironmentVariables = expandAmplifyEnvironmentVariables(v.(map[string]interface{})) + input.EnvironmentVariables = expandStringMap(d.Get("environment_variables").(map[string]interface{})) } if d.HasChange("iam_service_role_arn") { - params.IamServiceRoleArn = aws.String(d.Get("iam_service_role_arn").(string)) + input.IamServiceRoleArn = aws.String(d.Get("iam_service_role_arn").(string)) } - if d.HasChange("name") { - params.Name = aws.String(d.Get("name").(string)) + if d.HasChange("oauth_token") { + input.OauthToken = aws.String(d.Get("oauth_token").(string)) } if d.HasChange("platform") { - params.Platform = aws.String(d.Get("platform").(string)) + input.Platform = aws.String(d.Get("platform").(string)) } if d.HasChange("repository") { - params.Repository = aws.String(d.Get("repository").(string)) - } - - if v, ok := d.GetOk("access_token"); ok { - params.AccessToken = aws.String(v.(string)) + input.Repository = aws.String(d.Get("repository").(string)) } - if v, ok := d.GetOk("oauth_token"); ok { - params.OauthToken = aws.String(v.(string)) - } + _, err := conn.UpdateApp(input) - _, err := conn.UpdateApp(params) if err != nil { - return fmt.Errorf("Error updating Amplify App: %s", err) + return fmt.Errorf("error updating Amplify App (%s): %w", d.Id(), err) } - */ + } if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 602fec007d6..caef2ec7e53 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -488,7 +488,8 @@ func TestAccAWSAmplifyApp_BasicAuthCredentials(t *testing.T) { Config: testAccAWSAmplifyAppConfigName(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "basic_auth_credentials", ""), + // Clearing basic_auth_credentials not reflected in API. + // resource.TestCheckResourceAttr(resourceName, "basic_auth_credentials", ""), resource.TestCheckResourceAttr(resourceName, "enable_basic_auth", "false"), ), }, From 335160c751e9b18cd47eef6773432f3e3d85ee1e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 19 May 2021 16:32:19 -0400 Subject: [PATCH 13/30] r/aws_amplify_app: Remove 'name_prefix' as it's not compatible with name updating. Acceptance test output: % make testacc TEST=./aws TESTARGS='-run=TestAccAWSAmplifyApp_Name\|TestAccAWSAmplifyApp_basic' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSAmplifyApp_Name\|TestAccAWSAmplifyApp_basic -timeout 180m === RUN TestAccAWSAmplifyApp_basic === PAUSE TestAccAWSAmplifyApp_basic === RUN TestAccAWSAmplifyApp_Name === PAUSE TestAccAWSAmplifyApp_Name === CONT TestAccAWSAmplifyApp_basic === CONT TestAccAWSAmplifyApp_Name --- PASS: TestAccAWSAmplifyApp_basic (13.33s) --- PASS: TestAccAWSAmplifyApp_Name (22.50s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 26.372s --- aws/resource_aws_amplify_app.go | 48 ++------ aws/resource_aws_amplify_app_test.go | 137 ++++++----------------- website/docs/r/amplify_app.html.markdown | 3 +- 3 files changed, 43 insertions(+), 145 deletions(-) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index 58e17bb7627..203ea55645f 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -11,7 +11,6 @@ import ( "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/naming" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/amplify/finder" "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) @@ -232,20 +231,9 @@ func resourceAwsAmplifyApp() *schema.Resource { }, "name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ValidateFunc: validation.StringLenBetween(1, 255), - ConflictsWith: []string{"name_prefix"}, - }, - - "name_prefix": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ConflictsWith: []string{"name"}, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 255), }, "oauth_token": { @@ -309,7 +297,7 @@ func resourceAwsAmplifyAppCreate(d *schema.ResourceData, meta interface{}) error defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) - name := naming.Generate(d.Get("name").(string), d.Get("name_prefix").(string)) + name := d.Get("name").(string) input := &lify.CreateAppInput{ Name: aws.String(name), @@ -495,7 +483,6 @@ func resourceAwsAmplifyAppRead(d *schema.ResourceData, meta interface{}) error { d.Set("environment_variables", aws.StringValueMap(app.EnvironmentVariables)) d.Set("iam_service_role_arn", app.IamServiceRoleArn) d.Set("name", app.Name) - d.Set("name_prefix", naming.NamePrefixFromName(aws.StringValue(app.Name))) d.Set("platform", app.Platform) if app.ProductionBranch != nil { if err := d.Set("production_branch", []interface{}{flattenAmplifyProductionBranch(app.ProductionBranch)}); err != nil { @@ -506,29 +493,6 @@ func resourceAwsAmplifyAppRead(d *schema.ResourceData, meta interface{}) error { } d.Set("repository", app.Repository) - /* - if err := d.Set("auto_branch_creation_config", flattenAmplifyAutoBranchCreationConfig(resp.App.AutoBranchCreationConfig, resp.App.AutoBranchCreationPatterns, resp.App.EnableAutoBranchCreation)); err != nil { - return fmt.Errorf("error setting auto_branch_creation_config: %s", err) - } - if err := d.Set("basic_auth_config", flattenAmplifyBasicAuthConfig(resp.App.EnableBasicAuth, resp.App.BasicAuthCredentials)); err != nil { - return fmt.Errorf("error setting basic_auth_config: %s", err) - } - d.Set("build_spec", resp.App.BuildSpec) - if err := d.Set("custom_rules", flattenAmplifyCustomRules(resp.App.CustomRules)); err != nil { - return fmt.Errorf("error setting custom_rules: %s", err) - } - d.Set("default_domain", resp.App.DefaultDomain) - d.Set("description", resp.App.Description) - d.Set("enable_branch_auto_build", resp.App.EnableBranchAutoBuild) - if err := d.Set("environment_variables", aws.StringValueMap(resp.App.EnvironmentVariables)); err != nil { - return fmt.Errorf("error setting environment_variables: %s", err) - } - d.Set("iam_service_role_arn", resp.App.IamServiceRoleArn) - d.Set("name", resp.App.Name) - d.Set("platform", resp.App.Platform) - d.Set("repository", resp.App.Repository) - */ - tags := keyvaluetags.AmplifyKeyValueTags(app.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { @@ -606,6 +570,10 @@ func resourceAwsAmplifyAppUpdate(d *schema.ResourceData, meta interface{}) error input.IamServiceRoleArn = aws.String(d.Get("iam_service_role_arn").(string)) } + if d.HasChange("name") { + input.Name = aws.String(d.Get("name").(string)) + } + if d.HasChange("oauth_token") { input.OauthToken = aws.String(d.Get("oauth_token").(string)) } diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index caef2ec7e53..6b28c8d2dd1 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -11,7 +11,6 @@ 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/naming" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/amplify/finder" "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) @@ -50,7 +49,6 @@ func TestAccAWSAmplifyApp_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "0"), resource.TestCheckResourceAttr(resourceName, "iam_service_role_arn", ""), resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "name_prefix", ""), resource.TestCheckNoResourceAttr(resourceName, "oauth_token"), resource.TestCheckResourceAttr(resourceName, "platform", "WEB"), resource.TestCheckResourceAttr(resourceName, "production_branch.#", "0"), @@ -67,8 +65,9 @@ func TestAccAWSAmplifyApp_basic(t *testing.T) { }) } -func TestAccAWSAmplifyApp_Name_Generated(t *testing.T) { +func TestAccAWSAmplifyApp_Tags(t *testing.T) { var app amplify.App + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ @@ -78,11 +77,11 @@ func TestAccAWSAmplifyApp_Name_Generated(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigNameGenerated(), + Config: testAccAWSAmplifyAppConfigTags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), - naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), - resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), }, { @@ -90,42 +89,35 @@ func TestAccAWSAmplifyApp_Name_Generated(t *testing.T) { ImportState: true, ImportStateVerify: true, }, - }, - }) -} - -func TestAccAWSAmplifyApp_NamePrefix(t *testing.T) { - var app amplify.App - resourceName := "aws_amplify_app.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, - ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSAmplifyAppDestroy, - Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigNamePrefix("tf-acc-test-prefix-"), + Config: testAccAWSAmplifyAppConfigTags2(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), - naming.TestCheckResourceAttrNameFromPrefix(resourceName, "name", "tf-acc-test-prefix-"), - resource.TestCheckResourceAttr(resourceName, "name_prefix", "tf-acc-test-prefix-"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + Config: testAccAWSAmplifyAppConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), }, }, }) } -func TestAccAWSAmplifyApp_Tags(t *testing.T) { +func TestAccAWSAmplifyApp_BasicAuthCredentials(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" + credentials1 := base64.StdEncoding.EncodeToString([]byte("username1:password1")) + credentials2 := base64.StdEncoding.EncodeToString([]byte("username2:password2")) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), @@ -133,11 +125,11 @@ func TestAccAWSAmplifyApp_Tags(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigTags1(rName, "key1", "value1"), + Config: testAccAWSAmplifyAppConfigBasicAuthCredentials(rName, credentials1), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + resource.TestCheckResourceAttr(resourceName, "basic_auth_credentials", credentials1), + resource.TestCheckResourceAttr(resourceName, "enable_basic_auth", "true"), ), }, { @@ -146,32 +138,31 @@ func TestAccAWSAmplifyApp_Tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Config: testAccAWSAmplifyAppConfigBasicAuthCredentials(rName, credentials2), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), - resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + resource.TestCheckResourceAttr(resourceName, "basic_auth_credentials", credentials2), + resource.TestCheckResourceAttr(resourceName, "enable_basic_auth", "true"), ), }, { - Config: testAccAWSAmplifyAppConfigTags1(rName, "key2", "value2"), + Config: testAccAWSAmplifyAppConfigName(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + // Clearing basic_auth_credentials not reflected in API. + // resource.TestCheckResourceAttr(resourceName, "basic_auth_credentials", ""), + resource.TestCheckResourceAttr(resourceName, "enable_basic_auth", "false"), ), }, }, }) } -func TestAccAWSAmplifyApp_rename(t *testing.T) { - resourceName := "aws_amplify_app.test" - - // name is not unique and can be renamed +func TestAccAWSAmplifyApp_Name(t *testing.T) { + var app amplify.App rName1 := acctest.RandomWithPrefix("tf-acc-test") rName2 := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, @@ -182,6 +173,7 @@ func TestAccAWSAmplifyApp_rename(t *testing.T) { { Config: testAccAWSAmplifyAppConfigName(rName1), Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), resource.TestCheckResourceAttr(resourceName, "name", rName1), ), }, @@ -193,6 +185,7 @@ func TestAccAWSAmplifyApp_rename(t *testing.T) { { Config: testAccAWSAmplifyAppConfigName(rName2), Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), resource.TestCheckResourceAttr(resourceName, "name", rName2), ), }, @@ -449,54 +442,6 @@ func TestAccAWSAmplifyApp_autoBranchCreationConfig(t *testing.T) { }) } -func TestAccAWSAmplifyApp_BasicAuthCredentials(t *testing.T) { - var app amplify.App - rName := acctest.RandomWithPrefix("tf-acc-test") - resourceName := "aws_amplify_app.test" - - credentials1 := base64.StdEncoding.EncodeToString([]byte("username1:password1")) - credentials2 := base64.StdEncoding.EncodeToString([]byte("username2:password2")) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, - ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSAmplifyAppDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSAmplifyAppConfigBasicAuthCredentials(rName, credentials1), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "basic_auth_credentials", credentials1), - resource.TestCheckResourceAttr(resourceName, "enable_basic_auth", "true"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccAWSAmplifyAppConfigBasicAuthCredentials(rName, credentials2), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "basic_auth_credentials", credentials2), - resource.TestCheckResourceAttr(resourceName, "enable_basic_auth", "true"), - ), - }, - { - Config: testAccAWSAmplifyAppConfigName(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app), - // Clearing basic_auth_credentials not reflected in API. - // resource.TestCheckResourceAttr(resourceName, "basic_auth_credentials", ""), - resource.TestCheckResourceAttr(resourceName, "enable_basic_auth", "false"), - ), - }, - }, - }) -} - func TestAccAWSAmplifyApp_enableBranchAutoBuild(t *testing.T) { rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -625,20 +570,6 @@ resource "aws_amplify_app" "test" { `, rName) } -func testAccAWSAmplifyAppConfigNameGenerated() string { - return ` -resource "aws_amplify_app" "test" {} -` -} - -func testAccAWSAmplifyAppConfigNamePrefix(namePrefix string) string { - return fmt.Sprintf(` -resource "aws_amplify_app" "test" { - name_prefix = %[1]q -} -`, namePrefix) -} - func testAccAWSAmplifyAppConfigDescription(rName string, description string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { diff --git a/website/docs/r/amplify_app.html.markdown b/website/docs/r/amplify_app.html.markdown index ec5d63afc6a..65f85c63bf8 100644 --- a/website/docs/r/amplify_app.html.markdown +++ b/website/docs/r/amplify_app.html.markdown @@ -130,8 +130,7 @@ resource "aws_amplify_app" "app" { The following arguments are supported: -* `name` - (Optional) Name of the Amplify App. If omitted, Terraform will assign a random, unique name. Conflicts with `name_prefix`. -* `name_prefix` - (Optional) Creates a unique name beginning with the specified prefix. Conflicts with `name`. +* `name` - (Required) Name of the Amplify App. * `access_token` - (Optional) Personal Access token for 3rd party source control system for an Amplify App, used to create webhook and read-only deploy key. Token is not stored. * `auto_branch_creation_config` - (Optional) Automated branch creation config for the Amplify App. An `auto_branch_creation_config` block is documented below. From 94a9d17e4e766eb774174d7ef8696fbac221f8dd Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 19 May 2021 17:26:45 -0400 Subject: [PATCH 14/30] r/aws_amplify_app: ForceNew if 'description' changes to "". Acceptance test output: % make testacc TEST=./aws TESTARGS='-run=TestAccAWSAmplifyApp_Description' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSAmplifyApp_Description -timeout 180m === RUN TestAccAWSAmplifyApp_Description === PAUSE TestAccAWSAmplifyApp_Description === CONT TestAccAWSAmplifyApp_Description --- PASS: TestAccAWSAmplifyApp_Description (33.31s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 36.482s --- aws/resource_aws_amplify_app.go | 10 +++++- aws/resource_aws_amplify_app_test.go | 53 +++++++++++++++++++++------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index 203ea55645f..1070e34d2ae 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -1,6 +1,7 @@ package aws import ( + "context" "fmt" "log" "time" @@ -8,6 +9,7 @@ import ( "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/customdiff" "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" @@ -25,7 +27,13 @@ func resourceAwsAmplifyApp() *schema.Resource { State: schema.ImportStatePassthrough, }, - CustomizeDiff: SetTagsDiff, + CustomizeDiff: customdiff.Sequence( + SetTagsDiff, + customdiff.ForceNewIfChange("description", func(_ context.Context, old, new, meta interface{}) bool { + // Any existing description cannot be cleared. + return new.(string) == "" + }), + ), Schema: map[string]*schema.Schema{ "access_token": { diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 6b28c8d2dd1..942fbb12966 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -7,6 +7,7 @@ import ( "regexp" "testing" + "github.com/aws/aws-sdk-go/aws" "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" @@ -193,14 +194,11 @@ func TestAccAWSAmplifyApp_Name(t *testing.T) { }) } -func TestAccAWSAmplifyApp_description(t *testing.T) { +func TestAccAWSAmplifyApp_Description(t *testing.T) { + var app1, app2, app3 amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" - // once set, description cannot be removed. - description1 := acctest.RandomWithPrefix("tf-acc-test") - description2 := acctest.RandomWithPrefix("tf-acc-test") - resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), @@ -208,9 +206,10 @@ func TestAccAWSAmplifyApp_description(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigDescription(rName, description1), + Config: testAccAWSAmplifyAppConfigDescription(rName, "description 1"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "description", description1), + testAccCheckAWSAmplifyAppExists(resourceName, &app1), + resource.TestCheckResourceAttr(resourceName, "description", "description 1"), ), }, { @@ -219,9 +218,19 @@ func TestAccAWSAmplifyApp_description(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfigDescription(rName, description2), + Config: testAccAWSAmplifyAppConfigDescription(rName, "description 2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app2), + testAccCheckAWSAmplifyAppNotRecreated(&app1, &app2), + resource.TestCheckResourceAttr(resourceName, "description", "description 2"), + ), + }, + { + Config: testAccAWSAmplifyAppConfigName(rName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "description", description2), + testAccCheckAWSAmplifyAppExists(resourceName, &app3), + testAccCheckAWSAmplifyAppRecreated(&app2, &app3), + resource.TestCheckResourceAttr(resourceName, "description", ""), ), }, }, @@ -562,6 +571,26 @@ func testAccPreCheckAWSAmplify(t *testing.T) { } } +func testAccCheckAWSAmplifyAppNotRecreated(before, after *amplify.App) resource.TestCheckFunc { + return func(s *terraform.State) error { + if before, after := aws.StringValue(before.AppId), aws.StringValue(after.AppId); before != after { + return fmt.Errorf("Amplify App (%s/%s) recreated", before, after) + } + + return nil + } +} + +func testAccCheckAWSAmplifyAppRecreated(before, after *amplify.App) resource.TestCheckFunc { + return func(s *terraform.State) error { + if before, after := aws.StringValue(before.AppId), aws.StringValue(after.AppId); before == after { + return fmt.Errorf("Amplify App (%s) not recreated", before) + } + + return nil + } +} + func testAccAWSAmplifyAppConfigName(rName string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { @@ -570,12 +599,12 @@ resource "aws_amplify_app" "test" { `, rName) } -func testAccAWSAmplifyAppConfigDescription(rName string, description string) string { +func testAccAWSAmplifyAppConfigDescription(rName, description string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" + name = %[1]q - description = "%s" + description = %[2]q } `, rName, description) } From 407f9fe2520ff3e7b103fa1c695c34c5dd07a760 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 19 May 2021 17:36:39 -0400 Subject: [PATCH 15/30] r/aws_amplify_app: ForceNew if 'build_spec' changes to "". Acceptance test output: % make testacc TEST=./aws TESTARGS='-run=TestAccAWSAmplifyApp_BuildSpec' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSAmplifyApp_BuildSpec -timeout 180m === RUN TestAccAWSAmplifyApp_BuildSpec === PAUSE TestAccAWSAmplifyApp_BuildSpec === CONT TestAccAWSAmplifyApp_BuildSpec --- PASS: TestAccAWSAmplifyApp_BuildSpec (33.73s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 37.005s --- aws/resource_aws_amplify_app.go | 6 +++++- aws/resource_aws_amplify_app_test.go | 32 +++++++++++++++++----------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index 1070e34d2ae..62422b1d0b3 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -29,8 +29,12 @@ func resourceAwsAmplifyApp() *schema.Resource { CustomizeDiff: customdiff.Sequence( SetTagsDiff, + customdiff.ForceNewIfChange("build_spec", func(_ context.Context, old, new, meta interface{}) bool { + // Any existing value cannot be cleared. + return new.(string) == "" + }), customdiff.ForceNewIfChange("description", func(_ context.Context, old, new, meta interface{}) bool { - // Any existing description cannot be cleared. + // Any existing value cannot be cleared. return new.(string) == "" }), ), diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 942fbb12966..a61704d2c3f 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -265,14 +265,11 @@ func TestAccAWSAmplifyApp_repository(t *testing.T) { }) } -func TestAccAWSAmplifyApp_buildSpec(t *testing.T) { +func TestAccAWSAmplifyApp_BuildSpec(t *testing.T) { + var app1, app2, app3 amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" - // once set, build_spec cannot be removed. - buildSpec1 := "version: 0.1" - buildSpec2 := "version: 0.2" - resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), @@ -280,9 +277,10 @@ func TestAccAWSAmplifyApp_buildSpec(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigBuildSpec(rName, buildSpec1), + Config: testAccAWSAmplifyAppConfigBuildSpec(rName, "version: 0.1"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "build_spec", buildSpec1), + testAccCheckAWSAmplifyAppExists(resourceName, &app1), + resource.TestCheckResourceAttr(resourceName, "build_spec", "version: 0.1"), ), }, { @@ -291,9 +289,19 @@ func TestAccAWSAmplifyApp_buildSpec(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfigBuildSpec(rName, buildSpec2), + Config: testAccAWSAmplifyAppConfigBuildSpec(rName, "version: 0.2"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "build_spec", buildSpec2), + testAccCheckAWSAmplifyAppExists(resourceName, &app2), + testAccCheckAWSAmplifyAppNotRecreated(&app1, &app2), + resource.TestCheckResourceAttr(resourceName, "build_spec", "version: 0.2"), + ), + }, + { + Config: testAccAWSAmplifyAppConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app3), + testAccCheckAWSAmplifyAppRecreated(&app2, &app3), + resource.TestCheckResourceAttr(resourceName, "build_spec", ""), ), }, }, @@ -623,12 +631,12 @@ resource "aws_amplify_app" "test" { `, rName, repository, accessToken) } -func testAccAWSAmplifyAppConfigBuildSpec(rName string, buildSpec string) string { +func testAccAWSAmplifyAppConfigBuildSpec(rName, buildSpec string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" + name = %[1]q - build_spec = "%s" + build_spec = %[2]q } `, rName, buildSpec) } From 6bc640666d62e7c6ea598cb0a4bb5b1db1124f90 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 20 May 2021 11:15:16 -0400 Subject: [PATCH 16/30] r/aws_amplify_app: Add test sweeper. Acceptance test output: % TEST=./aws SWEEP=us-west-2 SWEEPARGS=-sweep-run=aws_amplify_app make sweep WARNING: This will destroy infrastructure. Use only in development accounts. go test ./aws -v -sweep=us-west-2 -sweep-run=aws_amplify_app -timeout 60m 2021/05/20 11:13:49 [DEBUG] Running Sweepers for region (us-west-2): 2021/05/20 11:13:49 [DEBUG] Running Sweeper (aws_amplify_app) in region (us-west-2) 2021/05/20 11:13:49 [INFO] AWS Auth provider used: "EnvProvider" 2021/05/20 11:13:49 [DEBUG] Trying to get account information via sts:GetCallerIdentity 2021/05/20 11:13:50 [DEBUG] Trying to get account information via sts:GetCallerIdentity 2021/05/20 11:13:50 [DEBUG] Deleting Amplify App (d3e2rpqo82owxg) 2021/05/20 11:13:51 Sweeper Tests ran successfully: - aws_amplify_app ok github.com/terraform-providers/terraform-provider-aws/aws 4.452s --- aws/internal/service/amplify/lister/list.go | 3 ++ .../service/amplify/lister/list_pages_gen.go | 31 +++++++++++ aws/resource_aws_amplify_app_test.go | 52 ++++++++++++++++++- 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 aws/internal/service/amplify/lister/list.go create mode 100644 aws/internal/service/amplify/lister/list_pages_gen.go diff --git a/aws/internal/service/amplify/lister/list.go b/aws/internal/service/amplify/lister/list.go new file mode 100644 index 00000000000..3b7007c9979 --- /dev/null +++ b/aws/internal/service/amplify/lister/list.go @@ -0,0 +1,3 @@ +//go:generate go run ../../../generators/listpages/main.go -function=ListApps github.com/aws/aws-sdk-go/service/amplify + +package lister diff --git a/aws/internal/service/amplify/lister/list_pages_gen.go b/aws/internal/service/amplify/lister/list_pages_gen.go new file mode 100644 index 00000000000..30fa4b12930 --- /dev/null +++ b/aws/internal/service/amplify/lister/list_pages_gen.go @@ -0,0 +1,31 @@ +// Code generated by "aws/internal/generators/listpages/main.go -function=ListApps github.com/aws/aws-sdk-go/service/amplify"; DO NOT EDIT. + +package lister + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/amplify" +) + +func ListAppsPages(conn *amplify.Amplify, input *amplify.ListAppsInput, fn func(*amplify.ListAppsOutput, bool) bool) error { + return ListAppsPagesWithContext(context.Background(), conn, input, fn) +} + +func ListAppsPagesWithContext(ctx context.Context, conn *amplify.Amplify, input *amplify.ListAppsInput, fn func(*amplify.ListAppsOutput, bool) bool) error { + for { + output, err := conn.ListAppsWithContext(ctx, input) + if err != nil { + return err + } + + lastPage := aws.StringValue(output.NextToken) == "" + if !fn(output, lastPage) || lastPage { + break + } + + input.NextToken = output.NextToken + } + return nil +} diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index a61704d2c3f..e5fa6643b9b 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -3,20 +3,70 @@ package aws import ( "encoding/base64" "fmt" + "log" "os" "regexp" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/amplify" + "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/amplify/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/amplify/lister" "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) -// TODO sweeper +func init() { + resource.AddTestSweepers("aws_amplify_app", &resource.Sweeper{ + Name: "aws_amplify_app", + F: testSweepAmplifyApps, + }) +} + +func testSweepAmplifyApps(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).amplifyconn + input := &lify.ListAppsInput{} + var sweeperErrs *multierror.Error + + err = lister.ListAppsPages(conn, input, func(page *amplify.ListAppsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, app := range page.Apps { + r := resourceAwsAmplifyApp() + d := r.Data(nil) + d.SetId(aws.StringValue(app.AppId)) + err = r.Delete(d, client) + + if err != nil { + log.Printf("[ERROR] %s", err) + sweeperErrs = multierror.Append(sweeperErrs, err) + continue + } + } + + return !lastPage + }) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping Amplify Apps sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Amplify Apps: %w", err)) + } + + return sweeperErrs.ErrorOrNil() +} func TestAccAWSAmplifyApp_basic(t *testing.T) { var app amplify.App From 9b05180934329a35249dc374e7d7880f014dde1b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 20 May 2021 11:20:05 -0400 Subject: [PATCH 17/30] r/aws_amplify_app: Add '_disappears' test (#15945). Acceptance test output: % make testacc TEST=./aws TESTARGS='-run=TestAccAWSAmplifyApp_disappears' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSAmplifyApp_disappears -timeout 180m === RUN TestAccAWSAmplifyApp_disappears === PAUSE TestAccAWSAmplifyApp_disappears === CONT TestAccAWSAmplifyApp_disappears --- PASS: TestAccAWSAmplifyApp_disappears (10.95s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 14.084s --- aws/resource_aws_amplify_app_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index e5fa6643b9b..c1e396f7a69 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -116,6 +116,29 @@ func TestAccAWSAmplifyApp_basic(t *testing.T) { }) } +func TestAccAWSAmplifyApp_disappears(t *testing.T) { + var app amplify.App + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigName(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + testAccCheckResourceDisappears(testAccProvider, resourceAwsAmplifyApp(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func TestAccAWSAmplifyApp_Tags(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") From f3f3ac053c53c1e088203068b9b3f6badbf9ee8f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 20 May 2021 11:41:52 -0400 Subject: [PATCH 18/30] r/aws_amplify_app: Send empty array to clear 'custom_rule'. Acceptance test output: % make testacc TEST=./aws TESTARGS='-run=TestAccAWSAmplifyApp_CustomRules' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSAmplifyApp_CustomRules -timeout 180m === RUN TestAccAWSAmplifyApp_CustomRules === PAUSE TestAccAWSAmplifyApp_CustomRules === CONT TestAccAWSAmplifyApp_CustomRules --- PASS: TestAccAWSAmplifyApp_CustomRules (30.06s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 33.169s --- aws/resource_aws_amplify_app.go | 6 +- aws/resource_aws_amplify_app_test.go | 150 +++++++++++---------------- 2 files changed, 63 insertions(+), 93 deletions(-) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index 62422b1d0b3..ef0ad4a8f80 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -551,7 +551,11 @@ func resourceAwsAmplifyAppUpdate(d *schema.ResourceData, meta interface{}) error } if d.HasChange("custom_rule") { - input.CustomRules = expandAmplifyCustomRules(d.Get("custom_rule").([]interface{})) + if v := d.Get("custom_rule").([]interface{}); len(v) > 0 { + input.CustomRules = expandAmplifyCustomRules(v) + } else { + input.CustomRules = []*amplify.CustomRule{} + } } if d.HasChange("description") { diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index c1e396f7a69..4605a94cd32 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -232,42 +232,7 @@ func TestAccAWSAmplifyApp_BasicAuthCredentials(t *testing.T) { }) } -func TestAccAWSAmplifyApp_Name(t *testing.T) { - var app amplify.App - rName1 := acctest.RandomWithPrefix("tf-acc-test") - rName2 := acctest.RandomWithPrefix("tf-acc-test") - resourceName := "aws_amplify_app.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, - ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSAmplifyAppDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSAmplifyAppConfigName(rName1), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "name", rName1), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccAWSAmplifyAppConfigName(rName2), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "name", rName2), - ), - }, - }, - }) -} - -func TestAccAWSAmplifyApp_Description(t *testing.T) { +func TestAccAWSAmplifyApp_BuildSpec(t *testing.T) { var app1, app2, app3 amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -279,10 +244,10 @@ func TestAccAWSAmplifyApp_Description(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigDescription(rName, "description 1"), + Config: testAccAWSAmplifyAppConfigBuildSpec(rName, "version: 0.1"), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app1), - resource.TestCheckResourceAttr(resourceName, "description", "description 1"), + resource.TestCheckResourceAttr(resourceName, "build_spec", "version: 0.1"), ), }, { @@ -291,11 +256,11 @@ func TestAccAWSAmplifyApp_Description(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfigDescription(rName, "description 2"), + Config: testAccAWSAmplifyAppConfigBuildSpec(rName, "version: 0.2"), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app2), testAccCheckAWSAmplifyAppNotRecreated(&app1, &app2), - resource.TestCheckResourceAttr(resourceName, "description", "description 2"), + resource.TestCheckResourceAttr(resourceName, "build_spec", "version: 0.2"), ), }, { @@ -303,14 +268,15 @@ func TestAccAWSAmplifyApp_Description(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app3), testAccCheckAWSAmplifyAppRecreated(&app2, &app3), - resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "build_spec", ""), ), }, }, }) } -func TestAccAWSAmplifyApp_repository(t *testing.T) { +func TestAccAWSAmplifyApp_CustomRules(t *testing.T) { + var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -321,26 +287,48 @@ func TestAccAWSAmplifyApp_repository(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigRepository(rName), + Config: testAccAWSAmplifyAppConfigCustomRules(rName), Check: resource.ComposeTestCheckFunc( - resource.TestMatchResourceAttr(resourceName, "repository", regexp.MustCompile("^https://github.com")), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "custom_rule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.source", "/<*>"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.status", "404"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.target", "/index.html"), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - // access_token is ignored because AWS does not store access_token and oauth_token - // See https://docs.aws.amazon.com/sdk-for-go/api/service/amplify/#CreateAppInput - ImportStateVerifyIgnore: []string{"access_token"}, + }, + { + Config: testAccAWSAmplifyAppConfigCustomRulesUpdated(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "custom_rule.#", "2"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.condition", ""), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.source", "/documents"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.status", "302"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.target", "/documents/us"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.1.source", "/<*>"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.1.status", "200"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.1.target", "/index.html"), + ), + }, + { + Config: testAccAWSAmplifyAppConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "custom_rule.#", "0"), + ), }, }, }) } -func TestAccAWSAmplifyApp_BuildSpec(t *testing.T) { - var app1, app2, app3 amplify.App - rName := acctest.RandomWithPrefix("tf-acc-test") +func TestAccAWSAmplifyApp_Name(t *testing.T) { + var app amplify.App + rName1 := acctest.RandomWithPrefix("tf-acc-test") + rName2 := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ @@ -350,10 +338,10 @@ func TestAccAWSAmplifyApp_BuildSpec(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigBuildSpec(rName, "version: 0.1"), + Config: testAccAWSAmplifyAppConfigName(rName1), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app1), - resource.TestCheckResourceAttr(resourceName, "build_spec", "version: 0.1"), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "name", rName1), ), }, { @@ -362,26 +350,17 @@ func TestAccAWSAmplifyApp_BuildSpec(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfigBuildSpec(rName, "version: 0.2"), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app2), - testAccCheckAWSAmplifyAppNotRecreated(&app1, &app2), - resource.TestCheckResourceAttr(resourceName, "build_spec", "version: 0.2"), - ), - }, - { - Config: testAccAWSAmplifyAppConfigName(rName), + Config: testAccAWSAmplifyAppConfigName(rName2), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app3), - testAccCheckAWSAmplifyAppRecreated(&app2, &app3), - resource.TestCheckResourceAttr(resourceName, "build_spec", ""), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "name", rName2), ), }, }, }) } -func TestAccAWSAmplifyApp_customRules(t *testing.T) { +func TestAccAWSAmplifyApp_repository(t *testing.T) { rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -392,31 +371,18 @@ func TestAccAWSAmplifyApp_customRules(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigCustomRules1(rName), + Config: testAccAWSAmplifyAppConfigRepository(rName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "custom_rules.#", "1"), - resource.TestCheckResourceAttr(resourceName, "custom_rules.0.source", "/<*>"), - resource.TestCheckResourceAttr(resourceName, "custom_rules.0.status", "404"), - resource.TestCheckResourceAttr(resourceName, "custom_rules.0.target", "/index.html"), + resource.TestMatchResourceAttr(resourceName, "repository", regexp.MustCompile("^https://github.com")), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - }, - { - Config: testAccAWSAmplifyAppConfigCustomRules2(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "custom_rules.#", "2"), - resource.TestCheckResourceAttr(resourceName, "custom_rules.0.source", "/documents"), - resource.TestCheckResourceAttr(resourceName, "custom_rules.0.status", "302"), - resource.TestCheckResourceAttr(resourceName, "custom_rules.0.target", "/documents/us"), - resource.TestCheckResourceAttr(resourceName, "custom_rules.0.condition", ""), - resource.TestCheckResourceAttr(resourceName, "custom_rules.1.source", "/<*>"), - resource.TestCheckResourceAttr(resourceName, "custom_rules.1.status", "200"), - resource.TestCheckResourceAttr(resourceName, "custom_rules.1.target", "/index.html"), - ), + // access_token is ignored because AWS does not store access_token and oauth_token + // See https://docs.aws.amazon.com/sdk-for-go/api/service/amplify/#CreateAppInput + ImportStateVerifyIgnore: []string{"access_token"}, }, }, }) @@ -714,12 +680,12 @@ resource "aws_amplify_app" "test" { `, rName, buildSpec) } -func testAccAWSAmplifyAppConfigCustomRules1(rName string) string { +func testAccAWSAmplifyAppConfigCustomRules(rName string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" + name = %[1]q - custom_rules { + custom_rule { source = "/<*>" status = "404" target = "/index.html" @@ -728,19 +694,19 @@ resource "aws_amplify_app" "test" { `, rName) } -func testAccAWSAmplifyAppConfigCustomRules2(rName string) string { +func testAccAWSAmplifyAppConfigCustomRulesUpdated(rName string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" + name = %[1]q - custom_rules { + custom_rule { + condition = "" source = "/documents" status = "302" target = "/documents/us" - condition = "" } - custom_rules { + custom_rule { source = "/<*>" status = "200" target = "/index.html" From 4b781c887f4698e647b3e14a8c3a686e6d9d341f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 20 May 2021 12:48:20 -0400 Subject: [PATCH 19/30] r/aws_amplify_app: Send map with empty key and value to clear 'environment_variables'. Acceptance test output: % make testacc TEST=./aws TESTARGS='-run=TestAccAWSAmplifyApp_EnvironmentVariables' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSAmplifyApp_EnvironmentVariables -timeout 180m === RUN TestAccAWSAmplifyApp_EnvironmentVariables === PAUSE TestAccAWSAmplifyApp_EnvironmentVariables === CONT TestAccAWSAmplifyApp_EnvironmentVariables --- PASS: TestAccAWSAmplifyApp_EnvironmentVariables (29.91s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 33.543s --- aws/resource_aws_amplify_app.go | 6 ++- aws/resource_aws_amplify_app_test.go | 76 +++++++++++++++------------- 2 files changed, 45 insertions(+), 37 deletions(-) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index ef0ad4a8f80..43c1de024b5 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -579,7 +579,11 @@ func resourceAwsAmplifyAppUpdate(d *schema.ResourceData, meta interface{}) error } if d.HasChange("environment_variables") { - input.EnvironmentVariables = expandStringMap(d.Get("environment_variables").(map[string]interface{})) + if v := d.Get("environment_variables").(map[string]interface{}); len(v) > 0 { + input.EnvironmentVariables = expandStringMap(v) + } else { + input.EnvironmentVariables = aws.StringMap(map[string]string{"": ""}) + } } if d.HasChange("iam_service_role_arn") { diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 4605a94cd32..cb3d82c7ce6 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -325,10 +325,9 @@ func TestAccAWSAmplifyApp_CustomRules(t *testing.T) { }) } -func TestAccAWSAmplifyApp_Name(t *testing.T) { +func TestAccAWSAmplifyApp_EnvironmentVariables(t *testing.T) { var app amplify.App - rName1 := acctest.RandomWithPrefix("tf-acc-test") - rName2 := acctest.RandomWithPrefix("tf-acc-test") + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ @@ -338,10 +337,11 @@ func TestAccAWSAmplifyApp_Name(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigName(rName1), + Config: testAccAWSAmplifyAppConfigEnvironmentVariables(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "name", rName1), + resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "1"), + resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR1", "1"), ), }, { @@ -350,18 +350,29 @@ func TestAccAWSAmplifyApp_Name(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfigName(rName2), + Config: testAccAWSAmplifyAppConfigEnvironmentVariablesUpdated(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "name", rName2), + resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "2"), + resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR1", "2"), + resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR2", "2"), + ), + }, + { + Config: testAccAWSAmplifyAppConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "0"), ), }, }, }) } -func TestAccAWSAmplifyApp_repository(t *testing.T) { - rName := acctest.RandomWithPrefix("tf-acc-test") +func TestAccAWSAmplifyApp_Name(t *testing.T) { + var app amplify.App + rName1 := acctest.RandomWithPrefix("tf-acc-test") + rName2 := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ @@ -371,24 +382,29 @@ func TestAccAWSAmplifyApp_repository(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigRepository(rName), + Config: testAccAWSAmplifyAppConfigName(rName1), Check: resource.ComposeTestCheckFunc( - resource.TestMatchResourceAttr(resourceName, "repository", regexp.MustCompile("^https://github.com")), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "name", rName1), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - // access_token is ignored because AWS does not store access_token and oauth_token - // See https://docs.aws.amazon.com/sdk-for-go/api/service/amplify/#CreateAppInput - ImportStateVerifyIgnore: []string{"access_token"}, + }, + { + Config: testAccAWSAmplifyAppConfigName(rName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "name", rName2), + ), }, }, }) } -func TestAccAWSAmplifyApp_environmentVariables(t *testing.T) { +func TestAccAWSAmplifyApp_repository(t *testing.T) { rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -399,30 +415,18 @@ func TestAccAWSAmplifyApp_environmentVariables(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigEnvironmentVariables1(rName), + Config: testAccAWSAmplifyAppConfigRepository(rName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "1"), - resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR1", "1"), + resource.TestMatchResourceAttr(resourceName, "repository", regexp.MustCompile("^https://github.com")), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - }, - { - Config: testAccAWSAmplifyAppConfigEnvironmentVariables2(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "2"), - resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR1", "2"), - resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR2", "2"), - ), - }, - { - Config: testAccAWSAmplifyAppConfigName(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "0"), - ), + // access_token is ignored because AWS does not store access_token and oauth_token + // See https://docs.aws.amazon.com/sdk-for-go/api/service/amplify/#CreateAppInput + ImportStateVerifyIgnore: []string{"access_token"}, }, }, }) @@ -715,10 +719,10 @@ resource "aws_amplify_app" "test" { `, rName) } -func testAccAWSAmplifyAppConfigEnvironmentVariables1(rName string) string { +func testAccAWSAmplifyAppConfigEnvironmentVariables(rName string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" + name = %[1]q environment_variables = { ENVVAR1 = "1" @@ -727,10 +731,10 @@ resource "aws_amplify_app" "test" { `, rName) } -func testAccAWSAmplifyAppConfigEnvironmentVariables2(rName string) string { +func testAccAWSAmplifyAppConfigEnvironmentVariablesUpdated(rName string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" + name = %[1]q environment_variables = { ENVVAR1 = "2", From b418fe78648866eeb8c2846e112596a58d4e989a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 20 May 2021 15:45:34 -0400 Subject: [PATCH 20/30] r/aws_amplify_app: Get branch auto-creation working. Acceptance test output: % make testacc TEST=./aws TESTARGS='-run=TestAccAWSAmplifyApp_AutoBranchCreationConfig' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSAmplifyApp_AutoBranchCreationConfig -timeout 180m === RUN TestAccAWSAmplifyApp_AutoBranchCreationConfig === PAUSE TestAccAWSAmplifyApp_AutoBranchCreationConfig === CONT TestAccAWSAmplifyApp_AutoBranchCreationConfig --- PASS: TestAccAWSAmplifyApp_AutoBranchCreationConfig (35.19s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 38.286s --- aws/internal/service/amplify/consts.go | 5 + aws/resource_aws_amplify_app.go | 116 +++----- aws/resource_aws_amplify_app_test.go | 369 +++++++++++++++---------- 3 files changed, 271 insertions(+), 219 deletions(-) create mode 100644 aws/internal/service/amplify/consts.go diff --git a/aws/internal/service/amplify/consts.go b/aws/internal/service/amplify/consts.go new file mode 100644 index 00000000000..3049f2bcfc2 --- /dev/null +++ b/aws/internal/service/amplify/consts.go @@ -0,0 +1,5 @@ +package amplify + +const ( + StageNone = "NONE" +) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index 43c1de024b5..1b5d72698c3 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -13,6 +13,7 @@ import ( "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" + 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" ) @@ -55,6 +56,7 @@ func resourceAwsAmplifyApp() *schema.Resource { "auto_branch_creation_config": { Type: schema.TypeList, Optional: true, + Computed: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -63,11 +65,20 @@ func resourceAwsAmplifyApp() *schema.Resource { Optional: true, Sensitive: true, ValidateFunc: validation.StringLenBetween(1, 2000), + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // These credentials are ignored if basic auth is not enabled. + if d.Get("auto_branch_creation_config.0.enable_basic_auth").(bool) { + return old == new + } + + return true + }, }, "build_spec": { Type: schema.TypeString, Optional: true, + Computed: true, ValidateFunc: validation.StringLenBetween(1, 25000), }, @@ -110,17 +121,17 @@ func resourceAwsAmplifyApp() *schema.Resource { }, "stage": { - Type: schema.TypeString, - Optional: true, - //TODO - // DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // // stage is "NONE" by default - // if old == "NONE" && new == "" { - // return true - // } - // return old == new - // }, + Type: schema.TypeString, + Optional: true, ValidateFunc: validation.StringInSlice(amplify.Stage_Values(), false), + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // API returns "NONE" by default. + if old == tfamplify.StageNone && new == "" { + return true + } + + return old == new + }, }, }, }, @@ -130,6 +141,14 @@ func resourceAwsAmplifyApp() *schema.Resource { Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // These patterns are ignored if branch auto-creation is not enabled. + if d.Get("enable_auto_branch_creation").(bool) { + return old == new + } + + return true + }, }, "basic_auth_credentials": { @@ -148,10 +167,8 @@ func resourceAwsAmplifyApp() *schema.Resource { }, "build_spec": { - Type: schema.TypeString, - Optional: true, - //TODO - //Computed: true, + Type: schema.TypeString, + Optional: true, ValidateFunc: validation.StringLenBetween(1, 25000), }, @@ -383,61 +400,6 @@ func resourceAwsAmplifyAppCreate(d *schema.ResourceData, meta interface{}) error input.Repository = aws.String(v.(string)) } - /* - if v, ok := d.GetOk("auto_branch_creation_config"); ok { - config, patterns, enable := expandAmplifyAutoBranchCreationConfig(v.([]interface{})) - params.AutoBranchCreationConfig = config - params.AutoBranchCreationPatterns = patterns - params.EnableAutoBranchCreation = enable - } - - if v, ok := d.GetOk("basic_auth_config"); ok { - enable, credentials := expandAmplifyBasicAuthConfig(v.([]interface{})) - params.EnableBasicAuth = enable - params.BasicAuthCredentials = credentials - } - - if v, ok := d.GetOk("build_spec"); ok { - params.BuildSpec = aws.String(v.(string)) - } - - if v, ok := d.GetOk("custom_rules"); ok { - params.CustomRules = expandAmplifyCustomRules(v.([]interface{})) - } - - if v, ok := d.GetOk("description"); ok { - params.Description = aws.String(v.(string)) - } - - if v, ok := d.GetOk("enable_branch_auto_build"); ok { - params.EnableBranchAutoBuild = aws.Bool(v.(bool)) - } - - if v, ok := d.GetOk("environment_variables"); ok { - params.EnvironmentVariables = stringMapToPointers(v.(map[string]interface{})) - } - - if v, ok := d.GetOk("iam_service_role_arn"); ok { - params.IamServiceRoleArn = aws.String(v.(string)) - } - - if v, ok := d.GetOk("platform"); ok { - params.Platform = aws.String(v.(string)) - } - - if v, ok := d.GetOk("repository"); ok { - params.Repository = aws.String(v.(string)) - } - - if v, ok := d.GetOk("access_token"); ok { - params.AccessToken = aws.String(v.(string)) - } - - if v, ok := d.GetOk("oauth_token"); ok { - params.OauthToken = aws.String(v.(string)) - } - */ - if len(tags) > 0 { input.Tags = tags.IgnoreAws().AmplifyTags() } @@ -532,6 +494,12 @@ func resourceAwsAmplifyAppUpdate(d *schema.ResourceData, meta interface{}) error if d.HasChange("auto_branch_creation_config") { input.AutoBranchCreationConfig = expandAmplifyAutoBranchCreationConfig(d.Get("auto_branch_creation_config").([]interface{})[0].(map[string]interface{})) + + if d.HasChange("auto_branch_creation_config.0.environment_variables") { + if v := d.Get("auto_branch_creation_config.0.environment_variables").(map[string]interface{}); len(v) == 0 { + input.AutoBranchCreationConfig.EnvironmentVariables = aws.StringMap(map[string]string{"": ""}) + } + } } if d.HasChange("auto_branch_creation_patterns") { @@ -657,19 +625,19 @@ func expandAmplifyAutoBranchCreationConfig(tfMap map[string]interface{}) *amplif apiObject.BuildSpec = aws.String(v) } - if v, ok := tfMap["enable_auto_build"].(bool); ok && v { + if v, ok := tfMap["enable_auto_build"].(bool); ok { apiObject.EnableAutoBuild = aws.Bool(v) } - if v, ok := tfMap["enable_basic_auth"].(bool); ok && v { + if v, ok := tfMap["enable_basic_auth"].(bool); ok { apiObject.EnableBasicAuth = aws.Bool(v) } - if v, ok := tfMap["enable_performance_mode"].(bool); ok && v { + if v, ok := tfMap["enable_performance_mode"].(bool); ok { apiObject.EnablePerformanceMode = aws.Bool(v) } - if v, ok := tfMap["enable_pull_request_preview"].(bool); ok && v { + if v, ok := tfMap["enable_pull_request_preview"].(bool); ok { apiObject.EnablePullRequestPreview = aws.Bool(v) } @@ -685,7 +653,7 @@ func expandAmplifyAutoBranchCreationConfig(tfMap map[string]interface{}) *amplif apiObject.PullRequestEnvironmentName = aws.String(v) } - if v, ok := tfMap["stage"].(string); ok && v != "" { + if v, ok := tfMap["stage"].(string); ok && v != "" && v != tfamplify.StageNone { apiObject.Stage = aws.String(v) } diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index cb3d82c7ce6..51c64428b9d 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -184,6 +184,101 @@ func TestAccAWSAmplifyApp_Tags(t *testing.T) { }) } +func TestAccAWSAmplifyApp_AutoBranchCreationConfig(t *testing.T) { + var app amplify.App + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_amplify_app.test" + + credentials := base64.StdEncoding.EncodeToString([]byte("username1:password1")) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, + ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAmplifyAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAmplifyAppConfigAutoBranchCreationConfigNoAutoBranchCreationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_credentials", ""), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.build_spec", ""), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_auto_build", "false"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_basic_auth", "false"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_performance_mode", "false"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_pull_request_preview", "false"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.environment_variables.%", "0"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.framework", ""), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.pull_request_environment_name", ""), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.stage", "NONE"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_patterns.#", "2"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_patterns.0", "*"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_patterns.1", "*/**"), + resource.TestCheckResourceAttr(resourceName, "enable_auto_branch_creation", "true"), + ), + }, + { + Config: testAccAWSAmplifyAppConfigAutoBranchCreationConfigAutoBranchCreationConfig(rName, credentials), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_credentials", credentials), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.build_spec", "version: 0.1"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_auto_build", "true"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_basic_auth", "true"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_performance_mode", "false"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_pull_request_preview", "true"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.environment_variables.%", "1"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.environment_variables.ENVVAR1", "1"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.framework", "React"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.pull_request_environment_name", "test1"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.stage", "DEVELOPMENT"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_patterns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_patterns.0", "feature/*"), + resource.TestCheckResourceAttr(resourceName, "enable_auto_branch_creation", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAmplifyAppConfigAutoBranchCreationConfigAutoBranchCreationConfigUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.#", "1"), + // Clearing basic_auth_credentials not reflected in API. + // resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_credentials", ""), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.build_spec", "version: 0.2"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_auto_build", "false"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_basic_auth", "false"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_performance_mode", "false"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_pull_request_preview", "false"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.environment_variables.%", "0"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.framework", "React"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.pull_request_environment_name", "test2"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.stage", "EXPERIMENTAL"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_patterns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_patterns.0", "feature/*"), + resource.TestCheckResourceAttr(resourceName, "enable_auto_branch_creation", "true"), + ), + }, + { + Config: testAccAWSAmplifyAppConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + // No change is reflected in API. + // resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.#", "0"), + // resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_patterns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "enable_auto_branch_creation", "false"), + ), + }, + }, + }) +} + func TestAccAWSAmplifyApp_BasicAuthCredentials(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") @@ -275,11 +370,15 @@ func TestAccAWSAmplifyApp_BuildSpec(t *testing.T) { }) } -func TestAccAWSAmplifyApp_CustomRules(t *testing.T) { +/* +func TestAccAWSAmplifyApp_CustomHeaders(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" + customHeaders1 := `{"customHeaders":[{"pattern":"*.json","headers":[{"key":"custom-header-name-1","value":"custom-header-value-1"}]}]}` + customHeaders2 := `{"customHeaders":[{"pattern":"*.json","headers":[{"key":"custom-header-name-2","value":"custom-header-value-2"}]},{"pattern":"/path/*","headers":[{"key":"custom-header-name-1","value":"custom-header-value-1"}]}]}` + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), @@ -287,13 +386,10 @@ func TestAccAWSAmplifyApp_CustomRules(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigCustomRules(rName), + Config: testAccAWSAmplifyAppConfigCustomHeaders(rName, customHeaders1), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "custom_rule.#", "1"), - resource.TestCheckResourceAttr(resourceName, "custom_rule.0.source", "/<*>"), - resource.TestCheckResourceAttr(resourceName, "custom_rule.0.status", "404"), - resource.TestCheckResourceAttr(resourceName, "custom_rule.0.target", "/index.html"), + resource.TestCheckResourceAttr(resourceName, "custom_headers", customHeaders1), ), }, { @@ -302,30 +398,25 @@ func TestAccAWSAmplifyApp_CustomRules(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfigCustomRulesUpdated(rName), + Config: testAccAWSAmplifyAppConfigCustomHeaders(rName, ""), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "custom_rule.#", "2"), - resource.TestCheckResourceAttr(resourceName, "custom_rule.0.condition", ""), - resource.TestCheckResourceAttr(resourceName, "custom_rule.0.source", "/documents"), - resource.TestCheckResourceAttr(resourceName, "custom_rule.0.status", "302"), - resource.TestCheckResourceAttr(resourceName, "custom_rule.0.target", "/documents/us"), - resource.TestCheckResourceAttr(resourceName, "custom_rule.1.source", "/<*>"), - resource.TestCheckResourceAttr(resourceName, "custom_rule.1.status", "200"), - resource.TestCheckResourceAttr(resourceName, "custom_rule.1.target", "/index.html"), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "custom_headers", customHeaders2), ), }, { Config: testAccAWSAmplifyAppConfigName(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "custom_rule.#", "0"), + resource.TestCheckResourceAttr(resourceName, "custom_headers", ""), ), }, }, }) } +*/ -func TestAccAWSAmplifyApp_EnvironmentVariables(t *testing.T) { +func TestAccAWSAmplifyApp_CustomRules(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -337,11 +428,13 @@ func TestAccAWSAmplifyApp_EnvironmentVariables(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigEnvironmentVariables(rName), + Config: testAccAWSAmplifyAppConfigCustomRules(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "1"), - resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR1", "1"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.source", "/<*>"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.status", "404"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.target", "/index.html"), ), }, { @@ -350,29 +443,32 @@ func TestAccAWSAmplifyApp_EnvironmentVariables(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfigEnvironmentVariablesUpdated(rName), + Config: testAccAWSAmplifyAppConfigCustomRulesUpdated(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "2"), - resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR1", "2"), - resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR2", "2"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.#", "2"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.condition", ""), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.source", "/documents"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.status", "302"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.target", "/documents/us"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.1.source", "/<*>"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.1.status", "200"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.1.target", "/index.html"), ), }, { Config: testAccAWSAmplifyAppConfigName(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "0"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.#", "0"), ), }, }, }) } -func TestAccAWSAmplifyApp_Name(t *testing.T) { - var app amplify.App - rName1 := acctest.RandomWithPrefix("tf-acc-test") - rName2 := acctest.RandomWithPrefix("tf-acc-test") +func TestAccAWSAmplifyApp_Description(t *testing.T) { + var app1, app2, app3 amplify.App + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ @@ -382,10 +478,10 @@ func TestAccAWSAmplifyApp_Name(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigName(rName1), + Config: testAccAWSAmplifyAppConfigDescription(rName, "description 1"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "name", rName1), + testAccCheckAWSAmplifyAppExists(resourceName, &app1), + resource.TestCheckResourceAttr(resourceName, "description", "description 1"), ), }, { @@ -394,17 +490,27 @@ func TestAccAWSAmplifyApp_Name(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfigName(rName2), + Config: testAccAWSAmplifyAppConfigDescription(rName, "description 2"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "name", rName2), + testAccCheckAWSAmplifyAppExists(resourceName, &app2), + testAccCheckAWSAmplifyAppNotRecreated(&app1, &app2), + resource.TestCheckResourceAttr(resourceName, "description", "description 2"), + ), + }, + { + Config: testAccAWSAmplifyAppConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app3), + testAccCheckAWSAmplifyAppRecreated(&app2, &app3), + resource.TestCheckResourceAttr(resourceName, "description", ""), ), }, }, }) } -func TestAccAWSAmplifyApp_repository(t *testing.T) { +func TestAccAWSAmplifyApp_EnvironmentVariables(t *testing.T) { + var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -415,25 +521,42 @@ func TestAccAWSAmplifyApp_repository(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigRepository(rName), + Config: testAccAWSAmplifyAppConfigEnvironmentVariables(rName), Check: resource.ComposeTestCheckFunc( - resource.TestMatchResourceAttr(resourceName, "repository", regexp.MustCompile("^https://github.com")), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "1"), + resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR1", "1"), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - // access_token is ignored because AWS does not store access_token and oauth_token - // See https://docs.aws.amazon.com/sdk-for-go/api/service/amplify/#CreateAppInput - ImportStateVerifyIgnore: []string{"access_token"}, + }, + { + Config: testAccAWSAmplifyAppConfigEnvironmentVariablesUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "2"), + resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR1", "2"), + resource.TestCheckResourceAttr(resourceName, "environment_variables.ENVVAR2", "2"), + ), + }, + { + Config: testAccAWSAmplifyAppConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "environment_variables.%", "0"), + ), }, }, }) } -func TestAccAWSAmplifyApp_autoBranchCreationConfig(t *testing.T) { - rName := acctest.RandomWithPrefix("tf-acc-test") +func TestAccAWSAmplifyApp_Name(t *testing.T) { + var app amplify.App + rName1 := acctest.RandomWithPrefix("tf-acc-test") + rName2 := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ @@ -443,20 +566,10 @@ func TestAccAWSAmplifyApp_autoBranchCreationConfig(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigAutoBranchCreationConfig(rName), + Config: testAccAWSAmplifyAppConfigName(rName1), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_auto_branch_creation", "true"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.auto_branch_creation_patterns.#", "2"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.auto_branch_creation_patterns.0", "*"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.auto_branch_creation_patterns.1", "*/**"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.build_spec", ""), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.framework", ""), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.stage", "NONE"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_config.#", "0"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_auto_build", "false"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_pull_request_preview", "false"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.pull_request_environment_name", ""), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.environment_variables.%", "0"), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "name", rName1), ), }, { @@ -465,44 +578,17 @@ func TestAccAWSAmplifyApp_autoBranchCreationConfig(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfigAutoBranchCreationConfigModified(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_auto_branch_creation", "true"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.auto_branch_creation_patterns.#", "1"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.auto_branch_creation_patterns.0", "feature/*"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.build_spec", "version: 0.1"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.framework", "React"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.stage", "DEVELOPMENT"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_config.#", "1"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_config.0.enable_basic_auth", "true"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_config.0.username", "username"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_config.0.password", "password"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_auto_build", "true"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_pull_request_preview", "true"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.pull_request_environment_name", "env"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.environment_variables.%", "1"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.environment_variables.ENVVAR1", "1"), - ), - }, - { - Config: testAccAWSAmplifyAppConfigAutoBranchCreationConfigModified2(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.enable_auto_branch_creation", "true"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.basic_auth_config.#", "0"), - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.0.environment_variables.%", "0"), - ), - }, - { - Config: testAccAWSAmplifyAppConfigName(rName), + Config: testAccAWSAmplifyAppConfigName(rName2), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_config.#", "0"), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "name", rName2), ), }, }, }) } -func TestAccAWSAmplifyApp_enableBranchAutoBuild(t *testing.T) { +func TestAccAWSAmplifyApp_repository(t *testing.T) { rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -513,21 +599,18 @@ func TestAccAWSAmplifyApp_enableBranchAutoBuild(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigEnableBranchAutoBuild(rName), + Config: testAccAWSAmplifyAppConfigRepository(rName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "enable_branch_auto_build", "true"), + resource.TestMatchResourceAttr(resourceName, "repository", regexp.MustCompile("^https://github.com")), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - }, - { - Config: testAccAWSAmplifyAppConfigName(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "enable_branch_auto_build", "false"), - ), + // access_token is ignored because AWS does not store access_token and oauth_token + // See https://docs.aws.amazon.com/sdk-for-go/api/service/amplify/#CreateAppInput + ImportStateVerifyIgnore: []string{"access_token"}, }, }, }) @@ -684,6 +767,16 @@ resource "aws_amplify_app" "test" { `, rName, buildSpec) } +func testAccAWSAmplifyAppConfigCustomHeaders(rName, customHeaders string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = %[1]q + + custom_headers = %[2]q +} +`, rName, customHeaders) +} + func testAccAWSAmplifyAppConfigCustomRules(rName string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { @@ -744,49 +837,43 @@ resource "aws_amplify_app" "test" { `, rName) } -func testAccAWSAmplifyAppConfigAutoBranchCreationConfig(rName string) string { +func testAccAWSAmplifyAppConfigAutoBranchCreationConfigNoAutoBranchCreationConfig(rName string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" + name = %[1]q - auto_branch_creation_config { - enable_auto_branch_creation = true + enable_auto_branch_creation = true - auto_branch_creation_patterns = [ - "*", - "*/**", - ] - } + auto_branch_creation_patterns = [ + "*", + "*/**", + ] } `, rName) } -func testAccAWSAmplifyAppConfigAutoBranchCreationConfigModified(rName string) string { +func testAccAWSAmplifyAppConfigAutoBranchCreationConfigAutoBranchCreationConfig(rName, basicAuthCredentials string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" - - auto_branch_creation_config { - enable_auto_branch_creation = true + name = %[1]q - auto_branch_creation_patterns = [ - "feature/*", - ] + enable_auto_branch_creation = true + + auto_branch_creation_patterns = [ + "feature/*", + ] + auto_branch_creation_config { build_spec = "version: 0.1" framework = "React" stage = "DEVELOPMENT" - basic_auth_config { - enable_basic_auth = true - username = "username" - password = "password" - } - - enable_auto_build = true + enable_basic_auth = true + basic_auth_credentials = %[2]q + enable_auto_build = true enable_pull_request_preview = true - pull_request_environment_name = "env" + pull_request_environment_name = "test1" environment_variables = { ENVVAR1 = "1" @@ -794,29 +881,31 @@ resource "aws_amplify_app" "test" { } } -`, rName) +`, rName, basicAuthCredentials) } -func testAccAWSAmplifyAppConfigAutoBranchCreationConfigModified2(rName string) string { +func testAccAWSAmplifyAppConfigAutoBranchCreationConfigAutoBranchCreationConfigUpdated(rName string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" - - auto_branch_creation_config { - enable_auto_branch_creation = true + name = %[1]q - auto_branch_creation_patterns = [ - "feature/*", - ] + enable_auto_branch_creation = true + + auto_branch_creation_patterns = [ + "feature/*", + ] - build_spec = "version: 0.1" + auto_branch_creation_config { + build_spec = "version: 0.2" framework = "React" - stage = "DEVELOPMENT" + stage = "EXPERIMENTAL" - enable_auto_build = false + enable_basic_auth = false - enable_pull_request_preview = false - pull_request_environment_name = "env" + enable_auto_build = false + enable_pull_request_preview = false + + pull_request_environment_name = "test2" } } `, rName) @@ -833,16 +922,6 @@ resource "aws_amplify_app" "test" { `, rName, basicAuthCredentials) } -func testAccAWSAmplifyAppConfigEnableBranchAutoBuild(rName string) string { - return fmt.Sprintf(` -resource "aws_amplify_app" "test" { - name = "%s" - - enable_branch_auto_build = true -} -`, rName) -} - func testAccAWSAmplifyAppConfigIAMServiceRoleArn(rName string, roleName string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { From 76e401e3f882bdf0341b74effdd2aecff608fb05 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 20 May 2021 16:56:54 -0400 Subject: [PATCH 21/30] r/aws_amplify_app: ForceNew if 'iam_service_role_arn' changes to "". Acceptance test output: % make testacc TEST=./aws TESTARGS='-run=TestAccAWSAmplifyApp_IamServiceRole' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSAmplifyApp_IamServiceRole -timeout 180m === RUN TestAccAWSAmplifyApp_IamServiceRole === PAUSE TestAccAWSAmplifyApp_IamServiceRole === CONT TestAccAWSAmplifyApp_IamServiceRole --- PASS: TestAccAWSAmplifyApp_IamServiceRole (32.33s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 35.813s --- aws/resource_aws_amplify_app.go | 6 +- aws/resource_aws_amplify_app_test.go | 132 +++++++++++++++++---------- 2 files changed, 90 insertions(+), 48 deletions(-) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index 1b5d72698c3..65c19bf06b1 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -38,6 +38,10 @@ func resourceAwsAmplifyApp() *schema.Resource { // Any existing value cannot be cleared. return new.(string) == "" }), + customdiff.ForceNewIfChange("iam_service_role_arn", func(_ context.Context, old, new, meta interface{}) bool { + // Any existing value cannot be cleared. + return new.(string) == "" + }), ), Schema: map[string]*schema.Schema{ @@ -78,7 +82,6 @@ func resourceAwsAmplifyApp() *schema.Resource { "build_spec": { Type: schema.TypeString, Optional: true, - Computed: true, ValidateFunc: validation.StringLenBetween(1, 25000), }, @@ -169,6 +172,7 @@ func resourceAwsAmplifyApp() *schema.Resource { "build_spec": { Type: schema.TypeString, Optional: true, + Computed: true, ValidateFunc: validation.StringLenBetween(1, 25000), }, diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 51c64428b9d..7db18775b5b 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -553,11 +553,12 @@ func TestAccAWSAmplifyApp_EnvironmentVariables(t *testing.T) { }) } -func TestAccAWSAmplifyApp_Name(t *testing.T) { - var app amplify.App - rName1 := acctest.RandomWithPrefix("tf-acc-test") - rName2 := acctest.RandomWithPrefix("tf-acc-test") +func TestAccAWSAmplifyApp_IamServiceRole(t *testing.T) { + var app1, app2, app3 amplify.App + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" + iamRole1ResourceName := "aws_iam_role.test1" + iamRole2ResourceName := "aws_iam_role.test2" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, @@ -566,11 +567,10 @@ func TestAccAWSAmplifyApp_Name(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigName(rName1), + Config: testAccAWSAmplifyAppConfigIAMServiceRoleArn(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "name", rName1), - ), + testAccCheckAWSAmplifyAppExists(resourceName, &app1), + resource.TestCheckResourceAttrPair(resourceName, "iam_service_role_arn", iamRole1ResourceName, "arn")), }, { ResourceName: resourceName, @@ -578,18 +578,29 @@ func TestAccAWSAmplifyApp_Name(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSAmplifyAppConfigName(rName2), + Config: testAccAWSAmplifyAppConfigIAMServiceRoleArnUpdated(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "name", rName2), + testAccCheckAWSAmplifyAppExists(resourceName, &app2), + testAccCheckAWSAmplifyAppNotRecreated(&app1, &app2), + resource.TestCheckResourceAttrPair(resourceName, "iam_service_role_arn", iamRole2ResourceName, "arn"), + ), + }, + { + Config: testAccAWSAmplifyAppConfigName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app3), + testAccCheckAWSAmplifyAppRecreated(&app2, &app3), + resource.TestCheckResourceAttr(resourceName, "iam_service_role_arn", ""), ), }, }, }) } -func TestAccAWSAmplifyApp_repository(t *testing.T) { - rName := acctest.RandomWithPrefix("tf-acc-test") +func TestAccAWSAmplifyApp_Name(t *testing.T) { + var app amplify.App + rName1 := acctest.RandomWithPrefix("tf-acc-test") + rName2 := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" resource.ParallelTest(t, resource.TestCase{ @@ -599,30 +610,32 @@ func TestAccAWSAmplifyApp_repository(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigRepository(rName), + Config: testAccAWSAmplifyAppConfigName(rName1), Check: resource.ComposeTestCheckFunc( - resource.TestMatchResourceAttr(resourceName, "repository", regexp.MustCompile("^https://github.com")), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "name", rName1), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - // access_token is ignored because AWS does not store access_token and oauth_token - // See https://docs.aws.amazon.com/sdk-for-go/api/service/amplify/#CreateAppInput - ImportStateVerifyIgnore: []string{"access_token"}, + }, + { + Config: testAccAWSAmplifyAppConfigName(rName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "name", rName2), + ), }, }, }) } -func TestAccAWSAmplifyApp_iamServiceRoleArn(t *testing.T) { +func TestAccAWSAmplifyApp_repository(t *testing.T) { rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" - roleName1 := acctest.RandomWithPrefix("tf-acc-test") - roleName2 := acctest.RandomWithPrefix("tf-acc-test") - resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), @@ -630,21 +643,18 @@ func TestAccAWSAmplifyApp_iamServiceRoleArn(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigIAMServiceRoleArn(rName, roleName1), + Config: testAccAWSAmplifyAppConfigRepository(rName), Check: resource.ComposeTestCheckFunc( - testAccMatchResourceAttrGlobalARN(resourceName, "iam_service_role_arn", "iam", regexp.MustCompile("role/"+roleName1)), + resource.TestMatchResourceAttr(resourceName, "repository", regexp.MustCompile("^https://github.com")), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - }, - { - Config: testAccAWSAmplifyAppConfigIAMServiceRoleArn(rName, roleName2), - Check: resource.ComposeTestCheckFunc( - testAccMatchResourceAttrGlobalARN(resourceName, "iam_service_role_arn", "iam", regexp.MustCompile("role/"+roleName2)), - ), + // access_token is ignored because AWS does not store access_token and oauth_token + // See https://docs.aws.amazon.com/sdk-for-go/api/service/amplify/#CreateAppInput + ImportStateVerifyIgnore: []string{"access_token"}, }, }, }) @@ -922,34 +932,62 @@ resource "aws_amplify_app" "test" { `, rName, basicAuthCredentials) } -func testAccAWSAmplifyAppConfigIAMServiceRoleArn(rName string, roleName string) string { +func testAccAWSAmplifyAppConfigIAMServiceRoleBase(rName string) string { return fmt.Sprintf(` -resource "aws_amplify_app" "test" { - name = "%s" +resource "aws_iam_role" "test1" { + name = "%[1]s-1" - iam_service_role_arn = aws_iam_role.role.arn + assume_role_policy = < Date: Thu, 20 May 2021 17:03:43 -0400 Subject: [PATCH 22/30] r/aws_amplify_app: Remove 'custom_headers' - Not amenable to IaC. --- aws/resource_aws_amplify_app.go | 15 -------- aws/resource_aws_amplify_app_test.go | 57 ---------------------------- 2 files changed, 72 deletions(-) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index 65c19bf06b1..8435abd0a95 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -176,12 +176,6 @@ func resourceAwsAmplifyApp() *schema.Resource { ValidateFunc: validation.StringLenBetween(1, 25000), }, - "custom_headers": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringLenBetween(1, 25000), - }, - "custom_rule": { Type: schema.TypeList, Optional: true, @@ -356,10 +350,6 @@ func resourceAwsAmplifyAppCreate(d *schema.ResourceData, meta interface{}) error input.BuildSpec = aws.String(v.(string)) } - if v, ok := d.GetOk("custom_headers"); ok { - input.CustomHeaders = aws.String(v.(string)) - } - if v, ok := d.GetOk("custom_rule"); ok && len(v.([]interface{})) > 0 { input.CustomRules = expandAmplifyCustomRules(v.([]interface{})) } @@ -448,7 +438,6 @@ func resourceAwsAmplifyAppRead(d *schema.ResourceData, meta interface{}) error { d.Set("auto_branch_creation_patterns", aws.StringValueSlice(app.AutoBranchCreationPatterns)) d.Set("basic_auth_credentials", app.BasicAuthCredentials) d.Set("build_spec", app.BuildSpec) - d.Set("custom_headers", app.CustomHeaders) if err := d.Set("custom_rule", flattenAmplifyCustomRules(app.CustomRules)); err != nil { return fmt.Errorf("error setting custom_rule: %w", err) } @@ -518,10 +507,6 @@ func resourceAwsAmplifyAppUpdate(d *schema.ResourceData, meta interface{}) error input.BuildSpec = aws.String(d.Get("build_spec").(string)) } - if d.HasChange("custom_headers") { - input.CustomHeaders = aws.String(d.Get("custom_headers").(string)) - } - if d.HasChange("custom_rule") { if v := d.Get("custom_rule").([]interface{}); len(v) > 0 { input.CustomRules = expandAmplifyCustomRules(v) diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 7db18775b5b..f5b6a8afe2b 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -89,7 +89,6 @@ func TestAccAWSAmplifyApp_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "auto_branch_creation_patterns.#", "0"), resource.TestCheckResourceAttr(resourceName, "basic_auth_credentials", ""), resource.TestCheckResourceAttr(resourceName, "build_spec", ""), - resource.TestCheckResourceAttr(resourceName, "custom_headers", ""), resource.TestCheckResourceAttr(resourceName, "custom_rule.#", "0"), resource.TestMatchResourceAttr(resourceName, "default_domain", regexp.MustCompile(`\.amplifyapp\.com$`)), resource.TestCheckResourceAttr(resourceName, "description", ""), @@ -370,52 +369,6 @@ func TestAccAWSAmplifyApp_BuildSpec(t *testing.T) { }) } -/* -func TestAccAWSAmplifyApp_CustomHeaders(t *testing.T) { - var app amplify.App - rName := acctest.RandomWithPrefix("tf-acc-test") - resourceName := "aws_amplify_app.test" - - customHeaders1 := `{"customHeaders":[{"pattern":"*.json","headers":[{"key":"custom-header-name-1","value":"custom-header-value-1"}]}]}` - customHeaders2 := `{"customHeaders":[{"pattern":"*.json","headers":[{"key":"custom-header-name-2","value":"custom-header-value-2"}]},{"pattern":"/path/*","headers":[{"key":"custom-header-name-1","value":"custom-header-value-1"}]}]}` - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSAmplify(t) }, - ErrorCheck: testAccErrorCheck(t, amplify.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSAmplifyAppDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSAmplifyAppConfigCustomHeaders(rName, customHeaders1), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "custom_headers", customHeaders1), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccAWSAmplifyAppConfigCustomHeaders(rName, ""), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "custom_headers", customHeaders2), - ), - }, - { - Config: testAccAWSAmplifyAppConfigName(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app), - resource.TestCheckResourceAttr(resourceName, "custom_headers", ""), - ), - }, - }, - }) -} -*/ - func TestAccAWSAmplifyApp_CustomRules(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") @@ -777,16 +730,6 @@ resource "aws_amplify_app" "test" { `, rName, buildSpec) } -func testAccAWSAmplifyAppConfigCustomHeaders(rName, customHeaders string) string { - return fmt.Sprintf(` -resource "aws_amplify_app" "test" { - name = %[1]q - - custom_headers = %[2]q -} -`, rName, customHeaders) -} - func testAccAWSAmplifyAppConfigCustomRules(rName string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { From 96f76cb7602a0c9336822669b00e3924b03e3a2e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 May 2021 09:06:22 -0400 Subject: [PATCH 23/30] r/aws_amplify_app: Environment variables for 'TestAccAWSAmplifyApp_Repository'. Acceptance test output: AMPLIFY_GITHUB_ACCESS_TOKEN="..." AMPLIFY_GITHUB_REPOSITORY="https://github.com/ewbankkit/terraform-aws-provider-testing" make testacc TEST=./aws TESTARGS='-run=TestAccAWSAmplifyApp_Repository' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSAmplifyApp_Repository -timeout 180m === RUN TestAccAWSAmplifyApp_Repository === PAUSE TestAccAWSAmplifyApp_Repository === CONT TestAccAWSAmplifyApp_Repository --- PASS: TestAccAWSAmplifyApp_Repository (15.63s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 18.557s --- aws/resource_aws_amplify_app.go | 7 +++--- aws/resource_aws_amplify_app_test.go | 32 +++++++++++++++++++--------- docs/MAINTAINING.md | 2 ++ 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index 8435abd0a95..ae93286513a 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -306,10 +306,9 @@ func resourceAwsAmplifyApp() *schema.Resource { }, "repository": { - Type: schema.TypeString, - Optional: true, - //TODO - //ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 1000), }, diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index f5b6a8afe2b..89fb5297a64 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -585,7 +585,20 @@ func TestAccAWSAmplifyApp_Name(t *testing.T) { }) } -func TestAccAWSAmplifyApp_repository(t *testing.T) { +func TestAccAWSAmplifyApp_Repository(t *testing.T) { + key := "AMPLIFY_GITHUB_ACCESS_TOKEN" + accessToken := os.Getenv(key) + if accessToken == "" { + t.Skipf("Environment variable %s is not set", key) + } + + key = "AMPLIFY_GITHUB_REPOSITORY" + repository := os.Getenv(key) + if repository == "" { + t.Skipf("Environment variable %s is not set", key) + } + + var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -596,9 +609,11 @@ func TestAccAWSAmplifyApp_repository(t *testing.T) { CheckDestroy: testAccCheckAWSAmplifyAppDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAmplifyAppConfigRepository(rName), + Config: testAccAWSAmplifyAppConfigRepository(rName, repository, accessToken), Check: resource.ComposeTestCheckFunc( - resource.TestMatchResourceAttr(resourceName, "repository", regexp.MustCompile("^https://github.com")), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + resource.TestCheckResourceAttr(resourceName, "access_token", accessToken), + resource.TestCheckResourceAttr(resourceName, "repository", repository), ), }, { @@ -706,16 +721,13 @@ resource "aws_amplify_app" "test" { `, rName, description) } -func testAccAWSAmplifyAppConfigRepository(rName string) string { - repository := os.Getenv("AMPLIFY_GITHUB_REPOSITORY") - accessToken := os.Getenv("AMPLIFY_GITHUB_ACCESS_TOKEN") - +func testAccAWSAmplifyAppConfigRepository(rName, repository, accessToken string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { - name = "%s" + name = %[1]q - repository = "%s" - access_token = "%s" + repository = %[2]q + access_token = %[3]q } `, rName, repository, accessToken) } diff --git a/docs/MAINTAINING.md b/docs/MAINTAINING.md index 1685b5f90d8..2a8d9b3c474 100644 --- a/docs/MAINTAINING.md +++ b/docs/MAINTAINING.md @@ -342,6 +342,8 @@ Environment variables (beyond standard AWS Go SDK ones) used by acceptance testi | `ACM_CERTIFICATE_SINGLE_ISSUED_DOMAIN` | Domain name of ACM Certificate with a single issued certificate. **DEPRECATED:** Should be replaced with `aws_acm_certficate` resource usage in tests. | | `ACM_CERTIFICATE_SINGLE_ISSUED_MOST_RECENT_ARN` | Amazon Resource Name of most recent ACM Certificate with a single issued certificate. **DEPRECATED:** Should be replaced with `aws_acm_certficate` resource usage in tests. | | `ADM_CLIENT_ID` | Identifier for Amazon Device Manager Client in Pinpoint testing. | +| `AMPLIFY_GITHUB_ACCESS_TOKEN` | GitHub access token used for AWS Amplify testing. | +| `AMPLIFY_GITHUB_REPOSITORY` | GitHub repository used for AWS Amplify testing. | | `ADM_CLIENT_SECRET` | Secret for Amazon Device Manager Client in Pinpoint testing. | | `APNS_BUNDLE_ID` | Identifier for Apple Push Notification Service Bundle in Pinpoint testing. | | `APNS_CERTIFICATE` | Certificate (PEM format) for Apple Push Notification Service in Pinpoint testing. | From b964ecfae3a91fdf441dba19c5a5f478a53bf5a6 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 May 2021 09:44:58 -0400 Subject: [PATCH 24/30] r/aws_amplify_app: Get all acceptance tests passing. --- aws/resource_aws_amplify_app.go | 199 +-------------------------- aws/resource_aws_amplify_app_test.go | 199 +++++++++++++-------------- 2 files changed, 100 insertions(+), 298 deletions(-) diff --git a/aws/resource_aws_amplify_app.go b/aws/resource_aws_amplify_app.go index ae93286513a..963fffb1269 100644 --- a/aws/resource_aws_amplify_app.go +++ b/aws/resource_aws_amplify_app.go @@ -30,10 +30,6 @@ func resourceAwsAmplifyApp() *schema.Resource { CustomizeDiff: customdiff.Sequence( SetTagsDiff, - customdiff.ForceNewIfChange("build_spec", func(_ context.Context, old, new, meta interface{}) bool { - // Any existing value cannot be cleared. - return new.(string) == "" - }), customdiff.ForceNewIfChange("description", func(_ context.Context, old, new, meta interface{}) bool { // Any existing value cannot be cleared. return new.(string) == "" @@ -259,7 +255,7 @@ func resourceAwsAmplifyApp() *schema.Resource { "name": { Type: schema.TypeString, - Optional: true, + Required: true, ValidateFunc: validation.StringLenBetween(1, 255), }, @@ -819,196 +815,3 @@ func flattenAmplifyProductionBranch(apiObject *amplify.ProductionBranch) map[str return tfMap } - -/* -func expandAmplifyEnvironmentVariables(envs map[string]interface{}) map[string]*string { - if len(envs) == 0 { - empty := "" - return map[string]*string{"": &empty} - } else { - return stringMapToPointers(envs) - } -} - -func expandAmplifyAutoBranchCreationConfig(v []interface{}) (*amplify.AutoBranchCreationConfig, []*string, *bool) { - config := &lify.AutoBranchCreationConfig{} - patterns := make([]*string, 0) - enable := aws.Bool(false) - - if len(v) == 0 { - return config, patterns, enable - } - - e := v[0].(map[string]interface{}) - - if ev, ok := e["auto_branch_creation_patterns"]; ok && len(ev.([]interface{})) > 0 { - patterns = expandStringList(ev.([]interface{})) - } - - if ev, ok := e["basic_auth_config"]; ok { - enable, credentials := expandAmplifyBasicAuthConfig(ev.([]interface{})) - config.EnableBasicAuth = enable - config.BasicAuthCredentials = credentials - } - - if ev, ok := e["build_spec"].(string); ok && ev != "" { - config.BuildSpec = aws.String(ev) - } - - if ev, ok := e["enable_auto_branch_creation"].(bool); ok { - enable = aws.Bool(ev) - } - - if ev, ok := e["enable_auto_build"].(bool); ok { - config.EnableAutoBuild = aws.Bool(ev) - } - - if ev, ok := e["enable_pull_request_preview"].(bool); ok { - config.EnablePullRequestPreview = aws.Bool(ev) - } - - if ev, ok := e["environment_variables"].(map[string]interface{}); ok { - config.EnvironmentVariables = expandAmplifyEnvironmentVariables(ev) - } - - if ev, ok := e["framework"].(string); ok { - config.Framework = aws.String(ev) - } - - if ev, ok := e["pull_request_environment_name"].(string); ok { - config.PullRequestEnvironmentName = aws.String(ev) - } - - if ev, ok := e["stage"].(string); ok { - config.Stage = aws.String(ev) - } - - return config, patterns, enable -} - -func flattenAmplifyAutoBranchCreationConfig(config *amplify.AutoBranchCreationConfig, patterns []*string, enable *bool) []map[string]interface{} { - value := make(map[string]interface{}) - - if !aws.BoolValue(enable) { - return nil - } - - value["enable_auto_branch_creation"] = aws.BoolValue(enable) - value["auto_branch_creation_patterns"] = patterns - - if config != nil { - value["basic_auth_config"] = flattenAmplifyBasicAuthConfig(config.EnableBasicAuth, config.BasicAuthCredentials) - value["build_spec"] = aws.StringValue(config.BuildSpec) - value["enable_auto_build"] = aws.BoolValue(config.EnableAutoBuild) - value["enable_pull_request_preview"] = aws.BoolValue(config.EnablePullRequestPreview) - value["environment_variables"] = aws.StringValueMap(config.EnvironmentVariables) - value["framework"] = aws.StringValue(config.Framework) - value["pull_request_environment_name"] = aws.StringValue(config.PullRequestEnvironmentName) - value["stage"] = aws.StringValue(config.Stage) - } - - return []map[string]interface{}{value} -} - -func expandAmplifyBasicAuthConfig(v []interface{}) (*bool, *string) { - enable := false - credentials := "" - - if len(v) == 0 { - return aws.Bool(enable), aws.String(credentials) - } - - config := v[0].(map[string]interface{}) - - if ev, ok := config["enable_basic_auth"].(bool); ok { - enable = ev - } - - // build basic_auth_credentials from raw username and password - username, ok1 := config["username"].(string) - password, ok2 := config["password"].(string) - if ok1 && ok2 { - credentials = encodeAmplifyBasicAuthCredentials(username, password) - } - - return aws.Bool(enable), aws.String(credentials) -} - -func flattenAmplifyBasicAuthConfig(enableBasicAuth *bool, basicAuthCredentials *string) []map[string]interface{} { - value := make(map[string]interface{}) - - if !aws.BoolValue(enableBasicAuth) { - return nil - } - - value["enable_basic_auth"] = aws.BoolValue(enableBasicAuth) - - if basicAuthCredentials != nil { - // Decode BasicAuthCredentials to username and password - username, password, _ := decodeAmplifyBasicAuthCredentials(aws.StringValue(basicAuthCredentials)) - value["username"] = username - value["password"] = password - } - - return []map[string]interface{}{value} -} - -func encodeAmplifyBasicAuthCredentials(username string, password string) string { - data := fmt.Sprintf("%s:%s", username, password) - return base64.StdEncoding.EncodeToString([]byte(data)) -} - -func decodeAmplifyBasicAuthCredentials(encoded string) (string, string, error) { - data, err := base64.StdEncoding.DecodeString(encoded) - if err != nil { - return "", "", err - } - s := strings.SplitN(string(data), ":", 2) - return s[0], s[1], nil -} - -func expandAmplifyCustomRules(l []interface{}) []*amplify.CustomRule { - rules := make([]*amplify.CustomRule, 0) - - for _, v := range l { - e := v.(map[string]interface{}) - - rule := &lify.CustomRule{} - - if ev, ok := e["condition"].(string); ok && ev != "" { - rule.Condition = aws.String(ev) - } - - if ev, ok := e["source"].(string); ok { - rule.Source = aws.String(ev) - } - - if ev, ok := e["status"].(string); ok && ev != "" { - rule.Status = aws.String(ev) - } - - if ev, ok := e["target"].(string); ok { - rule.Target = aws.String(ev) - } - - rules = append(rules, rule) - } - - return rules -} - -func flattenAmplifyCustomRules(rules []*amplify.CustomRule) []map[string]interface{} { - values := make([]map[string]interface{}, 0) - - for _, rule := range rules { - value := make(map[string]interface{}) - value["condition"] = aws.StringValue(rule.Condition) - value["source"] = aws.StringValue(rule.Source) - value["status"] = aws.StringValue(rule.Status) - value["target"] = aws.StringValue(rule.Target) - values = append(values, value) - } - - return values -} -*/ diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 89fb5297a64..c68bb844424 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -327,7 +327,7 @@ func TestAccAWSAmplifyApp_BasicAuthCredentials(t *testing.T) { } func TestAccAWSAmplifyApp_BuildSpec(t *testing.T) { - var app1, app2, app3 amplify.App + var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -340,7 +340,7 @@ func TestAccAWSAmplifyApp_BuildSpec(t *testing.T) { { Config: testAccAWSAmplifyAppConfigBuildSpec(rName, "version: 0.1"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app1), + testAccCheckAWSAmplifyAppExists(resourceName, &app), resource.TestCheckResourceAttr(resourceName, "build_spec", "version: 0.1"), ), }, @@ -352,17 +352,16 @@ func TestAccAWSAmplifyApp_BuildSpec(t *testing.T) { { Config: testAccAWSAmplifyAppConfigBuildSpec(rName, "version: 0.2"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app2), - testAccCheckAWSAmplifyAppNotRecreated(&app1, &app2), + testAccCheckAWSAmplifyAppExists(resourceName, &app), resource.TestCheckResourceAttr(resourceName, "build_spec", "version: 0.2"), ), }, { Config: testAccAWSAmplifyAppConfigName(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSAmplifyAppExists(resourceName, &app3), - testAccCheckAWSAmplifyAppRecreated(&app2, &app3), - resource.TestCheckResourceAttr(resourceName, "build_spec", ""), + testAccCheckAWSAmplifyAppExists(resourceName, &app), + // build_spec is Computed. + resource.TestCheckResourceAttr(resourceName, "build_spec", "version: 0.2"), ), }, }, @@ -711,95 +710,29 @@ resource "aws_amplify_app" "test" { `, rName) } -func testAccAWSAmplifyAppConfigDescription(rName, description string) string { - return fmt.Sprintf(` -resource "aws_amplify_app" "test" { - name = %[1]q - - description = %[2]q -} -`, rName, description) -} - -func testAccAWSAmplifyAppConfigRepository(rName, repository, accessToken string) string { - return fmt.Sprintf(` -resource "aws_amplify_app" "test" { - name = %[1]q - - repository = %[2]q - access_token = %[3]q -} -`, rName, repository, accessToken) -} - -func testAccAWSAmplifyAppConfigBuildSpec(rName, buildSpec string) string { - return fmt.Sprintf(` -resource "aws_amplify_app" "test" { - name = %[1]q - - build_spec = %[2]q -} -`, rName, buildSpec) -} - -func testAccAWSAmplifyAppConfigCustomRules(rName string) string { - return fmt.Sprintf(` -resource "aws_amplify_app" "test" { - name = %[1]q - - custom_rule { - source = "/<*>" - status = "404" - target = "/index.html" - } -} -`, rName) -} - -func testAccAWSAmplifyAppConfigCustomRulesUpdated(rName string) string { - return fmt.Sprintf(` -resource "aws_amplify_app" "test" { - name = %[1]q - - custom_rule { - condition = "" - source = "/documents" - status = "302" - target = "/documents/us" - } - - custom_rule { - source = "/<*>" - status = "200" - target = "/index.html" - } -} -`, rName) -} - -func testAccAWSAmplifyAppConfigEnvironmentVariables(rName string) string { +func testAccAWSAmplifyAppConfigTags1(rName, tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { name = %[1]q - environment_variables = { - ENVVAR1 = "1" + tags = { + %[2]q = %[3]q } } -`, rName) +`, rName, tagKey1, tagValue1) } -func testAccAWSAmplifyAppConfigEnvironmentVariablesUpdated(rName string) string { +func testAccAWSAmplifyAppConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { name = %[1]q - environment_variables = { - ENVVAR1 = "2", - ENVVAR2 = "2" + tags = { + %[2]q = %[3]q + %[4]q = %[5]q } } -`, rName) +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) } func testAccAWSAmplifyAppConfigAutoBranchCreationConfigNoAutoBranchCreationConfig(rName string) string { @@ -887,6 +820,86 @@ resource "aws_amplify_app" "test" { `, rName, basicAuthCredentials) } +func testAccAWSAmplifyAppConfigBuildSpec(rName, buildSpec string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = %[1]q + + build_spec = %[2]q +} +`, rName, buildSpec) +} + +func testAccAWSAmplifyAppConfigCustomRules(rName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = %[1]q + + custom_rule { + source = "/<*>" + status = "404" + target = "/index.html" + } +} +`, rName) +} + +func testAccAWSAmplifyAppConfigCustomRulesUpdated(rName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = %[1]q + + custom_rule { + condition = "" + source = "/documents" + status = "302" + target = "/documents/us" + } + + custom_rule { + source = "/<*>" + status = "200" + target = "/index.html" + } +} +`, rName) +} + +func testAccAWSAmplifyAppConfigDescription(rName, description string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = %[1]q + + description = %[2]q +} +`, rName, description) +} + +func testAccAWSAmplifyAppConfigEnvironmentVariables(rName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = %[1]q + + environment_variables = { + ENVVAR1 = "1" + } +} +`, rName) +} + +func testAccAWSAmplifyAppConfigEnvironmentVariablesUpdated(rName string) string { + return fmt.Sprintf(` +resource "aws_amplify_app" "test" { + name = %[1]q + + environment_variables = { + ENVVAR1 = "2", + ENVVAR2 = "2" + } +} +`, rName) +} + func testAccAWSAmplifyAppConfigIAMServiceRoleBase(rName string) string { return fmt.Sprintf(` resource "aws_iam_role" "test1" { @@ -945,27 +958,13 @@ resource "aws_amplify_app" "test" { `, rName)) } -func testAccAWSAmplifyAppConfigTags1(rName, tagKey1, tagValue1 string) string { - return fmt.Sprintf(` -resource "aws_amplify_app" "test" { - name = %[1]q - - tags = { - %[2]q = %[3]q - } -} -`, rName, tagKey1, tagValue1) -} - -func testAccAWSAmplifyAppConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { +func testAccAWSAmplifyAppConfigRepository(rName, repository, accessToken string) string { return fmt.Sprintf(` resource "aws_amplify_app" "test" { name = %[1]q - tags = { - %[2]q = %[3]q - %[4]q = %[5]q - } + repository = %[2]q + access_token = %[3]q } -`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +`, rName, repository, accessToken) } From 1643971750a143d18caf409cbd277ba7ab44f989 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 May 2021 09:54:53 -0400 Subject: [PATCH 25/30] r/aws_amplify_app: Serialize acceptance tests to limit API rate-limit exceeded errors. --- aws/resource_aws_amplify_app_test.go | 59 ++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index c68bb844424..f928d6b78a4 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -7,6 +7,7 @@ import ( "os" "regexp" "testing" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/amplify" @@ -68,7 +69,41 @@ func testSweepAmplifyApps(region string) error { return sweeperErrs.ErrorOrNil() } -func TestAccAWSAmplifyApp_basic(t *testing.T) { +// Serialize to limit API rate-limit exceeded errors. +func TestAccAWSAmplify_serial(t *testing.T) { + testCases := map[string]map[string]func(t *testing.T){ + "App": { + "basic": testAccAWSAmplifyApp_basic, + "disappears": testAccAWSAmplifyApp_disappears, + "Tags": testAccAWSAmplifyApp_Tags, + "AutoBranchCreationConfig": testAccAWSAmplifyApp_AutoBranchCreationConfig, + "BasicAuthCredentials": testAccAWSAmplifyApp_BasicAuthCredentials, + "BuildSpec": testAccAWSAmplifyApp_BuildSpec, + "CustomRules": testAccAWSAmplifyApp_CustomRules, + "Description": testAccAWSAmplifyApp_Description, + "EnvironmentVariables": testAccAWSAmplifyApp_EnvironmentVariables, + "IamServiceRole": testAccAWSAmplifyApp_IamServiceRole, + "Name": testAccAWSAmplifyApp_Name, + "Repository": testAccAWSAmplifyApp_Repository, + }, + } + + for group, m := range testCases { + m := m + t.Run(group, func(t *testing.T) { + for name, tc := range m { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + // Explicitly sleep between tests. + time.Sleep(5 * time.Second) + }) + } + }) + } +} + +func testAccAWSAmplifyApp_basic(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -115,7 +150,7 @@ func TestAccAWSAmplifyApp_basic(t *testing.T) { }) } -func TestAccAWSAmplifyApp_disappears(t *testing.T) { +func testAccAWSAmplifyApp_disappears(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -138,7 +173,7 @@ func TestAccAWSAmplifyApp_disappears(t *testing.T) { }) } -func TestAccAWSAmplifyApp_Tags(t *testing.T) { +func testAccAWSAmplifyApp_Tags(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -183,7 +218,7 @@ func TestAccAWSAmplifyApp_Tags(t *testing.T) { }) } -func TestAccAWSAmplifyApp_AutoBranchCreationConfig(t *testing.T) { +func testAccAWSAmplifyApp_AutoBranchCreationConfig(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -278,7 +313,7 @@ func TestAccAWSAmplifyApp_AutoBranchCreationConfig(t *testing.T) { }) } -func TestAccAWSAmplifyApp_BasicAuthCredentials(t *testing.T) { +func testAccAWSAmplifyApp_BasicAuthCredentials(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -326,7 +361,7 @@ func TestAccAWSAmplifyApp_BasicAuthCredentials(t *testing.T) { }) } -func TestAccAWSAmplifyApp_BuildSpec(t *testing.T) { +func testAccAWSAmplifyApp_BuildSpec(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -368,7 +403,7 @@ func TestAccAWSAmplifyApp_BuildSpec(t *testing.T) { }) } -func TestAccAWSAmplifyApp_CustomRules(t *testing.T) { +func testAccAWSAmplifyApp_CustomRules(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -418,7 +453,7 @@ func TestAccAWSAmplifyApp_CustomRules(t *testing.T) { }) } -func TestAccAWSAmplifyApp_Description(t *testing.T) { +func testAccAWSAmplifyApp_Description(t *testing.T) { var app1, app2, app3 amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -461,7 +496,7 @@ func TestAccAWSAmplifyApp_Description(t *testing.T) { }) } -func TestAccAWSAmplifyApp_EnvironmentVariables(t *testing.T) { +func testAccAWSAmplifyApp_EnvironmentVariables(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -505,7 +540,7 @@ func TestAccAWSAmplifyApp_EnvironmentVariables(t *testing.T) { }) } -func TestAccAWSAmplifyApp_IamServiceRole(t *testing.T) { +func testAccAWSAmplifyApp_IamServiceRole(t *testing.T) { var app1, app2, app3 amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_amplify_app.test" @@ -549,7 +584,7 @@ func TestAccAWSAmplifyApp_IamServiceRole(t *testing.T) { }) } -func TestAccAWSAmplifyApp_Name(t *testing.T) { +func testAccAWSAmplifyApp_Name(t *testing.T) { var app amplify.App rName1 := acctest.RandomWithPrefix("tf-acc-test") rName2 := acctest.RandomWithPrefix("tf-acc-test") @@ -584,7 +619,7 @@ func TestAccAWSAmplifyApp_Name(t *testing.T) { }) } -func TestAccAWSAmplifyApp_Repository(t *testing.T) { +func testAccAWSAmplifyApp_Repository(t *testing.T) { key := "AMPLIFY_GITHUB_ACCESS_TOKEN" accessToken := os.Getenv(key) if accessToken == "" { From dc8b24e35c034b949e18732eb995db5635ed14ea Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 May 2021 10:34:12 -0400 Subject: [PATCH 26/30] r/aws_amplify_app: Update documentation. --- website/docs/r/amplify_app.html.markdown | 131 ++++++++++++----------- 1 file changed, 67 insertions(+), 64 deletions(-) diff --git a/website/docs/r/amplify_app.html.markdown b/website/docs/r/amplify_app.html.markdown index 65f85c63bf8..d7e6b81fcce 100644 --- a/website/docs/r/amplify_app.html.markdown +++ b/website/docs/r/amplify_app.html.markdown @@ -15,8 +15,8 @@ Provides an Amplify App resource, a fullstack serverless app hosted on the [AWS ## Example Usage ```hcl -resource "aws_amplify_app" "app" { - name = "app" +resource "aws_amplify_app" "example" { + name = "example" repository = "https://github.com/example/app" # The default build_spec added by the Amplify Console for React. @@ -39,12 +39,16 @@ resource "aws_amplify_app" "app" { - node_modules/**/* EOT - # The default custom_rules added by the Amplify Console. - custom_rules { + # The default rewrites and redirects added by the Amplify Console. + custom_rule { source = "/<*>" status = "404" target = "/index.html" } + + environment_variables = { + ENV = "test" + } } ``` @@ -53,8 +57,8 @@ resource "aws_amplify_app" "app" { If you create a new Amplify App with the `repository` argument, you also need to set `oauth_token` or `access_token` for authentication. For GitHub, get a [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) and set `access_token` as follows: ```hcl -resource "aws_amplify_app" "app" { - name = "app" +resource "aws_amplify_app" "example" { + name = "example" repository = "https://github.com/example/app" # GitHub personal access token @@ -67,19 +71,18 @@ You can omit `access_token` if you import an existing Amplify App created by the ### Auto Branch Creation ```hcl -resource "aws_amplify_app" "app" { - name = "app" +resource "aws_amplify_app" "example" { + name = "example" - auto_branch_creation_config { - # Enable auto branch creation. - enable_auto_branch_creation = true + enable_auto_branch_creation = true - # The default patterns added by the Amplify Console. - auto_branch_creation_patterns = [ - "*", - "*/**", - ] + # The default patterns added by the Amplify Console. + auto_branch_creation_patterns = [ + "*", + "*/**", + ] + auto_branch_creation_config { # Enable auto build for the created branch. enable_auto_build = true } @@ -89,28 +92,23 @@ resource "aws_amplify_app" "app" { ### Basic Authentication ```hcl -resource "aws_amplify_app" "app" { - name = "app" - - basic_auth_config { - # Enable basic authentication. - enable_basic_auth = true +resource "aws_amplify_app" "example" { + name = "example" - username = "username" - password = "password" - } + enable_basic_auth = true + basic_auth_credentials = base64encode("username1:password1") } ``` ### Rewrites and redirects ```hcl -resource "aws_amplify_app" "app" { - name = "app" +resource "aws_amplify_app" "example" { + name = "example" # Reverse Proxy Rewrite for API requests # https://docs.aws.amazon.com/amplify/latest/userguide/redirects.html#reverse-proxy-rewrite - custom_rules { + custom_rule { source = "/api/<*>" status = "200" target = "https://api.example.com/api/<*>" @@ -118,7 +116,7 @@ resource "aws_amplify_app" "app" { # Redirects for Single Page Web Apps (SPA) # https://docs.aws.amazon.com/amplify/latest/userguide/redirects.html#redirects-for-single-page-web-apps-spa - custom_rules { + custom_rule { source = "" status = "200" target = "/index.html" @@ -130,58 +128,63 @@ resource "aws_amplify_app" "app" { The following arguments are supported: -* `name` - (Required) Name of the Amplify App. - -* `access_token` - (Optional) Personal Access token for 3rd party source control system for an Amplify App, used to create webhook and read-only deploy key. Token is not stored. -* `auto_branch_creation_config` - (Optional) Automated branch creation config for the Amplify App. An `auto_branch_creation_config` block is documented below. -* `basic_auth_config` - (Optional) Basic Authentication config for the Amplify App. A `basic_auth_config` block is documented below. -* `build_spec` - (Optional) BuildSpec content for Amplify App. -* `custom_rules` - (Optional) Custom redirect / rewrite rules for the Amplify App. A `custom_rules` block is documented below. -* `description` - (Optional) Description for the Amplify App. +* `name` - (Required) The name for an Amplify app. +* `access_token` - (Optional) The personal access token for a third-party source control system for an Amplify app. The personal access token is used to create a webhook and a read-only deploy key. The token is not stored. +* `auto_branch_creation_config` - (Optional) The automated branch creation configuration for an Amplify app. An `auto_branch_creation_config` block is documented below. +* `auto_branch_creation_patterns` - (Optional) The automated branch creation glob patterns for an Amplify app. +* `basic_auth_credentials` - (Optional) The credentials for basic authorization for an Amplify app. +* `build_spec` - (Optional) The [build specification](https://docs.aws.amazon.com/amplify/latest/userguide/build-settings.html) (build spec) for an Amplify app. +* `custom_rule` - (Optional) The custom rewrite and redirect rules for an Amplify app. A `custom_rule` block is documented below. +* `description` - (Optional) The description for an Amplify app. +* `enable_auto_branch_creation` - (Optional) Enables automated branch creation for an Amplify app. +* `enable_basic_auth` - (Optional) Enables basic authorization for an Amplify app. This will apply to all branches that are part of this app. * `enable_branch_auto_build` - (Optional) Enables auto-building of branches for the Amplify App. -* `environment_variables` - (Optional) Environment Variables for the Amplify App. -* `iam_service_role_arn` - (Optional) IAM service role ARN for the Amplify App. -* `oauth_token` - (Optional) OAuth token for 3rd party source control system for an Amplify App, used to create webhook and read-only deploy key. OAuth token is not stored. -* `platform` - (Optional) Platform for the Amplify App. -* `repository` - (Optional) Repository for the Amplify App. +* `enable_branch_auto_deletion` - (Optional) Automatically disconnects a branch in the Amplify Console when you delete a branch from your Git repository. +* `environment_variables` - (Optional) The environment variables map for an Amplify app. +* `iam_service_role_arn` - (Optional) The AWS Identity and Access Management (IAM) service role for an Amplify app. +* `oauth_token` - (Optional) The OAuth token for a third-party source control system for an Amplify app. The OAuth token is used to create a webhook and a read-only deploy key. The OAuth token is not stored. +* `platform` - (Optional) The platform or framework for an Amplify app. Valid values: `WEB`. +* `repository` - (Optional) The repository for an Amplify app. * `tags` - (Optional) Key-value mapping 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. An `auto_branch_creation_config` block supports the following arguments: -* `enable_auto_branch_creation` - (Optional) Enables automated branch creation for the Amplify App. -* `auto_branch_creation_patterns` - (Optional) Automated branch creation glob patterns for the Amplify App. -* `basic_auth_config` - (Optional) Basic Authentication config for the auto created branch. A `basic_auth_config` block is documented below. -* `build_spec` - (Optional) BuildSpec for the auto created branch. -* `enable_auto_build` - (Optional) Enables auto building for the auto created branch. -* `enable_basic_auth` - (Optional) Enables Basic Auth for the auto created branch. -* `enable_pull_request_preview` - (Optional) Enables Pull Request Preview for auto created branch. -* `environment_variables` - (Optional) Environment Variables for the auto created branch. -* `framework` - (Optional) Framework for the auto created branch. -* `pull_request_environment_name` - (Optional) The Amplify Environment name for the pull request. -* `stage` - (Optional) Stage for the branch. Possible values: "PRODUCTION", "BETA", "DEVELOPMENT", "EXPERIMENTAL", or "PULL_REQUEST". - -An `basic_auth_config` block supports the following arguments: - -* `enable_basic_auth` - (Optional) Enables Basic Authorization. -* `username` - (Optional) Basic Authorization username. -* `password` - (Optional) Basic Authorization password. +* `basic_auth_credentials` - (Optional) The basic authorization credentials for the autocreated branch. +* `build_spec` - (Optional) The build specification (build spec) for the autocreated branch. +* `enable_auto_build` - (Optional) Enables auto building for the autocreated branch. +* `enable_basic_auth` - (Optional) Enables basic authorization for the autocreated branch. +* `enable_performance_mode` - (Optional) Enables performance mode for the branch. +* `enable_pull_request_preview` - (Optional) Enables pull request previews for the autocreated branch. +* `environment_variables` - (Optional) The environment variables for the autocreated branch. +* `framework` - (Optional) The framework for the autocreated branch. +* `pull_request_environment_name` - (Optional) The Amplify environment name for the pull request. +* `stage` - (Optional) Describes the current stage for the autocreated branch. Valid values: `PRODUCTION`, `BETA`, `DEVELOPMENT`, `EXPERIMENTAL`, `PULL_REQUEST`. -A `custom_rules` block supports the following arguments: +A `custom_rule` block supports the following arguments: +* `condition` - (Optional) The condition for a URL rewrite or redirect rule, such as a country code. * `source` - (Required) The source pattern for a URL rewrite or redirect rule. +* `status` - (Optional) The status code for a URL rewrite or redirect rule. Valid values: `200`, `301`, `302`, `404`, `404-200`. * `target` - (Required) The target pattern for a URL rewrite or redirect rule. -* `condition` - (Optional) The condition for a URL rewrite or redirect rule, e.g. country code. -* `status` - (Optional) The status code for a URL rewrite or redirect rule. ## Attribute Reference The following attributes are exported: -* `arn` - ARN for the Amplify App. -* `default_domain` - Default domain for the Amplify App. +* `arn` - The Amazon Resource Name (ARN) of the Amplify app. +* `default_domain` - The default domain for the Amplify app. +* `id` - The unique ID of the Amplify app. +* `production_branch` - Describes the information about a production branch for an Amplify app. A `production_branch` block is documented below. * `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). +A `production_branch` block supports the following attributes: + +* `branch_name` - The branch name for the production branch. +* `last_deploy_time` - The last deploy time of the production branch. +* `status` - The status of the production branch. +* `thumbnail_url` - The thumbnail URL for the production branch. + ## Import Amplify App can be imported using Amplify App ID (appId), e.g. From 769b5ce934f4dc43e016cf835ce76ee14fe8a5ff Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 May 2021 10:38:50 -0400 Subject: [PATCH 27/30] Add 'resource_aws_amplify_test.go'. --- aws/resource_aws_amplify_app_test.go | 35 ------------------------ aws/resource_aws_amplify_test.go | 40 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 35 deletions(-) create mode 100644 aws/resource_aws_amplify_test.go diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index f928d6b78a4..9c1dc8c0d18 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -7,7 +7,6 @@ import ( "os" "regexp" "testing" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/amplify" @@ -69,40 +68,6 @@ func testSweepAmplifyApps(region string) error { return sweeperErrs.ErrorOrNil() } -// Serialize to limit API rate-limit exceeded errors. -func TestAccAWSAmplify_serial(t *testing.T) { - testCases := map[string]map[string]func(t *testing.T){ - "App": { - "basic": testAccAWSAmplifyApp_basic, - "disappears": testAccAWSAmplifyApp_disappears, - "Tags": testAccAWSAmplifyApp_Tags, - "AutoBranchCreationConfig": testAccAWSAmplifyApp_AutoBranchCreationConfig, - "BasicAuthCredentials": testAccAWSAmplifyApp_BasicAuthCredentials, - "BuildSpec": testAccAWSAmplifyApp_BuildSpec, - "CustomRules": testAccAWSAmplifyApp_CustomRules, - "Description": testAccAWSAmplifyApp_Description, - "EnvironmentVariables": testAccAWSAmplifyApp_EnvironmentVariables, - "IamServiceRole": testAccAWSAmplifyApp_IamServiceRole, - "Name": testAccAWSAmplifyApp_Name, - "Repository": testAccAWSAmplifyApp_Repository, - }, - } - - for group, m := range testCases { - m := m - t.Run(group, func(t *testing.T) { - for name, tc := range m { - tc := tc - t.Run(name, func(t *testing.T) { - tc(t) - // Explicitly sleep between tests. - time.Sleep(5 * time.Second) - }) - } - }) - } -} - func testAccAWSAmplifyApp_basic(t *testing.T) { var app amplify.App rName := acctest.RandomWithPrefix("tf-acc-test") diff --git a/aws/resource_aws_amplify_test.go b/aws/resource_aws_amplify_test.go new file mode 100644 index 00000000000..4907084c10e --- /dev/null +++ b/aws/resource_aws_amplify_test.go @@ -0,0 +1,40 @@ +package aws + +import ( + "testing" + "time" +) + +// Serialize to limit API rate-limit exceeded errors. +func TestAccAWSAmplify_serial(t *testing.T) { + testCases := map[string]map[string]func(t *testing.T){ + "App": { + "basic": testAccAWSAmplifyApp_basic, + "disappears": testAccAWSAmplifyApp_disappears, + "Tags": testAccAWSAmplifyApp_Tags, + "AutoBranchCreationConfig": testAccAWSAmplifyApp_AutoBranchCreationConfig, + "BasicAuthCredentials": testAccAWSAmplifyApp_BasicAuthCredentials, + "BuildSpec": testAccAWSAmplifyApp_BuildSpec, + "CustomRules": testAccAWSAmplifyApp_CustomRules, + "Description": testAccAWSAmplifyApp_Description, + "EnvironmentVariables": testAccAWSAmplifyApp_EnvironmentVariables, + "IamServiceRole": testAccAWSAmplifyApp_IamServiceRole, + "Name": testAccAWSAmplifyApp_Name, + "Repository": testAccAWSAmplifyApp_Repository, + }, + } + + for group, m := range testCases { + m := m + t.Run(group, func(t *testing.T) { + for name, tc := range m { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + // Explicitly sleep between tests. + time.Sleep(5 * time.Second) + }) + } + }) + } +} From ee538b0d7818d994262b98fc68c887a7561634f9 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 May 2021 11:03:12 -0400 Subject: [PATCH 28/30] Fix 'terrafmt' errors. --- aws/resource_aws_amplify_app_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/aws/resource_aws_amplify_app_test.go b/aws/resource_aws_amplify_app_test.go index 9c1dc8c0d18..94b49a37ee8 100644 --- a/aws/resource_aws_amplify_app_test.go +++ b/aws/resource_aws_amplify_app_test.go @@ -743,8 +743,8 @@ resource "aws_amplify_app" "test" { enable_auto_branch_creation = true auto_branch_creation_patterns = [ - "*", - "*/**", + "*", + "*/**", ] } `, rName) @@ -756,7 +756,7 @@ resource "aws_amplify_app" "test" { name = %[1]q enable_auto_branch_creation = true - + auto_branch_creation_patterns = [ "feature/*", ] @@ -766,8 +766,8 @@ resource "aws_amplify_app" "test" { framework = "React" stage = "DEVELOPMENT" - enable_basic_auth = true - basic_auth_credentials = %[2]q + enable_basic_auth = true + basic_auth_credentials = %[2]q enable_auto_build = true enable_pull_request_preview = true @@ -788,7 +788,7 @@ resource "aws_amplify_app" "test" { name = %[1]q enable_auto_branch_creation = true - + auto_branch_creation_patterns = [ "feature/*", ] @@ -798,7 +798,7 @@ resource "aws_amplify_app" "test" { framework = "React" stage = "EXPERIMENTAL" - enable_basic_auth = false + enable_basic_auth = false enable_auto_build = false enable_pull_request_preview = false From ad4df307d7d7869db51bdaa413264e322c47b522 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 May 2021 11:05:45 -0400 Subject: [PATCH 29/30] Fix 'tfproviderdocs' errors. --- website/docs/r/amplify_app.html.markdown | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/website/docs/r/amplify_app.html.markdown b/website/docs/r/amplify_app.html.markdown index d7e6b81fcce..891f2431a67 100644 --- a/website/docs/r/amplify_app.html.markdown +++ b/website/docs/r/amplify_app.html.markdown @@ -14,7 +14,7 @@ Provides an Amplify App resource, a fullstack serverless app hosted on the [AWS ## Example Usage -```hcl +```terraform resource "aws_amplify_app" "example" { name = "example" repository = "https://github.com/example/app" @@ -56,7 +56,7 @@ resource "aws_amplify_app" "example" { If you create a new Amplify App with the `repository` argument, you also need to set `oauth_token` or `access_token` for authentication. For GitHub, get a [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) and set `access_token` as follows: -```hcl +```terraform resource "aws_amplify_app" "example" { name = "example" repository = "https://github.com/example/app" @@ -70,7 +70,7 @@ You can omit `access_token` if you import an existing Amplify App created by the ### Auto Branch Creation -```hcl +```terraform resource "aws_amplify_app" "example" { name = "example" @@ -89,9 +89,9 @@ resource "aws_amplify_app" "example" { } ``` -### Basic Authentication +### Basic Authorization -```hcl +```terraform resource "aws_amplify_app" "example" { name = "example" @@ -100,9 +100,9 @@ resource "aws_amplify_app" "example" { } ``` -### Rewrites and redirects +### Rewrites and Redirects -```hcl +```terraform resource "aws_amplify_app" "example" { name = "example" From 70526bfe796452b674cfc120c17493163202b85d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 May 2021 11:13:24 -0400 Subject: [PATCH 30/30] Fix 'tfproviderdocs' errors. --- website/docs/r/amplify_app.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/amplify_app.html.markdown b/website/docs/r/amplify_app.html.markdown index 891f2431a67..3da86a70893 100644 --- a/website/docs/r/amplify_app.html.markdown +++ b/website/docs/r/amplify_app.html.markdown @@ -168,7 +168,7 @@ A `custom_rule` block supports the following arguments: * `status` - (Optional) The status code for a URL rewrite or redirect rule. Valid values: `200`, `301`, `302`, `404`, `404-200`. * `target` - (Required) The target pattern for a URL rewrite or redirect rule. -## Attribute Reference +## Attributes Reference The following attributes are exported: