From ed5fe12ec54e841f9db1044173683a44124b2a6a Mon Sep 17 00:00:00 2001 From: Tom Janne Date: Thu, 26 Apr 2018 10:42:38 +0200 Subject: [PATCH 1/3] first version of AWS Cognito Resource Server --- aws/provider.go | 1 + aws/resource_aws_cognito_resource_server.go | 178 ++++++++++++++++++++ aws/structure.go | 37 ++++ aws/validators.go | 27 +++ 4 files changed, 243 insertions(+) create mode 100644 aws/resource_aws_cognito_resource_server.go diff --git a/aws/provider.go b/aws/provider.go index 281cbb31fd0a..d6d7e8540043 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -316,6 +316,7 @@ func Provider() terraform.ResourceProvider { "aws_cognito_user_pool": resourceAwsCognitoUserPool(), "aws_cognito_user_pool_client": resourceAwsCognitoUserPoolClient(), "aws_cognito_user_pool_domain": resourceAwsCognitoUserPoolDomain(), + "aws_cognito_resource_server": resourceAwsCognitoResourceServer(), "aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(), "aws_cloudwatch_dashboard": resourceAwsCloudWatchDashboard(), "aws_codedeploy_app": resourceAwsCodeDeployApp(), diff --git a/aws/resource_aws_cognito_resource_server.go b/aws/resource_aws_cognito_resource_server.go new file mode 100644 index 000000000000..b686585323aa --- /dev/null +++ b/aws/resource_aws_cognito_resource_server.go @@ -0,0 +1,178 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsCognitoResourceServer() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsCognitoResourceServerCreate, + Read: resourceAwsCognitoResourceServerRead, + Update: resourceAwsCognitoResourceServerUpdate, + Delete: resourceAwsCognitoResourceServerDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + // https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_CreateResourceServer.html + Schema: map[string]*schema.Schema{ + "identifier": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "scope": { + Type: schema.TypeSet, + Optional: true, + MaxItems: 25, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "scope_description": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateCognitoResourceServerScopeDescription, + }, + "scope_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateCognitoResourceServerScopeName, + }, + "scope_identifier": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "user_pool_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "scope_identifiers": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func resourceAwsCognitoResourceServerCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoidpconn + + params := &cognitoidentityprovider.CreateResourceServerInput{ + Identifier: aws.String(d.Get("identifier").(string)), + Name: aws.String(d.Get("name").(string)), + UserPoolId: aws.String(d.Get("user_pool_id").(string)), + } + + if v, ok := d.GetOk("scope"); ok { + configs := v.(*schema.Set).List() + params.Scopes = expandCognitoResourceServerScope(configs) + } + + log.Printf("[DEBUG] Creating Cognito Resource Server: %s", params) + + resp, err := conn.CreateResourceServer(params) + + if err != nil { + return errwrap.Wrapf("Error creating Cognito Resource Server: {{err}}", err) + } + + d.SetId(*resp.ResourceServer.Identifier) + + return resourceAwsCognitoResourceServerRead(d, meta) +} + +func resourceAwsCognitoResourceServerRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoidpconn + + params := &cognitoidentityprovider.DescribeResourceServerInput{ + Identifier: aws.String(d.Id()), + UserPoolId: aws.String(d.Get("user_pool_id").(string)), + } + + log.Printf("[DEBUG] Reading Cognito Resource Server: %s", params) + + resp, err := conn.DescribeResourceServer(params) + + if err != nil { + if isAWSErr(err, "ResourceNotFoundException", "") { + log.Printf("[WARN] Cognito Resource Server %s is already gone", d.Id()) + d.SetId("") + return nil + } + return err + } + + d.SetId(*resp.ResourceServer.Identifier) + d.Set("name", *resp.ResourceServer.Name) + d.Set("user_pool_id", *resp.ResourceServer.UserPoolId) + + scopes := flattenCognitoResourceServerScope(*resp.ResourceServer.Identifier, resp.ResourceServer.Scopes) + if err := d.Set("scope", scopes); err != nil { + return fmt.Errorf("Failed setting schema: %s", err) + } + + var scope_identifiers []string + for _, elem := range scopes { + + scope_identifier := elem["scope_identifier"].(string) + scope_identifiers = append(scope_identifiers, scope_identifier) + } + d.Set("scope_identifiers", scope_identifiers) + return nil +} + +func resourceAwsCognitoResourceServerUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoidpconn + + params := &cognitoidentityprovider.UpdateResourceServerInput{ + Identifier: aws.String(d.Id()), + Name: aws.String(d.Get("name").(string)), + UserPoolId: aws.String(d.Get("user_pool_id").(string)), + } + + log.Printf("[DEBUG] Updating Cognito Resource Server: %s", params) + + _, err := conn.UpdateResourceServer(params) + if err != nil { + return errwrap.Wrapf("Error updating Cognito Resource Server: {{err}}", err) + } + + return resourceAwsCognitoResourceServerRead(d, meta) +} + +func resourceAwsCognitoResourceServerDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cognitoidpconn + + params := &cognitoidentityprovider.DeleteResourceServerInput{ + Identifier: aws.String(d.Id()), + UserPoolId: aws.String(d.Get("user_pool_id").(string)), + } + + log.Printf("[DEBUG] Deleting Resource Server: %s", params) + + _, err := conn.DeleteResourceServer(params) + + if err != nil { + return errwrap.Wrapf("Error deleting Resource Server: {{err}}", err) + } + + return nil +} diff --git a/aws/structure.go b/aws/structure.go index 26b9c70afb6d..7eba19194bd4 100644 --- a/aws/structure.go +++ b/aws/structure.go @@ -2699,6 +2699,43 @@ func flattenCognitoUserPoolPasswordPolicy(s *cognitoidentityprovider.PasswordPol return []map[string]interface{}{} } +func expandCognitoResourceServerScope(inputs []interface{}) []*cognitoidentityprovider.ResourceServerScopeType { + configs := make([]*cognitoidentityprovider.ResourceServerScopeType, len(inputs), len(inputs)) + for i, input := range inputs { + param := input.(map[string]interface{}) + config := &cognitoidentityprovider.ResourceServerScopeType{} + + if v, ok := param["scope_description"]; ok { + config.ScopeDescription = aws.String(v.(string)) + } + + if v, ok := param["scope_name"]; ok { + config.ScopeName = aws.String(v.(string)) + } + + configs[i] = config + } + + return configs +} + +func flattenCognitoResourceServerScope(identifier string, inputs []*cognitoidentityprovider.ResourceServerScopeType) []map[string]interface{} { + values := make([]map[string]interface{}, 0) + + for _, input := range inputs { + if input == nil { + continue + } + var value = map[string]interface{}{ + "scope_name": aws.StringValue(input.ScopeName), + "scope_description": aws.StringValue(input.ScopeDescription), + "scope_identifier": strings.Join([]string{identifier, "/", aws.StringValue(input.ScopeName)}, ""), + } + values = append(values, value) + } + return values +} + func expandCognitoUserPoolSchema(inputs []interface{}) []*cognitoidentityprovider.SchemaAttributeType { configs := make([]*cognitoidentityprovider.SchemaAttributeType, len(inputs), len(inputs)) diff --git a/aws/validators.go b/aws/validators.go index a8a762a5e689..6f3e19c6ae58 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -1386,6 +1386,33 @@ func validateCognitoUserPoolClientURL(v interface{}, k string) (ws []string, es return } +func validateCognitoResourceServerScopeDescription(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if len(value) < 1 { + errors = append(errors, fmt.Errorf("%q cannot be less than 1 character", k)) + } + if len(value) > 256 { + errors = append(errors, fmt.Errorf("%q cannot be longer than 256 character", k)) + } + return +} + +func validateCognitoResourceServerScopeName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if len(value) < 1 { + errors = append(errors, fmt.Errorf("%q cannot be less than 1 character", k)) + } + if len(value) > 256 { + errors = append(errors, fmt.Errorf("%q cannot be longer than 256 character", k)) + } + if !regexp.MustCompile(`[\x21\x23-\x2E\x30-\x5B\x5D-\x7E]+`).MatchString(value) { + errors = append(errors, fmt.Errorf("%q must satisfy regular expression pattern: [\\x21\\x23-\\x2E\\x30-\\x5B\\x5D-\\x7E]+", k)) + } + return +} + func validateWafMetricName(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if !regexp.MustCompile(`^[0-9A-Za-z]+$`).MatchString(value) { From 92e496560025da78722b8298fa1b983acc7b4c8c Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 8 May 2018 13:55:07 +0200 Subject: [PATCH 2/3] add ACC tests. documentation --- aws/resource_aws_cognito_resource_server.go | 8 +- ...source_aws_cognito_resource_server_test.go | 146 ++++++++++++++++++ .../docs/r/cognito_resource_server.markdown | 67 ++++++++ 3 files changed, 217 insertions(+), 4 deletions(-) create mode 100644 aws/resource_aws_cognito_resource_server_test.go create mode 100644 website/docs/r/cognito_resource_server.markdown diff --git a/aws/resource_aws_cognito_resource_server.go b/aws/resource_aws_cognito_resource_server.go index b686585323aa..a77736f45061 100644 --- a/aws/resource_aws_cognito_resource_server.go +++ b/aws/resource_aws_cognito_resource_server.go @@ -129,13 +129,13 @@ func resourceAwsCognitoResourceServerRead(d *schema.ResourceData, meta interface return fmt.Errorf("Failed setting schema: %s", err) } - var scope_identifiers []string + var scopeIdentifiers []string for _, elem := range scopes { - scope_identifier := elem["scope_identifier"].(string) - scope_identifiers = append(scope_identifiers, scope_identifier) + scopeIdentifier := elem["scope_identifier"].(string) + scopeIdentifiers = append(scopeIdentifiers, scopeIdentifier) } - d.Set("scope_identifiers", scope_identifiers) + d.Set("scope_identifiers", scopeIdentifiers) return nil } diff --git a/aws/resource_aws_cognito_resource_server_test.go b/aws/resource_aws_cognito_resource_server_test.go new file mode 100644 index 000000000000..bf8c7770ba24 --- /dev/null +++ b/aws/resource_aws_cognito_resource_server_test.go @@ -0,0 +1,146 @@ +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 TestAccAWSCognitoResourceServer_basic(t *testing.T) { + identifier := fmt.Sprintf("tf-acc-test-resource-server-id-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + name := fmt.Sprintf("tf-acc-test-resource-server-name-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + 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: testAccCheckAWSCognitoResourceServerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoResourceServerConfig_basic(identifier, name, poolName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoResourceServerExists("aws_cognito_resource_server.main"), + resource.TestCheckResourceAttr("aws_cognito_resource_server.main", "identifier", identifier), + resource.TestCheckResourceAttr("aws_cognito_resource_server.main", "name", name), + resource.TestCheckResourceAttr("aws_cognito_user_pool.main", "name", poolName), + ), + }, + }, + }) +} + +func TestAccAWSCognitoResourceServer_full(t *testing.T) { + identifier := fmt.Sprintf("tf-acc-test-resource-server-id-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + name := fmt.Sprintf("tf-acc-test-resource-server-name-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + scopeName := fmt.Sprintf("tf-acc-test-resource-server-scope-name-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + scopeDescription := fmt.Sprintf("tf-acc-test-resource-server-scope-description-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + 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: testAccCheckAWSCognitoResourceServerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCognitoResourceServerConfig_full(identifier, name, scopeName, scopeDescription, poolName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSCognitoResourceServerExists("aws_cognito_resource_server.main"), + resource.TestCheckResourceAttr("aws_cognito_resource_server.main", "identifier", identifier), + resource.TestCheckResourceAttr("aws_cognito_resource_server.main", "name", name), + resource.TestCheckResourceAttr("aws_cognito_user_pool.main", "name", poolName), + resource.TestCheckResourceAttrSet("aws_cognito_resource_server.main", "scope_identifiers"), + ), + }, + }, + }) +} + +func testAccCheckAWSCognitoResourceServerExists(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 Resource Server ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).cognitoidpconn + + _, err := conn.DescribeResourceServer(&cognitoidentityprovider.DescribeResourceServerInput{ + Identifier: aws.String(rs.Primary.ID), + UserPoolId: aws.String(rs.Primary.Attributes["user_pool_id"]), + }) + + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckAWSCognitoResourceServerDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).cognitoidpconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cognito_resource_server" { + continue + } + + _, err := conn.DescribeResourceServer(&cognitoidentityprovider.DescribeResourceServerInput{ + Identifier: aws.String(rs.Primary.ID), + UserPoolId: aws.String(rs.Primary.Attributes["user_pool_id"]), + }) + + if err != nil { + if isAWSErr(err, "ResourceNotFoundException", "") { + return nil + } + return err + } + } + + return nil +} + +func testAccAWSCognitoResourceServerConfig_basic(identifier string, name string, poolName string) string { + return fmt.Sprintf(` +resource "aws_cognito_resource_server" "main" { + identifier = "%s" + name = "%s" + user_pool_id = "${aws_cognito_user_pool.main.id}" +} + +resource "aws_cognito_user_pool" "main" { + name = "%s" +} +`, identifier, name, poolName) +} + +func testAccAWSCognitoResourceServerConfig_full(identifier string, name string, scopeName string, scopeDescription string, poolName string) string { + return fmt.Sprintf(` +resource "aws_cognito_resource_server" "main" { + identifier = "%s" + name = "%s" + + scope = { + scope_name = "%s" + scope_description = "%s" + } + + user_pool_id = "${aws_cognito_user_pool.main.id}" +} + +resource "aws_cognito_user_pool" "main" { + name = "%s" +} +`, identifier, name, scopeName, scopeDescription, poolName) +} diff --git a/website/docs/r/cognito_resource_server.markdown b/website/docs/r/cognito_resource_server.markdown new file mode 100644 index 000000000000..bda43990126e --- /dev/null +++ b/website/docs/r/cognito_resource_server.markdown @@ -0,0 +1,67 @@ +--- +layout: "aws" +page_title: "AWS: aws_cognito_resource_server" +side_bar_current: "docs-aws-resource-cognito-resource-server" +description: |- + Provides a Cognito Resource Server. +--- + +# aws_cognito_resource_server + +Provides a Cognito Resource Server. + +## Example Usage + +### Create a basic resource server + +```hcl +resource "aws_cognito_user_pool" "pool" { + name = "pool" +} + +resource "aws_cognito_resource_server" "resource" { + identifier = "res" + name = "resource" + + user_pool_id = "${aws_cognito_user_pool.pool.id}" +} +``` + +### Create a resource server with sample-scope + +```hcl +resource "aws_cognito_user_pool" "pool" { + name = "pool" +} + +resource "aws_cognito_resource_server" "resource" { + identifier = "res" + name = "resource" + + scope = { + scope_name = "sample-scope" + scope_description = "a Sample Scope Description" + } + + user_pool_id = "${aws_cognito_user_pool.pool.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `identifier` - (Required) An identifier for the resource server. +* `name` - (Required) A name for the resource server. +* `scope` - (Optional) The configuration for an [Authorization Scope](#authorization_scope). + +### Authorization Scope + +* `scope_name` - (Required) The scope name. +* `scope_description` - (Required) The scope description. + +## Attribute Reference + +In addition to the arguments, which are exported, the following attributes are exported: + +* `scope_identifiers` - A list of all scopes configured for this resource server in the format identifier/scope_name. From 71a8b99af20a8fe6b898bc0ebc64d132333e55ea Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 11 May 2018 12:02:13 +0200 Subject: [PATCH 3/3] fix tests --- ...source_aws_cognito_resource_server_test.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/aws/resource_aws_cognito_resource_server_test.go b/aws/resource_aws_cognito_resource_server_test.go index bf8c7770ba24..3fa283fc5dc8 100644 --- a/aws/resource_aws_cognito_resource_server_test.go +++ b/aws/resource_aws_cognito_resource_server_test.go @@ -38,8 +38,6 @@ func TestAccAWSCognitoResourceServer_basic(t *testing.T) { func TestAccAWSCognitoResourceServer_full(t *testing.T) { identifier := fmt.Sprintf("tf-acc-test-resource-server-id-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) name := fmt.Sprintf("tf-acc-test-resource-server-name-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - scopeName := fmt.Sprintf("tf-acc-test-resource-server-scope-name-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) - scopeDescription := fmt.Sprintf("tf-acc-test-resource-server-scope-description-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) poolName := fmt.Sprintf("tf-acc-test-pool-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) resource.Test(t, resource.TestCase{ @@ -48,13 +46,13 @@ func TestAccAWSCognitoResourceServer_full(t *testing.T) { CheckDestroy: testAccCheckAWSCognitoResourceServerDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCognitoResourceServerConfig_full(identifier, name, scopeName, scopeDescription, poolName), + Config: testAccAWSCognitoResourceServerConfig_full(identifier, name, poolName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckAWSCognitoResourceServerExists("aws_cognito_resource_server.main"), resource.TestCheckResourceAttr("aws_cognito_resource_server.main", "identifier", identifier), resource.TestCheckResourceAttr("aws_cognito_resource_server.main", "name", name), - resource.TestCheckResourceAttr("aws_cognito_user_pool.main", "name", poolName), resource.TestCheckResourceAttrSet("aws_cognito_resource_server.main", "scope_identifiers"), + resource.TestCheckResourceAttr("aws_cognito_user_pool.main", "name", poolName), ), }, }, @@ -125,15 +123,20 @@ resource "aws_cognito_user_pool" "main" { `, identifier, name, poolName) } -func testAccAWSCognitoResourceServerConfig_full(identifier string, name string, scopeName string, scopeDescription string, poolName string) string { +func testAccAWSCognitoResourceServerConfig_full(identifier string, name string, poolName string) string { return fmt.Sprintf(` resource "aws_cognito_resource_server" "main" { identifier = "%s" name = "%s" scope = { - scope_name = "%s" - scope_description = "%s" + scope_name = "scope_1_name" + scope_description = "scope_1_description" + } + + scope = { + scope_name = "scope_2_name" + scope_description = "scope_2_description" } user_pool_id = "${aws_cognito_user_pool.main.id}" @@ -142,5 +145,5 @@ resource "aws_cognito_resource_server" "main" { resource "aws_cognito_user_pool" "main" { name = "%s" } -`, identifier, name, scopeName, scopeDescription, poolName) +`, identifier, name, poolName) }