From a0920e2e59dc66886cbfbfc8aa3dce000f8c4c7c Mon Sep 17 00:00:00 2001 From: Benjamin Clark Date: Tue, 17 Sep 2024 19:35:31 +0100 Subject: [PATCH 1/2] Initial module setup --- .terraform-version | 1 + README.md | 64 +++++++++++++++++++++++- api_gateway_api_key.tf | 17 +++++++ api_gateway_deployment.tf | 16 ++++++ api_gateway_stage.tf | 8 +++ api_gateway_usage_plan.tf | 27 ++++++++++ api_gateway_usage_plan_key.tf | 12 +++++ lambda_permission.tf | 42 ++++++++++++++++ main.tf | 9 ++++ rest_api.tf | 21 ++++++++ variables.tf | 92 +++++++++++++++++++++++++++++++++++ 11 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 .terraform-version create mode 100644 api_gateway_api_key.tf create mode 100644 api_gateway_deployment.tf create mode 100644 api_gateway_stage.tf create mode 100644 api_gateway_usage_plan.tf create mode 100644 api_gateway_usage_plan_key.tf create mode 100644 lambda_permission.tf create mode 100644 main.tf create mode 100644 rest_api.tf create mode 100644 variables.tf diff --git a/.terraform-version b/.terraform-version new file mode 100644 index 0000000..8e03717 --- /dev/null +++ b/.terraform-version @@ -0,0 +1 @@ +1.5.1 \ No newline at end of file diff --git a/README.md b/README.md index c87a030..5162392 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,73 @@ The below documentation is intended to assist users in utilising the module, the the module itself, and the [examples](#examples) section which has examples of how to utilise the module. +## Requirements +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | ~> 1.5.0 | +| [aws](#requirement\_aws) | >= 5.61.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 5.67.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_api_gateway_api_key.api_keys](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_api_key) | resource | +| [aws_api_gateway_deployment.deployments](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_deployment) | resource | +| [aws_api_gateway_rest_api.api_gateway](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_rest_api) | resource | +| [aws_api_gateway_stage.stages](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_stage) | resource | +| [aws_api_gateway_usage_plan.usage_plans](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_usage_plan) | resource | +| [aws_api_gateway_usage_plan_key.keys](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_usage_plan_key) | resource | +| [aws_lambda_permission.allow_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [application\_name](#input\_application\_name) | Name of the application utilising resource. | `string` | n/a | yes | +| [environment](#input\_environment) | Which environment this is being instantiated in. | `string` | n/a | yes | +| [raw\_api\_gateway\_rest\_apis](#input\_raw\_api\_gateway\_rest\_apis) | Data structure
---------------
A list of dictionaries, where each dictionary has the following attributes:

REQUIRED
---------
- suffix : Suffix to use when creating the RESTAPI Gateway
- open\_api\_file\_path : Path to OpenAPI definition file
- description : A human-friendly description of the API
- tags : A dictionary of tags, required to tag Stage to ensure its protected by WAF.


OPTIONAL
---------
- template\_input : A dictionary of variable input for the OpenAPI definition file (leave blank if no template required)
- allowed\_lambdas : A list of strings, where each string is the function\_name of a lambda to allow access to.
- quota\_limit : Maximum number of requests that can be made in a given time period, defaults to 10.
- quota\_offset : Number of requests subtracted from the given limit in the initial time period, defaults to 0.
- quota\_period : Time period in which the limit applies. Valid values are "DAY", "WEEK" or "MONTH". Defaults to "DAY"
- burst\_limit : 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. Defaults to 5.
- rate\_limit : The API request steady-state rate limit, defaults to 10.
- api\_keys : List of strings, where each string is name of an API key to create for the API, defaults to empty list. |
list(
object({
suffix = string,
description = string,
tags = map(string),
open_api_file_path = string,
template_input = optional(map(string), {}),
quota_limit = optional(number, 10),
quota_offset = optional(number, 0),
quota_period = optional(string, "DAY"),
burst_limit = optional(number, 5),
rate_limit = optional(number, 10)
allowed_lambdas = optional(list(string), [])
api_keys = optional(list(string), [])
})
)
| n/a | yes | + +## Outputs + +No outputs. ## Data structure - +``` +Data structure +--------------- +A list of dictionaries, where each dictionary has the following attributes: + +REQUIRED +--------- +- suffix : Suffix to use when creating the RESTAPI Gateway +- open_api_file_path : Path to OpenAPI definition file +- description : A human-friendly description of the API +- tags : A dictionary of tags, required to tag Stage to ensure its protected by WAF. + + +OPTIONAL +--------- +- template_input : A dictionary of variable input for the OpenAPI definition file (leave blank if no template required) +- allowed_lambdas : A list of strings, where each string is the function_name of a lambda to allow access to. +- quota_limit : Maximum number of requests that can be made in a given time period, defaults to 10. +- quota_offset : Number of requests subtracted from the given limit in the initial time period, defaults to 0. +- quota_period : Time period in which the limit applies. Valid values are "DAY", "WEEK" or "MONTH". Defaults to "DAY" +- burst_limit : 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. Defaults to 5. +- rate_limit : The API request steady-state rate limit, defaults to 10. +- api_keys : List of strings, where each string is name of an API key to create for the API, defaults to empty list. +``` ## Examples See `examples` folder for an example setup. diff --git a/api_gateway_api_key.tf b/api_gateway_api_key.tf new file mode 100644 index 0000000..2adb3cc --- /dev/null +++ b/api_gateway_api_key.tf @@ -0,0 +1,17 @@ +locals { + actual_api_keys = flatten([ + for api in var.raw_api_gateway_rest_apis : [ + for key in api.api_keys : { + api = api.suffix + name = format("%s/%s", api.suffix, key) + } + ] + ]) +} + +resource "aws_api_gateway_api_key" "api_keys" { + for_each = { for key in local.actual_api_keys : key.name => key } + + name = each.value["name"] + description = format("Automatically generated key for %s API Gateway - managed by Terraform", each.value["api"]) +} \ No newline at end of file diff --git a/api_gateway_deployment.tf b/api_gateway_deployment.tf new file mode 100644 index 0000000..b00bd12 --- /dev/null +++ b/api_gateway_deployment.tf @@ -0,0 +1,16 @@ +resource "aws_api_gateway_deployment" "deployments" { + for_each = { for api in local.actual_raw_api_gateway_rest_apis : api.suffix => api } + + rest_api_id = aws_api_gateway_rest_api.api_gateway[each.value["suffix"]].id + triggers = { + redeployment = sha1(jsonencode(aws_api_gateway_rest_api.api_gateway[each.value["suffix"]].body)) + } + + lifecycle { + create_before_destroy = true + } + + depends_on = [ + aws_api_gateway_rest_api.api_gateway + ] +} \ No newline at end of file diff --git a/api_gateway_stage.tf b/api_gateway_stage.tf new file mode 100644 index 0000000..0eb5b36 --- /dev/null +++ b/api_gateway_stage.tf @@ -0,0 +1,8 @@ +resource "aws_api_gateway_stage" "stages" { + for_each = { for api in local.actual_raw_api_gateway_rest_apis : api.suffix => api } + + deployment_id = aws_api_gateway_deployment.deployments[each.value["suffix"]].id + rest_api_id = aws_api_gateway_rest_api.api_gateway[each.value["suffix"]].id + stage_name = var.environment + tags = each.value["tags"] +} \ No newline at end of file diff --git a/api_gateway_usage_plan.tf b/api_gateway_usage_plan.tf new file mode 100644 index 0000000..36197b8 --- /dev/null +++ b/api_gateway_usage_plan.tf @@ -0,0 +1,27 @@ +resource "aws_api_gateway_usage_plan" "usage_plans" { + for_each = { for api in local.actual_raw_api_gateway_rest_apis : api.suffix => api } + + name = format("%s-usage-plan", each.value["suffix"]) + description = format("API Gateway usage plan for %s - managed by Terraform", each.value["suffix"]) + + api_stages { + api_id = aws_api_gateway_rest_api.api_gateway[each.value["suffix"]].id + stage = aws_api_gateway_stage.stages[each.value["suffix"]].stage_name + } + + quota_settings { + limit = try(each.value["quota_limit"], 10) + offset = try(each.value["quota_offset"], 0) + period = try(each.value["quota_period"], "DAY") + } + + throttle_settings { + burst_limit = try(each.value["burst_limit"], 5) + rate_limit = try(each.value["rate_limit"], 10) + } + + depends_on = [ + aws_api_gateway_rest_api.api_gateway, + aws_api_gateway_stage.stages + ] +} \ No newline at end of file diff --git a/api_gateway_usage_plan_key.tf b/api_gateway_usage_plan_key.tf new file mode 100644 index 0000000..4d21843 --- /dev/null +++ b/api_gateway_usage_plan_key.tf @@ -0,0 +1,12 @@ +resource "aws_api_gateway_usage_plan_key" "keys" { + for_each = { for key in local.actual_api_keys : key.name => key } + + key_id = aws_api_gateway_api_key.api_keys[each.value["name"]].id + key_type = "API_KEY" + usage_plan_id = aws_api_gateway_usage_plan.usage_plans[each.value["api"]].id + + depends_on = [ + aws_api_gateway_usage_plan.usage_plans, + aws_api_gateway_api_key.api_keys + ] +} \ No newline at end of file diff --git a/lambda_permission.tf b/lambda_permission.tf new file mode 100644 index 0000000..cdf8e67 --- /dev/null +++ b/lambda_permission.tf @@ -0,0 +1,42 @@ +locals { + actual_lambda_permissions = flatten([ + for api in local.actual_raw_api_gateway_rest_apis : [ + for lambda in api.allowed_lambdas : [ + { + key : format("%s/%s/stage", api.suffix, lambda) + function_name : lambda + source_arn : format("%s/*", aws_api_gateway_stage.stages[api.suffix].arn) + statement_id = format("AllowExecutionFrom%sAPIGatewayStage", replace(api.suffix, "-", "")) + }, + { + key : format("%s/%s/api", api.suffix, lambda) + function_name : lambda + source_arn : format("%s/*", aws_api_gateway_rest_api.api_gateway[api.suffix].arn) + statement_id = format("AllowExecutionFrom%sAPIGateway", replace(api.suffix, "-", "")) + }, + { + key : format("%s/%s/deployment", api.suffix, lambda) + function_name : lambda + source_arn : format("%s*", aws_api_gateway_deployment.deployments[api.suffix].execution_arn) + statement_id = format("AllowExecutionFrom%sAPIGatewayDeployment", replace(api.suffix, "-", "")) + } + ] + ] + ]) +} + +resource "aws_lambda_permission" "allow_execution" { + for_each = { for permission in local.actual_lambda_permissions : permission.key => permission } + + depends_on = [ + aws_api_gateway_stage.stages, + aws_api_gateway_rest_api.api_gateway, + aws_api_gateway_deployment.deployments + ] + + statement_id = each.value["statement_id"] + action = "lambda:InvokeFunction" + function_name = each.value["function_name"] + principal = "apigateway.amazonaws.com" + source_arn = each.value["source_arn"] +} \ No newline at end of file diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..f022823 --- /dev/null +++ b/main.tf @@ -0,0 +1,9 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.61.0" + } + } + required_version = "~> 1.5.0" +} \ No newline at end of file diff --git a/rest_api.tf b/rest_api.tf new file mode 100644 index 0000000..aec92d2 --- /dev/null +++ b/rest_api.tf @@ -0,0 +1,21 @@ +locals { + actual_raw_api_gateway_rest_apis = flatten([ + for api in var.raw_api_gateway_rest_apis : merge(api, { + open_api_definition = templatefile(api.open_api_file_path, try(api.template_input, {})) + }) + ]) +} + +resource "aws_api_gateway_rest_api" "api_gateway" { + + for_each = { for api in local.actual_raw_api_gateway_rest_apis : api.suffix => api } + + name = format(lower("aws-${var.environment}-${var.application_name}-%s"), each.value["suffix"]) + description = format("%s- managed by Terraform", each.value["description"]) + body = each.value["open_api_definition"] + tags = each.value["tags"] + + lifecycle { + create_before_destroy = true + } +} \ No newline at end of file diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..b4a8950 --- /dev/null +++ b/variables.tf @@ -0,0 +1,92 @@ +variable "environment" { + description = "Which environment this is being instantiated in." + type = string + validation { + condition = contains(["dev", "test", "prod"], var.environment) + error_message = "Must be either dev, test or prod" + } +} + +variable "application_name" { + description = "Name of the application utilising resource." + type = string +} + +variable "raw_api_gateway_rest_apis" { + description = <= 0) + ]) + error_message = "quota_limit must be greater than or equal to 0" + } + + validation { + condition = alltrue([ + for api in var.raw_api_gateway_rest_apis : (api.quota_offset >= 0) + ]) + error_message = "quota_offset must be greater than or equal to 0" + } + + validation { + condition = alltrue([ + for api in var.raw_api_gateway_rest_apis : contains(["DAY", "WEEK", "MONTH"], api.quota_period) + ]) + error_message = "quota_period must be one of the following: DAY, WEEK, MONTH" + } + + validation { + condition = alltrue([ + for api in var.raw_api_gateway_rest_apis : (api.burst_limit >= 0) + ]) + error_message = "burst_limit must be greater than or equal to 0" + } + + validation { + condition = alltrue([ + for api in var.raw_api_gateway_rest_apis : (api.rate_limit >= 0) + ]) + error_message = "rate_limit must be greater than or equal to 0" + } +} \ No newline at end of file From a7e31acded7ed915d112b4271d2f5bc29081eddd Mon Sep 17 00:00:00 2001 From: Benjamin Clark Date: Tue, 17 Sep 2024 19:46:55 +0100 Subject: [PATCH 2/2] Add examples --- .github/workflows/commit-to-pr.yaml | 6 +- README.md | 3 +- api_gateway_stage.tf | 1 - examples/api_gateway/files/petstore.yaml | 806 +++++++++++++++++++++++ examples/api_gateway/locals.tf | 18 + examples/api_gateway/main.tf | 22 + examples/api_gateway/variables.tf | 15 + rest_api.tf | 1 - variables.tf | 2 - 9 files changed, 865 insertions(+), 9 deletions(-) create mode 100644 examples/api_gateway/files/petstore.yaml create mode 100644 examples/api_gateway/locals.tf create mode 100644 examples/api_gateway/main.tf create mode 100644 examples/api_gateway/variables.tf diff --git a/.github/workflows/commit-to-pr.yaml b/.github/workflows/commit-to-pr.yaml index 2bd9dc0..5bb9a7d 100644 --- a/.github/workflows/commit-to-pr.yaml +++ b/.github/workflows/commit-to-pr.yaml @@ -20,7 +20,7 @@ jobs: validation: strategy: matrix: - folder: ["add", "folders", "here"] + folder: ["./", "examples/api_gateway"] name: Terraform validate for ${{ matrix.folder }} runs-on: ubuntu-20.04 steps: @@ -41,7 +41,7 @@ jobs: linting: strategy: matrix: - folder: ["add", "folders", "here"] + folder: ["./", "examples/api_gateway"] name: Terraform lint for ${{ matrix.folder }} runs-on: ubuntu-20.04 steps: @@ -59,7 +59,7 @@ jobs: plan: strategy: matrix: - folder: ["add", "folders", "here"] + folder: ["examples/api_gateway"] name: Terraform plan for ${{ matrix.folder }} runs-on: ubuntu-20.04 needs: [validation, linting] diff --git a/README.md b/README.md index 5162392..9766afa 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ No modules. |------|-------------|------|---------|:--------:| | [application\_name](#input\_application\_name) | Name of the application utilising resource. | `string` | n/a | yes | | [environment](#input\_environment) | Which environment this is being instantiated in. | `string` | n/a | yes | -| [raw\_api\_gateway\_rest\_apis](#input\_raw\_api\_gateway\_rest\_apis) | Data structure
---------------
A list of dictionaries, where each dictionary has the following attributes:

REQUIRED
---------
- suffix : Suffix to use when creating the RESTAPI Gateway
- open\_api\_file\_path : Path to OpenAPI definition file
- description : A human-friendly description of the API
- tags : A dictionary of tags, required to tag Stage to ensure its protected by WAF.


OPTIONAL
---------
- template\_input : A dictionary of variable input for the OpenAPI definition file (leave blank if no template required)
- allowed\_lambdas : A list of strings, where each string is the function\_name of a lambda to allow access to.
- quota\_limit : Maximum number of requests that can be made in a given time period, defaults to 10.
- quota\_offset : Number of requests subtracted from the given limit in the initial time period, defaults to 0.
- quota\_period : Time period in which the limit applies. Valid values are "DAY", "WEEK" or "MONTH". Defaults to "DAY"
- burst\_limit : 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. Defaults to 5.
- rate\_limit : The API request steady-state rate limit, defaults to 10.
- api\_keys : List of strings, where each string is name of an API key to create for the API, defaults to empty list. |
list(
object({
suffix = string,
description = string,
tags = map(string),
open_api_file_path = string,
template_input = optional(map(string), {}),
quota_limit = optional(number, 10),
quota_offset = optional(number, 0),
quota_period = optional(string, "DAY"),
burst_limit = optional(number, 5),
rate_limit = optional(number, 10)
allowed_lambdas = optional(list(string), [])
api_keys = optional(list(string), [])
})
)
| n/a | yes | +| [raw\_api\_gateway\_rest\_apis](#input\_raw\_api\_gateway\_rest\_apis) | Data structure
---------------
A list of dictionaries, where each dictionary has the following attributes:

REQUIRED
---------
- suffix : Suffix to use when creating the RESTAPI Gateway
- open\_api\_file\_path : Path to OpenAPI definition file
- description : A human-friendly description of the API


OPTIONAL
---------
- template\_input : A dictionary of variable input for the OpenAPI definition file (leave blank if no template required)
- allowed\_lambdas : A list of strings, where each string is the function\_name of a lambda to allow access to.
- quota\_limit : Maximum number of requests that can be made in a given time period, defaults to 10.
- quota\_offset : Number of requests subtracted from the given limit in the initial time period, defaults to 0.
- quota\_period : Time period in which the limit applies. Valid values are "DAY", "WEEK" or "MONTH". Defaults to "DAY"
- burst\_limit : 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. Defaults to 5.
- rate\_limit : The API request steady-state rate limit, defaults to 10.
- api\_keys : List of strings, where each string is name of an API key to create for the API, defaults to empty list. |
list(
object({
suffix = string,
description = string,
open_api_file_path = string,
template_input = optional(map(string), {}),
quota_limit = optional(number, 10),
quota_offset = optional(number, 0),
quota_period = optional(string, "DAY"),
burst_limit = optional(number, 5),
rate_limit = optional(number, 10)
allowed_lambdas = optional(list(string), [])
api_keys = optional(list(string), [])
})
)
| n/a | yes | ## Outputs @@ -95,7 +95,6 @@ REQUIRED - suffix : Suffix to use when creating the RESTAPI Gateway - open_api_file_path : Path to OpenAPI definition file - description : A human-friendly description of the API -- tags : A dictionary of tags, required to tag Stage to ensure its protected by WAF. OPTIONAL diff --git a/api_gateway_stage.tf b/api_gateway_stage.tf index 0eb5b36..51eb574 100644 --- a/api_gateway_stage.tf +++ b/api_gateway_stage.tf @@ -4,5 +4,4 @@ resource "aws_api_gateway_stage" "stages" { deployment_id = aws_api_gateway_deployment.deployments[each.value["suffix"]].id rest_api_id = aws_api_gateway_rest_api.api_gateway[each.value["suffix"]].id stage_name = var.environment - tags = each.value["tags"] } \ No newline at end of file diff --git a/examples/api_gateway/files/petstore.yaml b/examples/api_gateway/files/petstore.yaml new file mode 100644 index 0000000..221b460 --- /dev/null +++ b/examples/api_gateway/files/petstore.yaml @@ -0,0 +1,806 @@ +openapi: 3.0.3 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! + You can now help us improve the API whether it's by making changes to the definition itself or to the code. + That way, with time, we can improve the API in general, and expose some of the new features in OAS3. + + _If you're looking for the Swagger 2.0/OAS 2.0 version of Petstore, then click [here](https://editor.swagger.io/?url=https://petstore.swagger.io/v2/swagger.yaml). Alternatively, you can load via the `Edit > Load Petstore OAS 2.0` menu option!_ + + Some useful links: + - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.11 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: + - url: https://petstore3.swagger.io/api/v3 +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io + - name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: http://swagger.io + - name: user + description: Operations about user +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '422': + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid input + '422': + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: false + explode: true + schema: + type: string + default: available + enum: + - available + - pending + - sold + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: false + explode: true + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + '400': + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet + description: delete a pet + operationId: deletePet + parameters: + - name: api_key + in: header + description: '' + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - write:pets + - read:pets + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: Place a new order in the store + operationId: placeOrder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Order' + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid input + '422': + description: Validation exception + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID + description: For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + default: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: Creates list of users with given input array + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: successful operation + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + '200': + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + parameters: [] + responses: + default: + description: successful operation + /user/{username}: + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Update user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + default: + description: successful operation + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + Customer: + type: object + properties: + id: + type: integer + format: int64 + example: 100000 + username: + type: string + example: fehguy + address: + type: array + xml: + name: addresses + wrapped: true + items: + $ref: '#/components/schemas/Address' + xml: + name: customer + Address: + type: object + properties: + street: + type: string + example: 437 Lytton + city: + type: string + example: Palo Alto + state: + type: string + example: CA + zip: + type: string + example: '94301' + xml: + name: address + Category: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + User: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: '12345' + phone: + type: string + example: '12345' + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header \ No newline at end of file diff --git a/examples/api_gateway/locals.tf b/examples/api_gateway/locals.tf new file mode 100644 index 0000000..2118e17 --- /dev/null +++ b/examples/api_gateway/locals.tf @@ -0,0 +1,18 @@ +locals { + raw_restapi_gateways = [ + { + suffix = "demoapi" + open_api_file_path : "${path.module}/files/petstore.yaml", + description = "DemoAPI for example purposes, utilising a remote OpenAPI definition file." + allowed_lambdas = [ + "auth-lambda", + "backend-lambda" + ] + api_keys = [ + "user1", + "user2", + "user3" + ] + } + ] +} \ No newline at end of file diff --git a/examples/api_gateway/main.tf b/examples/api_gateway/main.tf new file mode 100644 index 0000000..8b6f9e1 --- /dev/null +++ b/examples/api_gateway/main.tf @@ -0,0 +1,22 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.61.0" + } + } + required_version = "~> 1.5.0" +} + +provider "aws" { + region = "eu-west-2" +} + +module "restapi_gateway" { + source = "github.com/sudoblark/sudoblark.terraform.module.aws.api_gateway?ref=1.0.0" + + application_name = var.application_name + environment = var.environment + raw_api_gateway_rest_apis = local.raw_restapi_gateways + +} \ No newline at end of file diff --git a/examples/api_gateway/variables.tf b/examples/api_gateway/variables.tf new file mode 100644 index 0000000..9ccf4b8 --- /dev/null +++ b/examples/api_gateway/variables.tf @@ -0,0 +1,15 @@ +variable "environment" { + description = "Which environment this is being instantiated in." + type = string + validation { + condition = contains(["dev", "test", "prod"], var.environment) + error_message = "Must be either dev, test or prod" + } + default = "prod" +} + +variable "application_name" { + description = "Name of the application utilising the resource resource." + type = string + default = "demo-app" +} \ No newline at end of file diff --git a/rest_api.tf b/rest_api.tf index aec92d2..9b796a2 100644 --- a/rest_api.tf +++ b/rest_api.tf @@ -13,7 +13,6 @@ resource "aws_api_gateway_rest_api" "api_gateway" { name = format(lower("aws-${var.environment}-${var.application_name}-%s"), each.value["suffix"]) description = format("%s- managed by Terraform", each.value["description"]) body = each.value["open_api_definition"] - tags = each.value["tags"] lifecycle { create_before_destroy = true diff --git a/variables.tf b/variables.tf index b4a8950..76e5807 100644 --- a/variables.tf +++ b/variables.tf @@ -24,7 +24,6 @@ REQUIRED - suffix : Suffix to use when creating the RESTAPI Gateway - open_api_file_path : Path to OpenAPI definition file - description : A human-friendly description of the API -- tags : A dictionary of tags, required to tag Stage to ensure its protected by WAF. OPTIONAL @@ -43,7 +42,6 @@ EOF object({ suffix = string, description = string, - tags = map(string), open_api_file_path = string, template_input = optional(map(string), {}), quota_limit = optional(number, 10),