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

r/api_gateway_usage_plan - support method specific throttle settings #21461

Merged
merged 3 commits into from
Oct 25, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .changelog/21461.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_apigateway_usage_plan : Add `throttle` argument for `api_stages` block.
```
104 changes: 103 additions & 1 deletion internal/service/apigateway/usage_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ func ResourceUsagePlan() *schema.Resource {
Type: schema.TypeString,
Required: true,
},
"throttle": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"path": {
Type: schema.TypeString,
Required: true,
},
"burst_limit": {
Type: schema.TypeInt,
Default: 0,
Optional: true,
},
"rate_limit": {
Type: schema.TypeFloat,
Default: 0,
Optional: true,
},
},
},
},
},
},
},
Expand Down Expand Up @@ -314,11 +336,28 @@ func resourceUsagePlanUpdate(d *schema.ResourceData, meta interface{}) error {
if len(ns) > 0 {
for _, v := range ns {
m := v.(map[string]interface{})
id := fmt.Sprintf("%s:%s", m["api_id"].(string), m["stage"].(string))
operations = append(operations, &apigateway.PatchOperation{
Op: aws.String(apigateway.OpAdd),
Path: aws.String("/apiStages"),
Value: aws.String(fmt.Sprintf("%s:%s", m["api_id"].(string), m["stage"].(string))),
Value: aws.String(id),
})
if t, ok := m["throttle"].(*schema.Set); ok && t.Len() > 0 {
for _, throttle := range t.List() {

th := throttle.(map[string]interface{})
operations = append(operations, &apigateway.PatchOperation{
Op: aws.String(apigateway.OpReplace),
Path: aws.String(fmt.Sprintf("/apiStages/%s/throttle/%s/rateLimit", id, th["path"].(string))),
Value: aws.String(strconv.FormatFloat(th["rate_limit"].(float64), 'f', -1, 64)),
})
operations = append(operations, &apigateway.PatchOperation{
Op: aws.String(apigateway.OpReplace),
Path: aws.String(fmt.Sprintf("/apiStages/%s/throttle/%s/burstLimit", id, th["path"].(string))),
Value: aws.String(strconv.Itoa(th["burst_limit"].(int))),
})
}
}
}
}
}
Expand Down Expand Up @@ -481,6 +520,10 @@ func resourceUsagePlanDelete(d *schema.ResourceData, meta interface{}) error {
UsagePlanId: aws.String(d.Id()),
})

if tfawserr.ErrMessageContains(err, apigateway.ErrCodeNotFoundException, "") {
return nil
}

if err != nil {
return fmt.Errorf("error deleting API gateway usage plan: %w", err)
}
Expand All @@ -504,6 +547,10 @@ func expandApiGatewayUsageApiStages(s *schema.Set) []*apigateway.ApiStage {
stage.Stage = aws.String(v)
}

if v, ok := mStage["throttle"].(*schema.Set); ok && v.Len() > 0 {
stage.Throttle = expandUsagePlanApiGatewayApiStagesThrottleSettings(v.List())
}

stages = append(stages, stage)
}

Expand Down Expand Up @@ -562,6 +609,7 @@ func flattenApiGatewayUsageApiStages(s []*apigateway.ApiStage) []map[string]inte
stage := make(map[string]interface{})
stage["api_id"] = aws.StringValue(bd.ApiId)
stage["stage"] = aws.StringValue(bd.Stage)
stage["throttle"] = flattenUsagePlanApiGatewayApiStagesThrottleSettings(bd.Throttle)

stages = append(stages, stage)
}
Expand Down Expand Up @@ -613,3 +661,57 @@ func flattenApiGatewayUsagePlanQuota(s *apigateway.QuotaSettings) []map[string]i

return []map[string]interface{}{settings}
}

func expandUsagePlanApiGatewayApiStagesThrottleSettings(tfList []interface{}) map[string]*apigateway.ThrottleSettings {
if len(tfList) == 0 {
return nil
}

apiObjects := map[string]*apigateway.ThrottleSettings{}

for _, tfMapRaw := range tfList {
tfMap, ok := tfMapRaw.(map[string]interface{})

if !ok {
continue
}

apiObject := &apigateway.ThrottleSettings{}

if v, ok := tfMap["burst_limit"].(int); ok {
apiObject.BurstLimit = aws.Int64(int64(v))
}

if v, ok := tfMap["rate_limit"].(float64); ok {
apiObject.RateLimit = aws.Float64(v)
}

if v, ok := tfMap["path"].(string); ok && v != "" {
apiObjects[v] = apiObject
}
}

return apiObjects
}

func flattenUsagePlanApiGatewayApiStagesThrottleSettings(apiObjects map[string]*apigateway.ThrottleSettings) []interface{} {
if len(apiObjects) == 0 {
return nil
}

var tfList []interface{}

for k, apiObject := range apiObjects {
if apiObject == nil {
continue
}

tfList = append(tfList, map[string]interface{}{
"path": k,
"rate_limit": aws.Float64Value(apiObject.RateLimit),
"burst_limit": aws.Int64Value(apiObject.BurstLimit),
})
}

return tfList
}
115 changes: 115 additions & 0 deletions internal/service/apigateway/usage_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,64 @@ func TestAccAPIGatewayUsagePlan_APIStages_multiple(t *testing.T) {
})
}

func TestAccAPIGatewayUsagePlan_APIStages_throttle(t *testing.T) {
var conf apigateway.UsagePlan
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_api_gateway_usage_plan.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckAPIGatewayTypeEDGE(t) },
ErrorCheck: acctest.ErrorCheck(t, apigateway.EndpointsID),
Providers: acctest.Providers,
CheckDestroy: testAccCheckUsagePlanDestroy,
Steps: []resource.TestStep{
{
Config: testAccUsagePlanAPIStagesConfigThrottle(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckUsagePlanExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "api_stages.#", "1"),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "api_stages.*", map[string]string{
"stage": "test",
"throttle.#": "1",
}),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "api_stages.0.throttle.*", map[string]string{
"path": "/test/GET",
"burst_limit": "3",
"rate_limit": "6",
}),
),
},
{
Config: testAccUsagePlanAPIStagesConfigThrottleMulti(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckUsagePlanExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "api_stages.#", "2"),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "api_stages.*", map[string]string{
"stage": "foo",
"throttle.#": "1",
}),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "api_stages.*", map[string]string{
"stage": "test",
"throttle.#": "1",
}),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "api_stages.0.throttle.*", map[string]string{
"path": "/test/GET",
"burst_limit": "3",
"rate_limit": "6",
}),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccAPIGatewayUsagePlan_disappears(t *testing.T) {
var conf apigateway.UsagePlan
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
Expand All @@ -477,6 +535,7 @@ func TestAccAPIGatewayUsagePlan_disappears(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckUsagePlanExists(resourceName, &conf),
acctest.CheckResourceDisappears(acctest.Provider, tfapigateway.ResourceUsagePlan(), resourceName),
acctest.CheckResourceDisappears(acctest.Provider, tfapigateway.ResourceUsagePlan(), resourceName),
),
ExpectNonEmptyPlan: true,
},
Expand Down Expand Up @@ -730,6 +789,62 @@ resource "aws_api_gateway_usage_plan" "test" {
`, rName)
}

func testAccUsagePlanAPIStagesConfigThrottle(rName string) string {
return testAccUsagePlanConfig(rName) + fmt.Sprintf(`
resource "aws_api_gateway_usage_plan" "test" {
name = "%s"

throttle_settings {
burst_limit = 3
rate_limit = 6
}

api_stages {
api_id = aws_api_gateway_rest_api.test.id
stage = aws_api_gateway_deployment.test.stage_name
throttle {
path = "${aws_api_gateway_resource.test.path}/${aws_api_gateway_method.test.http_method}"
burst_limit = 3
rate_limit = 6
}
}
}
`, rName)
}

func testAccUsagePlanAPIStagesConfigThrottleMulti(rName string) string {
return testAccUsagePlanConfig(rName) + fmt.Sprintf(`
resource "aws_api_gateway_usage_plan" "test" {
name = "%s"

throttle_settings {
burst_limit = 3
rate_limit = 6
}

api_stages {
api_id = aws_api_gateway_rest_api.test.id
stage = aws_api_gateway_deployment.test.stage_name
throttle {
path = "${aws_api_gateway_resource.test.path}/${aws_api_gateway_method.test.http_method}"
burst_limit = 3
rate_limit = 6
}
}

api_stages {
api_id = aws_api_gateway_rest_api.test.id
stage = aws_api_gateway_deployment.foo.stage_name
throttle {
path = "${aws_api_gateway_resource.test.path}/${aws_api_gateway_method.test.http_method}"
burst_limit = 3
rate_limit = 6
}
}
}
`, rName)
}

func testAccUsagePlanAPIStagesModifiedConfig(rName string) string {
return testAccUsagePlanConfig(rName) + fmt.Sprintf(`
resource "aws_api_gateway_usage_plan" "test" {
Expand Down
7 changes: 7 additions & 0 deletions website/docs/r/api_gateway_usage_plan.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ The API Gateway Usage Plan argument layout is a structure composed of several su

* `api_id` (Required) - API Id of the associated API stage in a usage plan.
* `stage` (Required) - API stage name of the associated API stage in a usage plan.
* `throttle` - (Optional) The [throttling limits](#throttle) of the usage plan.

##### Throttle

* `path` (Required) - The method to apply the throttle settings for. Specfiy the path and method, for example `/test/GET`.
* `burst_limit` (Optional) - The API request burst limit, the maximum rate limit over a time ranging from one to a few seconds, depending upon whether the underlying token bucket is at its full capacity.
* `rate_limit` (Optional) - The API request steady-state rate limit.

#### Quota Settings Arguments

Expand Down