diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index f7869a9cf604..343c5015a756 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -114,6 +114,7 @@ func Provider() terraform.ResourceProvider { "aws_ami": resourceAwsAmi(), "aws_ami_copy": resourceAwsAmiCopy(), "aws_ami_from_instance": resourceAwsAmiFromInstance(), + "aws_api_gateway_account": resourceAwsApiGatewayAccount(), "aws_api_gateway_api_key": resourceAwsApiGatewayApiKey(), "aws_api_gateway_authorizer": resourceAwsApiGatewayAuthorizer(), "aws_api_gateway_deployment": resourceAwsApiGatewayDeployment(), diff --git a/builtin/providers/aws/resource_aws_api_gateway_account.go b/builtin/providers/aws/resource_aws_api_gateway_account.go new file mode 100644 index 000000000000..2f562e63b584 --- /dev/null +++ b/builtin/providers/aws/resource_aws_api_gateway_account.go @@ -0,0 +1,124 @@ +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/apigateway" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsApiGatewayAccount() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsApiGatewayAccountUpdate, + Read: resourceAwsApiGatewayAccountRead, + Update: resourceAwsApiGatewayAccountUpdate, + Delete: resourceAwsApiGatewayAccountDelete, + + Schema: map[string]*schema.Schema{ + "cloudwatch_role_arn": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "throttle_settings": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "burst_limit": &schema.Schema{ + Type: schema.TypeInt, + Computed: true, + }, + "rate_limit": &schema.Schema{ + Type: schema.TypeFloat, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func resourceAwsApiGatewayAccountRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigateway + + log.Printf("[INFO] Reading API Gateway Account %s", d.Id()) + account, err := conn.GetAccount(&apigateway.GetAccountInput{}) + if err != nil { + return err + } + + log.Printf("[DEBUG] Received API Gateway Account: %s", account) + + if _, ok := d.GetOk("cloudwatch_role_arn"); ok { + // CloudwatchRoleArn cannot be empty nor made empty via API + // This resource can however be useful w/out defining cloudwatch_role_arn + // (e.g. for referencing throttle_settings) + d.Set("cloudwatch_role_arn", account.CloudwatchRoleArn) + } + d.Set("throttle_settings", flattenApiGatewayThrottleSettings(account.ThrottleSettings)) + + return nil +} + +func resourceAwsApiGatewayAccountUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigateway + + input := apigateway.UpdateAccountInput{} + operations := make([]*apigateway.PatchOperation, 0) + + if d.HasChange("cloudwatch_role_arn") { + arn := d.Get("cloudwatch_role_arn").(string) + if len(arn) > 0 { + // Unfortunately AWS API doesn't allow empty ARNs, + // even though that's default settings for new AWS accounts + // BadRequestException: The role ARN is not well formed + operations = append(operations, &apigateway.PatchOperation{ + Op: aws.String("replace"), + Path: aws.String("/cloudwatchRoleArn"), + Value: aws.String(arn), + }) + } + } + input.PatchOperations = operations + + log.Printf("[INFO] Updating API Gateway Account: %s", input) + + // Retry due to eventual consistency of IAM + expectedErrMsg := "The role ARN does not have required permissions set to API Gateway" + var out *apigateway.Account + var err error + err = resource.Retry(2*time.Minute, func() *resource.RetryError { + out, err = conn.UpdateAccount(&input) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "BadRequestException" && + awsErr.Message() == expectedErrMsg { + log.Printf("[DEBUG] Retrying API Gateway Account update: %s", awsErr) + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + + return nil + }) + if err != nil { + return fmt.Errorf("Updating API Gateway Account failed: %s", err) + } + log.Printf("[DEBUG] API Gateway Account updated: %s", out) + + d.SetId("api-gateway-account") + return resourceAwsApiGatewayAccountRead(d, meta) +} + +func resourceAwsApiGatewayAccountDelete(d *schema.ResourceData, meta interface{}) error { + // There is no API for "deleting" account or resetting it to "default" settings + d.SetId("") + return nil +} diff --git a/builtin/providers/aws/resource_aws_api_gateway_account_test.go b/builtin/providers/aws/resource_aws_api_gateway_account_test.go new file mode 100644 index 000000000000..c50339f7edb7 --- /dev/null +++ b/builtin/providers/aws/resource_aws_api_gateway_account_test.go @@ -0,0 +1,205 @@ +package aws + +import ( + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/service/apigateway" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSAPIGatewayAccount_basic(t *testing.T) { + var conf apigateway.Account + + expectedRoleArn_first := regexp.MustCompile("[0-9]+") + expectedRoleArn_second := regexp.MustCompile("[0-9]+") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayAccountDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSAPIGatewayAccountConfig_updated, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayAccountExists("aws_api_gateway_account.test", &conf), + testAccCheckAWSAPIGatewayAccountCloudwatchRoleArn(&conf, expectedRoleArn_first), + resource.TestMatchResourceAttr("aws_api_gateway_account.test", "cloudwatch_role_arn", expectedRoleArn_first), + ), + }, + resource.TestStep{ + Config: testAccAWSAPIGatewayAccountConfig_updated2, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayAccountExists("aws_api_gateway_account.test", &conf), + testAccCheckAWSAPIGatewayAccountCloudwatchRoleArn(&conf, expectedRoleArn_second), + resource.TestMatchResourceAttr("aws_api_gateway_account.test", "cloudwatch_role_arn", expectedRoleArn_second), + ), + }, + resource.TestStep{ + Config: testAccAWSAPIGatewayAccountConfig_empty, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayAccountExists("aws_api_gateway_account.test", &conf), + testAccCheckAWSAPIGatewayAccountCloudwatchRoleArn(&conf, expectedRoleArn_second), + ), + }, + }, + }) +} + +func testAccCheckAWSAPIGatewayAccountCloudwatchRoleArn(conf *apigateway.Account, expectedArn *regexp.Regexp) resource.TestCheckFunc { + return func(s *terraform.State) error { + if expectedArn == nil && conf.CloudwatchRoleArn == nil { + return nil + } + if expectedArn == nil && conf.CloudwatchRoleArn != nil { + return fmt.Errorf("Expected empty CloudwatchRoleArn, given: %q", *conf.CloudwatchRoleArn) + } + if expectedArn != nil && conf.CloudwatchRoleArn == nil { + return fmt.Errorf("Empty CloudwatchRoleArn, expected: %q", expectedArn) + } + if !expectedArn.MatchString(*conf.CloudwatchRoleArn) { + return fmt.Errorf("CloudwatchRoleArn didn't match. Expected: %q, Given: %q", expectedArn, *conf.CloudwatchRoleArn) + } + return nil + } +} + +func testAccCheckAWSAPIGatewayAccountExists(n string, res *apigateway.Account) 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 fmt.Errorf("No API Gateway Account ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).apigateway + + req := &apigateway.GetAccountInput{} + describe, err := conn.GetAccount(req) + if err != nil { + return err + } + if describe == nil { + return fmt.Errorf("Got nil account ?!") + } + + *res = *describe + + return nil + } +} + +func testAccCheckAWSAPIGatewayAccountDestroy(s *terraform.State) error { + // Intentionally noop + // as there is no API method for deleting or resetting account settings + return nil +} + +const testAccAWSAPIGatewayAccountConfig_empty = ` +resource "aws_api_gateway_account" "test" { +} +` + +const testAccAWSAPIGatewayAccountConfig_updated = ` +resource "aws_api_gateway_account" "test" { + cloudwatch_role_arn = "${aws_iam_role.cloudwatch.arn}" +} + +resource "aws_iam_role" "cloudwatch" { + name = "api_gateway_cloudwatch_global" + assume_role_policy = < **Note:** As there is no API method for deleting account settings or resetting it to defaults, destroying this resource will keep your account settings intact + +## Example Usage + +``` +resource "aws_api_gateway_account" "demo" { + cloudwatch_role_arn = "${aws_iam_role.cloudwatch.arn}" +} + +resource "aws_iam_role" "cloudwatch" { + name = "api_gateway_cloudwatch_global" + assume_role_policy = <> API Gateway Resources