diff --git a/aws/provider.go b/aws/provider.go index e4c80bfccebf..b3887547b514 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -292,6 +292,7 @@ func Provider() terraform.ResourceProvider { "aws_cognito_identity_pool": resourceAwsCognitoIdentityPool(), "aws_cognito_identity_pool_roles_attachment": resourceAwsCognitoIdentityPoolRolesAttachment(), "aws_cognito_user_pool": resourceAwsCognitoUserPool(), + "aws_cognito_user_pool_domain": resourceAwsCognitoUserPoolDomain(), "aws_autoscaling_lifecycle_hook": resourceAwsAutoscalingLifecycleHook(), "aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(), "aws_cloudwatch_dashboard": resourceAwsCloudWatchDashboard(), diff --git a/aws/resource_aws_cognito_user_pool_domain.go b/aws/resource_aws_cognito_user_pool_domain.go new file mode 100644 index 000000000000..08c70a478fde --- /dev/null +++ b/aws/resource_aws_cognito_user_pool_domain.go @@ -0,0 +1,171 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsCognitoUserPoolDomain() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsCognitoUserPoolDomainCreate, + Read: resourceAwsCognitoUserPoolDomainRead, + Delete: resourceAwsCognitoUserPoolDomainDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "domain": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateCognitoUserPoolDomain, + }, + "user_pool_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "aws_account_id": { + Type: schema.TypeString, + Computed: true, + }, + "cloudfront_distribution_arn": { + Type: schema.TypeString, + Computed: true, + }, + "s3_bucket": { + Type: schema.TypeString, + Computed: true, + }, + "version": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsCognitoUserPoolDomainCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoidpconn + + domain := d.Get("domain").(string) + + params := &cognitoidentityprovider.CreateUserPoolDomainInput{ + Domain: aws.String(domain), + UserPoolId: aws.String(d.Get("user_pool_id").(string)), + } + log.Printf("[DEBUG] Creating Cognito User Pool Domain: %s", params) + + _, err := conn.CreateUserPoolDomain(params) + if err != nil { + return fmt.Errorf("Error creating Cognito User Pool Domain: %s", err) + } + + d.SetId(domain) + + stateConf := resource.StateChangeConf{ + Pending: []string{ + cognitoidentityprovider.DomainStatusTypeCreating, + cognitoidentityprovider.DomainStatusTypeUpdating, + }, + Target: []string{ + cognitoidentityprovider.DomainStatusTypeActive, + }, + Timeout: 1 * time.Minute, + Refresh: func() (interface{}, string, error) { + domain, err := conn.DescribeUserPoolDomain(&cognitoidentityprovider.DescribeUserPoolDomainInput{ + Domain: aws.String(d.Get("domain").(string)), + }) + if err != nil { + return 42, "", err + } + + desc := domain.DomainDescription + + return domain, *desc.Status, nil + }, + } + _, err = stateConf.WaitForState() + if err != nil { + return err + } + + return resourceAwsCognitoUserPoolDomainRead(d, meta) +} + +func resourceAwsCognitoUserPoolDomainRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoidpconn + log.Printf("[DEBUG] Reading Cognito User Pool Domain: %s", d.Id()) + + domain, err := conn.DescribeUserPoolDomain(&cognitoidentityprovider.DescribeUserPoolDomainInput{ + Domain: aws.String(d.Id()), + }) + if err != nil { + if isAWSErr(err, "ResourceNotFoundException", "") { + log.Printf("[WARN] Cognito User Pool Domain %q not found, removing from state", d.Id()) + d.SetId("") + return nil + } + return err + } + + desc := domain.DomainDescription + + d.Set("domain", d.Id()) + d.Set("aws_account_id", desc.AWSAccountId) + d.Set("cloudfront_distribution_arn", desc.CloudFrontDistribution) + d.Set("s3_bucket", desc.S3Bucket) + d.Set("user_pool_id", desc.UserPoolId) + d.Set("version", desc.Version) + + return nil +} + +func resourceAwsCognitoUserPoolDomainDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoidpconn + log.Printf("[DEBUG] Deleting Cognito User Pool Domain: %s", d.Id()) + + _, err := conn.DeleteUserPoolDomain(&cognitoidentityprovider.DeleteUserPoolDomainInput{ + Domain: aws.String(d.Id()), + UserPoolId: aws.String(d.Get("user_pool_id").(string)), + }) + if err != nil { + return err + } + + stateConf := resource.StateChangeConf{ + Pending: []string{ + cognitoidentityprovider.DomainStatusTypeUpdating, + cognitoidentityprovider.DomainStatusTypeDeleting, + }, + Target: []string{""}, + Timeout: 1 * time.Minute, + Refresh: func() (interface{}, string, error) { + domain, err := conn.DescribeUserPoolDomain(&cognitoidentityprovider.DescribeUserPoolDomainInput{ + Domain: aws.String(d.Id()), + }) + if err != nil { + if isAWSErr(err, "ResourceNotFoundException", "") { + return 42, "", nil + } + return 42, "", err + } + + desc := domain.DomainDescription + if desc.Status == nil { + return 42, "", nil + } + + return domain, *desc.Status, nil + }, + } + _, err = stateConf.WaitForState() + return err +} diff --git a/aws/resource_aws_cognito_user_pool_domain_test.go b/aws/resource_aws_cognito_user_pool_domain_test.go new file mode 100644 index 000000000000..eef019df2f7f --- /dev/null +++ b/aws/resource_aws_cognito_user_pool_domain_test.go @@ -0,0 +1,120 @@ +package aws + +import ( + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSCognitoUserPoolDomain_basic(t *testing.T) { + domainName := fmt.Sprintf("tf-acc-test-domain-%d", acctest.RandInt()) + poolName := fmt.Sprintf("tf-acc-test-pool-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoUserPoolDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoUserPoolDomainConfig_basic(domainName, poolName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoUserPoolDomainExists("aws_cognito_user_pool_domain.main"), + resource.TestCheckResourceAttr("aws_cognito_user_pool_domain.main", "domain", domainName), + resource.TestCheckResourceAttr("aws_cognito_user_pool.main", "name", poolName), + resource.TestCheckResourceAttrSet("aws_cognito_user_pool_domain.main", "aws_account_id"), + resource.TestCheckResourceAttrSet("aws_cognito_user_pool_domain.main", "cloudfront_distribution_arn"), + resource.TestCheckResourceAttrSet("aws_cognito_user_pool_domain.main", "s3_bucket"), + resource.TestCheckResourceAttrSet("aws_cognito_user_pool_domain.main", "version"), + ), + }, + }, + }) +} + +func TestAccAWSCognitoUserPoolDomain_import(t *testing.T) { + domainName := fmt.Sprintf("tf-acc-test-domain-%d", acctest.RandInt()) + poolName := fmt.Sprintf("tf-acc-test-pool-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCognitoUserPoolDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoUserPoolDomainConfig_basic(domainName, poolName), + }, + { + ResourceName: "aws_cognito_user_pool_domain.main", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSCognitoUserPoolDomainExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return errors.New("No Cognito User Pool Domain ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).cognitoidpconn + + _, err := conn.DescribeUserPoolDomain(&cognitoidentityprovider.DescribeUserPoolDomainInput{ + Domain: aws.String(rs.Primary.ID), + }) + + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckAWSCognitoUserPoolDomainDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).cognitoidpconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cognito_user_pool_domain" { + continue + } + + _, err := conn.DescribeUserPoolDomain(&cognitoidentityprovider.DescribeUserPoolDomainInput{ + Domain: aws.String(rs.Primary.ID), + }) + + if err != nil { + if isAWSErr(err, "ResourceNotFoundException", "") { + return nil + } + return err + } + } + + return nil +} + +func testAccAWSCognitoUserPoolDomainConfig_basic(domainName, poolName string) string { + return fmt.Sprintf(` +resource "aws_cognito_user_pool_domain" "main" { + domain = "%s" + user_pool_id = "${aws_cognito_user_pool.main.id}" +} + +resource "aws_cognito_user_pool" "main" { + name = "%s" +} +`, domainName, poolName) +} diff --git a/aws/validators.go b/aws/validators.go index c502925e31da..380db338bf72 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -1986,6 +1986,15 @@ func validateCognitoRoles(v map[string]interface{}, k string) (errors []error) { return } +func validateCognitoUserPoolDomain(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if !regexp.MustCompile(`^[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?$`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "only lowercase alphanumeric characters and hyphens (max length 63 chars) allowed in %q", k)) + } + return +} + func validateDxConnectionBandWidth(v interface{}, k string) (ws []string, errors []error) { val, ok := v.(string) if !ok { diff --git a/aws/validators_test.go b/aws/validators_test.go index 9337faacb441..1a63f7ab8ad2 100644 --- a/aws/validators_test.go +++ b/aws/validators_test.go @@ -2863,3 +2863,30 @@ func TestResourceAWSElastiCacheReplicationGroupAuthTokenValidation(t *testing.T) } } } + +func TestValidateCognitoUserPoolDomain(t *testing.T) { + validTypes := []string{ + "valid-domain", + "validdomain", + "val1d-d0main", + } + for _, v := range validTypes { + _, errors := validateCognitoUserPoolDomain(v, "name") + if len(errors) != 0 { + t.Fatalf("%q should be a valid Cognito User Pool Domain: %q", v, errors) + } + } + + invalidTypes := []string{ + "UpperCase", + "-invalid", + "invalid-", + strings.Repeat("i", 64), // > 63 + } + for _, v := range invalidTypes { + _, errors := validateCognitoUserPoolDomain(v, "name") + if len(errors) == 0 { + t.Fatalf("%q should be an invalid Cognito User Pool Domain", v) + } + } +} diff --git a/website/aws.erb b/website/aws.erb index 5bf9ba7af53f..81fba1358e48 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -487,6 +487,9 @@ > aws_cognito_user_pool + > + aws_cognito_user_pool_domain + diff --git a/website/docs/r/cognito_user_pool_domain.markdown b/website/docs/r/cognito_user_pool_domain.markdown new file mode 100644 index 000000000000..a5e995002a5a --- /dev/null +++ b/website/docs/r/cognito_user_pool_domain.markdown @@ -0,0 +1,40 @@ +-- +layout: "aws" +page_title: "AWS: aws_cognito_user_pool_domain" +side_bar_current: "docs-aws-resource-cognito-user-pool-domain" +description: |- + Provides a Cognito User Pool Domain resource. +--- + +# aws_cognito_user_pool_domain + +Provides a Cognito User Pool Domain resource. + +## Example Usage + +```hcl +resource "aws_cognito_user_pool_domain" "main" { + domain = "example-domain" + user_pool_id = "${aws_cognito_user_pool.example.id}" +} + +resource "aws_cognito_user_pool" "example" { + name = "example-pool" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `domain` - (Required) The domain string. +* `user_pool_id` - (Required) The user pool ID. + +## Attribute Reference + +The following attributes are exported: + +* `aws_account_id` - The AWS account ID for the user pool owner. +* `cloudfront_distribution_arn` - The ARN of the CloudFront distribution. +* `s3_bucket` - The S3 bucket where the static files for this domain are stored. +* `version` - The app version.