Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Resource: aws_cognito_identity_provider #3601

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ func Provider() terraform.ResourceProvider {
"aws_config_delivery_channel": resourceAwsConfigDeliveryChannel(),
"aws_cognito_identity_pool": resourceAwsCognitoIdentityPool(),
"aws_cognito_identity_pool_roles_attachment": resourceAwsCognitoIdentityPoolRolesAttachment(),
"aws_cognito_identity_provider": resourceAwsCognitoIdentityProvider(),
"aws_cognito_user_group": resourceAwsCognitoUserGroup(),
"aws_cognito_user_pool": resourceAwsCognitoUserPool(),
"aws_cognito_user_pool_client": resourceAwsCognitoUserPoolClient(),
Expand Down
180 changes: 180 additions & 0 deletions aws/resource_aws_cognito_identity_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package aws

import (
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsCognitoIdentityProvider() *schema.Resource {
return &schema.Resource{
Create: resourceAwsCognitoIdentityProviderCreate,
Read: resourceAwsCognitoIdentityProviderRead,
Update: resourceAwsCognitoIdentityProviderUpdate,
Delete: resourceAwsCognitoIdentityProviderDelete,

Timeouts: &schema.ResourceTimeout{
Delete: schema.DefaultTimeout(5 * time.Minute),
},

Schema: map[string]*schema.Schema{
"attribute_mapping": {
Type: schema.TypeMap,
Optional: true,
},

"idp_identifiers": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},

"provider_details": {
Type: schema.TypeMap,
Optional: true,
Computed: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this was intentional, but it does not seem like we should be ignoring this from the API if its not in the Terraform configuration. I'll double check from the acceptance testing. Also, the API/SDK documentation seems to imply this should be Required: true.

},

"provider_name": {
Type: schema.TypeString,
Required: true,
},

"provider_type": {
Type: schema.TypeString,
Required: true,
},

"user_pool_id": {
Type: schema.TypeString,
Required: true,
Copy link

@ldenman ldenman May 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should user_pool_id be ForceNew? If the pool id changes, wouldn't a new provider need to be created?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

},
},
}
}

func resourceAwsCognitoIdentityProviderCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cognitoidpconn
log.Print("[DEBUG] Creating Cognito Identity Provider")

name := aws.String(d.Get("provider_name").(string))
params := &cognitoidentityprovider.CreateIdentityProviderInput{
ProviderName: name,
ProviderType: aws.String(d.Get("provider_type").(string)),
UserPoolId: aws.String(d.Get("user_pool_id").(string)),
}

if v, ok := d.GetOk("attribute_mapping"); ok {
params.AttributeMapping = expandCognitoIdentityProviderMap(v.(map[string]interface{}))
}

if v, ok := d.GetOk("provider_details"); ok {
params.ProviderDetails = expandCognitoIdentityProviderMap(v.(map[string]interface{}))
}

if v, ok := d.GetOk("idp_identifiers"); ok {
params.IdpIdentifiers = expandStringList(v.([]interface{}))
}

_, err := conn.CreateIdentityProvider(params)
if err != nil {
return fmt.Errorf("Error creating Cognito Identity Provider: %s", err)
}

d.SetId(*name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to easily support import, let's prepend the resource ID with the user pool ID:

d.SetId(fmt.Sprintf("%s:%s", userPoolID, providerName))

Then with configuring the passthrough Importer in the resource, we can support import via terraform import aws_cognito_identity_provider.example xxx_yyyyyy:example


return resourceAwsCognitoIdentityProviderRead(d, meta)
}

func resourceAwsCognitoIdentityProviderRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cognitoidpconn
log.Printf("[DEBUG] Reading Cognito Identity Provider: %s", d.Id())

ret, err := conn.DescribeIdentityProvider(&cognitoidentityprovider.DescribeIdentityProviderInput{
ProviderName: aws.String(d.Id()),
UserPoolId: aws.String(d.Get("user_pool_id").(string)),
})

if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ResourceNotFoundException" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpick: we have a helper function to simplify this logic: isAWSErr(err, cognitoidentity.ErrCodeResourceNotFoundException, "")

d.SetId("")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When removing a resource from the state, we prefer to provide a warning log, e.g.

log.Printf("[WARN] Cognito Identity Provider %q not found, removing from state", d.Id())

return nil
}
return err
}

ip := ret.IdentityProvider
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should perform a nil check here in the unlikely scenario that the API does not error and also omits this attribute.

d.Set("provider_name", ip.ProviderName)
d.Set("provider_type", ip.ProviderType)
d.Set("user_pool_id", ip.UserPoolId)

if err := d.Set("attribute_mapping", flattenCognitoIdentityProviderMap(ip.AttributeMapping)); err != nil {
return fmt.Errorf("[DEBUG] Error setting attribute_mapping error: %#v", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpick: these error messages will be returned to the operator, so they are not DEBUG and should return the normal error string via %s

}

if err := d.Set("provider_details", flattenCognitoIdentityProviderMap(ip.ProviderDetails)); err != nil {
return fmt.Errorf("[DEBUG] Error setting provider_details error: %#v", err)
}

if err := d.Set("idp_identifiers", flattenStringList(ip.IdpIdentifiers)); err != nil {
return fmt.Errorf("[DEBUG] Error setting idp_identifiers error: %#v", err)
}

return nil
}

func resourceAwsCognitoIdentityProviderUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cognitoidpconn
log.Print("[DEBUG] Updating Cognito Identity Provider")

params := &cognitoidentityprovider.UpdateIdentityProviderInput{
UserPoolId: aws.String(d.Get("UserPoolId").(string)),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering... is "d.Get("UserPoolId") correct? Or should it be: d.Get("user_pool_id")?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will change it.

ProviderName: aws.String(d.Id()),
}

if d.HasChange("attribute_mapping") {
params.AttributeMapping = expandCognitoIdentityProviderMap(d.Get("attribute_mapping").(map[string]interface{}))
}

if d.HasChange("provider_details") {
params.ProviderDetails = expandCognitoIdentityProviderMap(d.Get("provider_details").(map[string]interface{}))
}

if d.HasChange("idp_identifiers") {
params.IdpIdentifiers = expandStringList(d.Get("supported_login_providers").([]interface{}))
}

_, err := conn.UpdateIdentityProvider(params)
if err != nil {
return fmt.Errorf("Error updating Cognito Identity Provider: %s", err)
}

return resourceAwsCognitoIdentityProviderRead(d, meta)
}

func resourceAwsCognitoIdentityProviderDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cognitoidpconn
log.Printf("[DEBUG] Deleting Cognito Identity Provider: %s", d.Id())

return resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This retry loop looks extraneous as there is no return resource.RetryableError(). It should be removed along with the customizable deletion timeout.

_, err := conn.DeleteIdentityProvider(&cognitoidentityprovider.DeleteIdentityProviderInput{
ProviderName: aws.String(d.Id()),
UserPoolId: aws.String(d.Get("user_pool_id").(string)),
})

if err == nil {
d.SetId("")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpick: d.SetId("") is extraneous in delete functions.

return nil
}

return resource.NonRetryableError(err)
})
}
95 changes: 95 additions & 0 deletions aws/resource_aws_cognito_identity_provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package aws

import (
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSCognitoIdentityProvider_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCognitoIdentityProviderDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCognitoIdentityProviderConfig_basic(),
Check: resource.ComposeAggregateTestCheckFunc(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing an testAccCheckAWSCognitoIdentityProviderExists() function, which we usually call first in a set of checks to help determine if the physical resource exists in the API as well as pass back the API response object back as a variable we can later reference in other checks.

resource.TestCheckResourceAttr("aws_cognito_identity_provider.tf_test_provider", "provider_name", "Google"),
resource.TestCheckResourceAttr("aws_cognito_identity_provider.tf_test_provider", "provider_type", "Google"),
resource.TestCheckResourceAttr("aws_cognito_identity_provider.tf_test_provider", "provider_details.%", "9"),
resource.TestCheckResourceAttr("aws_cognito_identity_provider.tf_test_provider", "provider_details.authorize_scopes", "email"),
resource.TestCheckResourceAttr("aws_cognito_identity_provider.tf_test_provider", "provider_details.authorize_url", "https://accounts.google.com/o/oauth2/v2/auth"),
resource.TestCheckResourceAttr("aws_cognito_identity_provider.tf_test_provider", "provider_details.client_id", "test-url.apps.googleusercontent.com"),
resource.TestCheckResourceAttr("aws_cognito_identity_provider.tf_test_provider", "provider_details.client_secret", "client_secret"),
resource.TestCheckResourceAttr("aws_cognito_identity_provider.tf_test_provider", "provider_details.attributes_url", "https://people.googleapis.com/v1/people/me?personFields="),
resource.TestCheckResourceAttr("aws_cognito_identity_provider.tf_test_provider", "provider_details.attributes_url_add_attributes", "true"),
resource.TestCheckResourceAttr("aws_cognito_identity_provider.tf_test_provider", "provider_details.token_request_method", "POST"),
resource.TestCheckResourceAttr("aws_cognito_identity_provider.tf_test_provider", "provider_details.token_url", "https://www.googleapis.com/oauth2/v4/token"),
resource.TestCheckResourceAttr("aws_cognito_identity_provider.tf_test_provider", "provider_details.oidc_issuer", "https://accounts.google.com"),
),
},
},
})
}

func testAccCheckAWSCognitoIdentityProviderDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).cognitoidpconn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_cognito_identity_provider" {
continue
}

_, err := conn.DescribeIdentityProvider(&cognitoidentityprovider.DescribeIdentityProviderInput{
ProviderName: aws.String(rs.Primary.ID),
UserPoolId: aws.String(rs.Primary.Attributes["user_pool_id"]),
})

if err != nil {
if wserr, ok := err.(awserr.Error); ok && wserr.Code() == "ResourceNotFoundException" {
return nil
}
return err
}
}

return nil
}

func testAccAWSCognitoIdentityProviderConfig_basic() string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpick: the formatting is fairly inconsistent throughout this test configuration which makes it harder to read

return `

resource "aws_cognito_user_pool" "tf_test_pool" {
name = "tfmytestpool"
auto_verified_attributes = ["email"]
}

resource "aws_cognito_identity_provider" "tf_test_provider" {
user_pool_id = "${aws_cognito_user_pool.tf_test_pool.id}"
provider_name = "Google"
provider_type = "Google"

provider_details {
authorize_scopes = "email"
client_id = "test-url.apps.googleusercontent.com"
client_secret = "client_secret"
attributes_url = "https://people.googleapis.com/v1/people/me?personFields="
attributes_url_add_attributes = "true"
authorize_url = "https://accounts.google.com/o/oauth2/v2/auth"
oidc_issuer = "https://accounts.google.com"
token_request_method = "POST"
token_url = "https://www.googleapis.com/oauth2/v4/token"
}

attribute_mapping {
email = "email"
username = "sub"
}
}
`
}
17 changes: 17 additions & 0 deletions aws/structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -3035,6 +3035,23 @@ func flattenCognitoIdentityPoolRoles(config map[string]*string) map[string]strin
return m
}

func expandCognitoIdentityProviderMap(config map[string]interface{}) map[string]*string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a (admittedly hard to find) helper function to perform this already: stringMapToPointers()

m := map[string]*string{}
for k, v := range config {
s := v.(string)
m[k] = &s
}
return m
}

func flattenCognitoIdentityProviderMap(config map[string]*string) map[string]string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AWS SDK already provides a helper function to perform this: aws.StringValueMap()

m := map[string]string{}
for k, v := range config {
m[k] = *v
}
return m
}

func expandCognitoIdentityPoolRoleMappingsAttachment(rms []interface{}) map[string]*cognitoidentity.RoleMapping {
values := make(map[string]*cognitoidentity.RoleMapping, 0)

Expand Down
57 changes: 57 additions & 0 deletions website/docs/r/cognito_identity_provider.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
layout: "aws"
page_title: "AWS: aws_cognito_identity_provider"
side_bar_current: "docs-aws-resource-cognito-identity-provider"
description: |-
Provides a Cognito User Identity Provider resource.
---

# aws_cognito_identity_provider

Provides a Cognito User Identity Provider resource.

## Example Usage

### Basic configuration

```hcl
resource "aws_cognito_user_pool" "example" {
name = "example-pool"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpick: the formatting is slightly off in the example configuration here and below with email

auto_verified_attributes = ["email"]
}

resource "aws_cognito_identity_provider" "example_provider" {
user_pool_id = "${aws_cognito_user_pool.example.id}"
provider_name = "example_name"
provider_type = "Google"

provider_details {
authorize_scopes = "email"
client_id = "your client_id"
client_secret = "your client_secret"
}

attribute_mapping {
email = "email"
username = "sub"
}
}
```


## Argument Reference

The following arguments are supported:

* `user_pool_id` (Required) - The user pool id
* `provider_name` (Required) - The provider name
* `provider_type` (Required) - The provider type. [See AWS API for valid values](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_CreateIdentityProvider.html#CognitoUserPools-CreateIdentityProvider-request-ProviderType)
* `attribute_mapping` (Optional) - The map of attribute mapping of user pool attributes. [AttributeMapping in AWS API documentation](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_CreateIdentityProvider.html#CognitoUserPools-CreateIdentityProvider-request-AttributeMapping)
* `idp_identifiers` (Optional) - The list of identity providers.
* `provider_details` (Optional) - The map of identity details, such as access token

## Timeouts

`aws_cognito_identity_provider` provides the following
[Timeouts](/docs/configuration/resources.html#timeouts) configuration options:
- `delete` - (Default `5 minutes`) Used for provider deletion