diff --git a/.github/labeler.yaml b/.github/labeler.yaml index 7af6085..356c6fc 100644 --- a/.github/labeler.yaml +++ b/.github/labeler.yaml @@ -8,6 +8,9 @@ ":floppy_disk: dx-private-virtual-interface": - modules/dx-private-virtual-interface/**/* +":floppy_disk: lattice-service-listener": +- modules/lattice-service-listener/**/* + ":floppy_disk: lattice-service-network": - modules/lattice-service-network/**/* diff --git a/.github/labels.yaml b/.github/labels.yaml index 6b1e07f..da1f05b 100644 --- a/.github/labels.yaml +++ b/.github/labels.yaml @@ -49,6 +49,9 @@ - color: "fbca04" description: "This issue or pull request is related to dx-private-virtual-interface module." name: ":floppy_disk: dx-private-virtual-interface" +- color: "fbca04" + description: "This issue or pull request is related to lattice-service-listener module." + name: ":floppy_disk: lattice-service-listener" - color: "fbca04" description: "This issue or pull request is related to lattice-service-network module." name: ":floppy_disk: lattice-service-network" diff --git a/README.md b/README.md index 3d05537..4578b38 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Terraform module which creates VPC Connectivity related resources (VPC Peering, - [dx-connection](./modules/dx-connection) - [dx-gateway](./modules/dx-gateway) - [dx-private-virtual-interface](./modules/dx-private-virtual-interface) +- [lattice-service-listener](./modules/lattice-service-listener) - [lattice-service-network](./modules/lattice-service-network) - [lattice-service](./modules/lattice-service) - [reachability-analyzer-path](./modules/reachability-analyzer-path) @@ -32,8 +33,12 @@ Terraform Modules from [this package](https://github.com/tedilabs/terraform-aws- - Interface Endpoint - **AWS VPC Lattice** - Service Network + - Service Listener - Service - Target Group + - ALB Target Group + - IP Target Group + - Instance Target Group - **AWS DX (Direct Connect)** - Connection - Gateway diff --git a/modules/lattice-service-listener/README.md b/modules/lattice-service-listener/README.md new file mode 100644 index 0000000..aa6e217 --- /dev/null +++ b/modules/lattice-service-listener/README.md @@ -0,0 +1,66 @@ +# lattice-service-listener + +This module creates following resources. + +- `aws_vpclattice_listener` +- `aws_vpclattice_listener_rule` (optional) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5 | +| [aws](#requirement\_aws) | >= 5.12 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 5.17.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [resource\_group](#module\_resource\_group) | tedilabs/misc/aws//modules/resource-group | ~> 0.10.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_vpclattice_listener.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpclattice_listener) | resource | +| [aws_vpclattice_listener_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpclattice_listener_rule) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [default\_action\_type](#input\_default\_action\_type) | (Required) The type of default routing action. Default action apply to traffic that does not meet the conditions of rules on your listener. Rules can be configured after the listener is created. Valid values are `FORWARD`, `FIXED_RESPONSE`. | `string` | n/a | yes | +| [name](#input\_name) | (Required) The name of the service listener. The name must be unique within the service. The valid characters are a-z, 0-9, and hyphens (-). You can't use a hyphen as the first or last character, or immediately after another hyphen. | `string` | n/a | yes | +| [protocol](#input\_protocol) | (Required) The protocol for the service listener. Valid values are `HTTP` and `HTTPS`. | `string` | n/a | yes | +| [service](#input\_service) | (Required) The ID or ARN (Amazon Resource Name) of the VPC Lattice service. | `string` | n/a | yes | +| [default\_action\_parameters](#input\_default\_action\_parameters) | (Optional) The configuration for the parameters of the default routing action. `default_action_parameters` block as defined below.
(Optional) `status_code` - Custom HTTP status code to drop client requests and return a custom HTTP response. Valid values are `404`. Only supported if `default_action_type` is `FIXED_RESPONSE`.
(Optional) `destinations` - A list of one or more target groups to route traffic. Only supported if `default_action_type` is `FORWARD`. Each item of `destinations` block as defined below.
(Required) `target_group` - The ID or ARN of the target group to which to route traffic.
(Optional) `weight` - The weight to use routing traffic to `target_group`. how requests are distributed to the target group. Only required if you specify multiple target groups for a forward action. For example, if you specify two target groups, one with a weight of 10 and the other with a weight of 20, the target group with a weight of 20 receives twice as many requests as the other target group. Valid value is `0` to `999`. Defaults to `100`. |
object({
status_code = optional(number, 404)
destinations = optional(list(object({
target_group = string
weight = optional(number, 100)
})), [])
})
| `{}` | no | +| [module\_tags\_enabled](#input\_module\_tags\_enabled) | (Optional) Whether to create AWS Resource Tags for the module informations. | `bool` | `true` | no | +| [port](#input\_port) | (Optional) The number of port on which the listener of the service is listening. Valid values are from `1` to `65535`. If `port` is not specified and `protocol` is `HTTP`, the value will default to `80`. If `port` is not specified and `protocol` is `HTTPS`, the value will default to `443`. | `number` | `null` | no | +| [resource\_group\_description](#input\_resource\_group\_description) | (Optional) The description of Resource Group. | `string` | `"Managed by Terraform."` | no | +| [resource\_group\_enabled](#input\_resource\_group\_enabled) | (Optional) Whether to create Resource Group to find and group AWS resources which are created by this module. | `bool` | `true` | no | +| [resource\_group\_name](#input\_resource\_group\_name) | (Optional) The name of Resource Group. A Resource Group name can have a maximum of 127 characters, including letters, numbers, hyphens, dots, and underscores. The name cannot start with `AWS` or `aws`. | `string` | `""` | no | +| [rules](#input\_rules) | (Optional) The configuration for the parameters of the default routing action. `default_action_parameters` block as defined below.
(Optional) `status_code` - The status code of HTTP response. Valid values are `2XX`, `4XX`, or `5XX`. Defaults to `503`. Only supported if `default_action_type` is `FIXED_RESPONSE`.
(Required) `targets` - A list of target configurations to route traffic. To route to a single target group, use `default_action_type` as `FORWARD`. Only supported if `default_action_type` is `WEIGHTED_FORWARD`. Each item of `targets` block as defined below.
(Required) `target_group` - The ARN of the target group to which to route traffic.
(Optional) `weight` - The weight to use routing traffic to `target_group`. Valid value is `0` to `999`. Defaults to `1`. |
list(object({
priority = number
name = optional(string)

action_type = string
action_parameters = optional(object({
status_code = optional(number, 404)
destinations = optional(list(object({
target_group = string
weight = optional(number, 100)
})), [])
}), {})
}))
| `[]` | no | +| [tags](#input\_tags) | (Optional) A map of tags to add to all resources. | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN of the service listener. | +| [created\_at](#output\_created\_at) | Date and time that the listener was created, specified in ISO-8601 format. | +| [default\_action](#output\_default\_action) | The configuration for default routing action of the service listener.
`type` - The type of default routing action.
`parameters` - The configuration for the parameters of the default routing action. `default_action_parameters` block as defined below. | +| [id](#output\_id) | The ID of the service listener. | +| [name](#output\_name) | The name of the service listener. | +| [port](#output\_port) | The number of port on which the listener of the service is listening. | +| [protocol](#output\_protocol) | The protocol for the service listener. | +| [rules](#output\_rules) | The configuration for default routing action of the service listener.
`type` - The type of default routing action.
`parameters` - The configuration for the parameters of the default routing action. `default_action_parameters` block as defined below. | +| [service](#output\_service) | The associated VPC Lattice service. | +| [updated\_at](#output\_updated\_at) | Date and time that the listener was last updated, specified in ISO-8601 format. | + diff --git a/modules/lattice-service-listener/main.tf b/modules/lattice-service-listener/main.tf new file mode 100644 index 0000000..41c18fd --- /dev/null +++ b/modules/lattice-service-listener/main.tf @@ -0,0 +1,154 @@ +locals { + metadata = { + package = "terraform-aws-vpc-connectivity" + version = trimspace(file("${path.module}/../../VERSION")) + module = basename(path.module) + name = var.name + } + module_tags = var.module_tags_enabled ? { + "module.terraform.io/package" = local.metadata.package + "module.terraform.io/version" = local.metadata.version + "module.terraform.io/name" = local.metadata.module + "module.terraform.io/full-name" = "${local.metadata.package}/${local.metadata.module}" + "module.terraform.io/instance" = local.metadata.name + } : {} +} + +locals { + is_arn = startswith(var.service, "arn:aws:vpc-lattice:") + default_ports = { + "HTTP" = 80 + "HTTPS" = 443 + } +} + + +################################################### +# Service Listener for VPC Lattice +################################################### + +resource "aws_vpclattice_listener" "this" { + service_arn = local.is_arn ? var.service : null + service_identifier = local.is_arn ? null : var.service + + name = var.name + + port = coalesce(var.port, local.default_ports[var.protocol]) + protocol = var.protocol + + default_action { + dynamic "fixed_response" { + for_each = (var.default_action_type == "FIXED_RESPONSE" + ? [var.default_action_parameters] + : [] + ) + + content { + status_code = fixed_response.value.status_code + } + } + + dynamic "forward" { + for_each = (var.default_action_type == "FORWARD" + ? [var.default_action_parameters] + : [] + ) + + content { + dynamic "target_groups" { + for_each = forward.value.destinations + + content { + target_group_identifier = target_groups.value.target_group + weight = target_groups.value.weight + } + } + } + } + } + + tags = merge( + { + "Name" = local.metadata.name + }, + local.module_tags, + var.tags, + ) +} + + +################################################### +# Service Listener Rules for VPC Lattice +################################################### + +resource "aws_vpclattice_listener_rule" "this" { + for_each = { + for rule in var.rules : + rule.priority => rule + } + + priority = each.key + name = coalesce(each.value.name, "${var.name}-${each.key}") + listener_identifier = aws_vpclattice_listener.this.listener_id + service_identifier = aws_vpclattice_listener.this.service_identifier + + match { + http_match { + + header_matches { + name = "example-header" + case_sensitive = false + + match { + exact = "example-contains" + } + } + + path_match { + case_sensitive = true + match { + prefix = "/example-path" + } + } + } + } + + action { + dynamic "fixed_response" { + for_each = (each.value.action_type == "FIXED_RESPONSE" + ? [each.value.action_parameters] + : [] + ) + + content { + status_code = fixed_response.value.status_code + } + } + + dynamic "forward" { + for_each = (each.value.action_type == "FORWARD" + ? [each.value.action_parameters] + : [] + ) + + content { + dynamic "target_groups" { + for_each = forward.value.destinations + + content { + target_group_identifier = target_groups.value.target_group + weight = target_groups.value.weight + } + } + } + } + } + + tags = merge( + { + "Name" = "${var.name}-${each.key}" + }, + local.module_tags, + var.tags, + ) +} diff --git a/modules/lattice-service-listener/outputs.tf b/modules/lattice-service-listener/outputs.tf new file mode 100644 index 0000000..50860e6 --- /dev/null +++ b/modules/lattice-service-listener/outputs.tf @@ -0,0 +1,94 @@ +output "service" { + description = "The associated VPC Lattice service." + value = { + id = aws_vpclattice_listener.this.service_identifier + arn = aws_vpclattice_listener.this.service_arn + } +} + +output "id" { + description = "The ID of the service listener." + value = aws_vpclattice_listener.this.listener_id +} + +output "arn" { + description = "The ARN of the service listener." + value = aws_vpclattice_listener.this.arn +} + +output "name" { + description = "The name of the service listener." + value = aws_vpclattice_listener.this.name +} + +output "port" { + description = "The number of port on which the listener of the service is listening." + value = aws_vpclattice_listener.this.port +} + +output "protocol" { + description = "The protocol for the service listener." + value = aws_vpclattice_listener.this.protocol +} + +output "default_action" { + description = < { + id = aws_vpclattice_listener_rule.this[rule.priority].rule_id + arn = aws_vpclattice_listener_rule.this[rule.priority].arn + priority = rule.priority + name = aws_vpclattice_listener_rule.this[rule.priority].name + + action = { + type = rule.action_type + parameters = rule.action_parameters + parameters = { + "FIXED_RESPONSE" = one(aws_vpclattice_listener_rule.this[rule.priority].action[0].fixed_response[*]) + "FORWARD" = { + destinations = one(aws_vpclattice_listener_rule.this[rule.priority].action[0].forward[*].target_groups) + } + }[rule.action_type] + } + z = { + for k, v in aws_vpclattice_listener_rule.this[rule.priority] : + k => v + if !contains(["name", "priority", "rule_id", "id", "arn", "listener_identifier", "service_identifier", "tags", "tags_all", "timeouts", "action"], k) + } + } + } +} + +output "created_at" { + description = "Date and time that the listener was created, specified in ISO-8601 format." + value = aws_vpclattice_listener.this.created_at +} + +output "updated_at" { + description = "Date and time that the listener was last updated, specified in ISO-8601 format." + value = aws_vpclattice_listener.this.last_updated_at +} diff --git a/modules/lattice-service-listener/resource-group.tf b/modules/lattice-service-listener/resource-group.tf new file mode 100644 index 0000000..7487ba0 --- /dev/null +++ b/modules/lattice-service-listener/resource-group.tf @@ -0,0 +1,31 @@ +locals { + resource_group_name = (var.resource_group_name != "" + ? var.resource_group_name + : join(".", [ + local.metadata.package, + local.metadata.module, + replace(local.metadata.name, "/[^a-zA-Z0-9_\\.-]/", "-"), + ]) + ) +} + + +module "resource_group" { + source = "tedilabs/misc/aws//modules/resource-group" + version = "~> 0.10.0" + + count = (var.resource_group_enabled && var.module_tags_enabled) ? 1 : 0 + + name = local.resource_group_name + description = var.resource_group_description + + query = { + resource_tags = local.module_tags + } + + module_tags_enabled = false + tags = merge( + local.module_tags, + var.tags, + ) +} diff --git a/modules/lattice-service-listener/variables.tf b/modules/lattice-service-listener/variables.tf new file mode 100644 index 0000000..264392f --- /dev/null +++ b/modules/lattice-service-listener/variables.tf @@ -0,0 +1,152 @@ +variable "service" { + description = "(Required) The ID or ARN (Amazon Resource Name) of the VPC Lattice service." + type = string + nullable = false + + validation { + condition = anytrue([ + startswith(var.service, "arn:aws:vpc-lattice:"), + startswith(var.service, "sn-"), + ]) + error_message = "Valid value for `service` must be the ID or ARN (Amazon Resource Name) of the VPC Lattice service." + } +} + +variable "name" { + description = "(Required) The name of the service listener. The name must be unique within the service. The valid characters are a-z, 0-9, and hyphens (-). You can't use a hyphen as the first or last character, or immediately after another hyphen." + type = string + nullable = false +} + +variable "port" { + description = "(Optional) The number of port on which the listener of the service is listening. Valid values are from `1` to `65535`. If `port` is not specified and `protocol` is `HTTP`, the value will default to `80`. If `port` is not specified and `protocol` is `HTTPS`, the value will default to `443`." + type = number + default = null + nullable = true +} + +variable "protocol" { + description = "(Required) The protocol for the service listener. Valid values are `HTTP` and `HTTPS`." + type = string + nullable = false + + validation { + condition = contains(["HTTP", "HTTPS"], var.protocol) + error_message = "Valid values are `HTTP` and `HTTPS`." + } +} + +variable "default_action_type" { + description = "(Required) The type of default routing action. Default action apply to traffic that does not meet the conditions of rules on your listener. Rules can be configured after the listener is created. Valid values are `FORWARD`, `FIXED_RESPONSE`." + type = string + nullable = false + + validation { + condition = contains(["FORWARD", "FIXED_RESPONSE"], var.default_action_type) + error_message = "Valid values are `FORWARD` and `FIXED_RESPONSE`." + } +} + +variable "default_action_parameters" { + description = <= 200, + tonumber(try(var.default_action_parameters.status_code, 404)) <= 599, + ]) + error_message = "Value of `status_code` should be 200 - 599." + } + validation { + condition = alltrue([ + for destination in try(var.default_action_parameters.destinations, []) : + alltrue([ + try(destination.weight, 1) >= 0, + try(destination.weight, 1) <= 999, + ]) + ]) + error_message = "Value of `destinations[].weight` should be between 0 and 999." + } +} + +# TODO: Update Docs +# TODO: Support Match +variable "rules" { + description = <