diff --git a/README.md b/README.md index d0c344c..a429ba3 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,22 @@ module "cis_alarms" { AWS CloudTrail normally publishes logs into AWS CloudWatch Logs. This module creates log metric filters together with metric alarms according to [CIS AWS Foundations Benchmark v1.4.0 (05-28-2021)](https://www.cisecurity.org/benchmark/amazon_web_services/). Read more about [CIS AWS Foundations Controls](https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html). +### Query Definition + +```hcl +module "query_definition" { + source = "terraform-aws-modules/cloudwatch/aws//modules/query-definition" + version = "~> 4.0" + + name = "my-query-definition" + log_group_names = ["my-log-group-name"] + query_string = < diff --git a/examples/complete-log-metric-filter-and-alarm/README.md b/examples/complete-log-metric-filter-and-alarm/README.md index fb03293..15531f3 100644 --- a/examples/complete-log-metric-filter-and-alarm/README.md +++ b/examples/complete-log-metric-filter-and-alarm/README.md @@ -20,7 +20,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.0 | +| [aws](#requirement\_aws) | >= 4.40 | ## Providers diff --git a/examples/complete-log-metric-filter-and-alarm/versions.tf b/examples/complete-log-metric-filter-and-alarm/versions.tf index d8dd1a4..fa875db 100644 --- a/examples/complete-log-metric-filter-and-alarm/versions.tf +++ b/examples/complete-log-metric-filter-and-alarm/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.0" + version = ">= 4.40" } } } diff --git a/examples/log-group-with-log-stream/README.md b/examples/log-group-with-log-stream/README.md index 0b323ac..b0c0eb4 100644 --- a/examples/log-group-with-log-stream/README.md +++ b/examples/log-group-with-log-stream/README.md @@ -20,7 +20,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.0 | +| [aws](#requirement\_aws) | >= 4.40 | ## Providers diff --git a/examples/log-group-with-log-stream/versions.tf b/examples/log-group-with-log-stream/versions.tf index d8dd1a4..fa875db 100644 --- a/examples/log-group-with-log-stream/versions.tf +++ b/examples/log-group-with-log-stream/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.0" + version = ">= 4.40" } } } diff --git a/examples/query-definition/README.md b/examples/query-definition/README.md new file mode 100644 index 0000000..c394339 --- /dev/null +++ b/examples/query-definition/README.md @@ -0,0 +1,51 @@ +# Cloudwatch query definition example + +Configuration in this directory creates a Cloudwatch query definition for a specific Cloudwatch log group. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [log\_group](#module\_log\_group) | ../../modules/log-group | n/a | +| [query\_definition](#module\_query\_definition) | ../../modules/query-definition | n/a | + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | ARN of Cloudwatch log group | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of Cloudwatch log group | +| [cloudwatch\_query\_definition\_id](#output\_cloudwatch\_query\_definition\_id) | The query definition id | + diff --git a/examples/query-definition/main.tf b/examples/query-definition/main.tf new file mode 100644 index 0000000..5922ae9 --- /dev/null +++ b/examples/query-definition/main.tf @@ -0,0 +1,24 @@ +provider "aws" { + region = "eu-west-1" +} + +module "log_group" { + source = "../../modules/log-group" + + name_prefix = "my-log-group-" + retention_in_days = 7 +} + +module "query_definition" { + source = "../../modules/query-definition" + + name = "query-example" + log_group_names = [ + module.log_group.cloudwatch_log_group_name + ] + query_string = < [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.0 | +| [aws](#requirement\_aws) | >= 4.40 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.0 | +| [aws](#provider\_aws) | >= 4.40 | ## Modules @@ -33,6 +33,7 @@ No modules. | [name](#input\_name) | A name for the log group | `string` | `null` | no | | [name\_prefix](#input\_name\_prefix) | A name prefix for the log group | `string` | `null` | no | | [retention\_in\_days](#input\_retention\_in\_days) | Specifies the number of days you want to retain log events in the specified log group. Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653. | `number` | `null` | no | +| [skip\_destroy](#input\_skip\_destroy) | Set to true if you do not wish the log group (and any logs it may contain) to be deleted at destroy time, and instead just remove the log group from the Terraform state | `bool` | `null` | no | | [tags](#input\_tags) | A map of tags to add to Cloudwatch log group | `map(string)` | `{}` | no | ## Outputs diff --git a/modules/log-group/main.tf b/modules/log-group/main.tf index a776539..36a0fc9 100644 --- a/modules/log-group/main.tf +++ b/modules/log-group/main.tf @@ -5,6 +5,7 @@ resource "aws_cloudwatch_log_group" "this" { name_prefix = var.name_prefix retention_in_days = var.retention_in_days kms_key_id = var.kms_key_id + skip_destroy = var.skip_destroy tags = var.tags } diff --git a/modules/log-group/variables.tf b/modules/log-group/variables.tf index 58608f2..9a63fb7 100644 --- a/modules/log-group/variables.tf +++ b/modules/log-group/variables.tf @@ -33,6 +33,12 @@ variable "kms_key_id" { default = null } +variable "skip_destroy" { + description = "Set to true if you do not wish the log group (and any logs it may contain) to be deleted at destroy time, and instead just remove the log group from the Terraform state" + type = bool + default = null +} + variable "tags" { description = "A map of tags to add to Cloudwatch log group" type = map(string) diff --git a/modules/log-group/versions.tf b/modules/log-group/versions.tf index d8dd1a4..fa875db 100644 --- a/modules/log-group/versions.tf +++ b/modules/log-group/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.0" + version = ">= 4.40" } } } diff --git a/modules/query-definition/README.md b/modules/query-definition/README.md new file mode 100644 index 0000000..aba4910 --- /dev/null +++ b/modules/query-definition/README.md @@ -0,0 +1,41 @@ +# query-definition + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_query_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_query_definition) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Whether to create the Cloudwatch query definition | `bool` | `true` | no | +| [log\_group\_names](#input\_log\_group\_names) | Specific log groups to use with the query. | `list(string)` | `null` | no | +| [name](#input\_name) | The name of the query. | `string` | n/a | yes | +| [query\_string](#input\_query\_string) | The The query to save. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_query\_definition\_id](#output\_cloudwatch\_query\_definition\_id) | The query definition id. | + diff --git a/modules/query-definition/main.tf b/modules/query-definition/main.tf new file mode 100644 index 0000000..19931a3 --- /dev/null +++ b/modules/query-definition/main.tf @@ -0,0 +1,7 @@ +resource "aws_cloudwatch_query_definition" "this" { + count = var.create ? 1 : 0 + + name = var.name + query_string = var.query_string + log_group_names = var.log_group_names +} diff --git a/modules/query-definition/outputs.tf b/modules/query-definition/outputs.tf new file mode 100644 index 0000000..13c3b70 --- /dev/null +++ b/modules/query-definition/outputs.tf @@ -0,0 +1,4 @@ +output "cloudwatch_query_definition_id" { + description = "The query definition id." + value = try(aws_cloudwatch_query_definition.this[0].query_definition_id, "") +} diff --git a/modules/query-definition/variables.tf b/modules/query-definition/variables.tf new file mode 100644 index 0000000..8efb5e2 --- /dev/null +++ b/modules/query-definition/variables.tf @@ -0,0 +1,21 @@ +variable "create" { + description = "Whether to create the Cloudwatch query definition" + type = bool + default = true +} + +variable "name" { + description = "The name of the query." + type = string +} + +variable "query_string" { + description = "The The query to save." + type = string +} + +variable "log_group_names" { + description = "Specific log groups to use with the query." + type = list(string) + default = null +} diff --git a/modules/query-definition/versions.tf b/modules/query-definition/versions.tf new file mode 100644 index 0000000..ddfcb0e --- /dev/null +++ b/modules/query-definition/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + } +} diff --git a/wrappers/log-group/main.tf b/wrappers/log-group/main.tf index 1751f98..7539583 100644 --- a/wrappers/log-group/main.tf +++ b/wrappers/log-group/main.tf @@ -8,5 +8,6 @@ module "wrapper" { name_prefix = try(each.value.name_prefix, var.defaults.name_prefix, null) retention_in_days = try(each.value.retention_in_days, var.defaults.retention_in_days, null) kms_key_id = try(each.value.kms_key_id, var.defaults.kms_key_id, null) + skip_destroy = try(each.value.skip_destroy, var.defaults.skip_destroy, null) tags = try(each.value.tags, var.defaults.tags, {}) } diff --git a/wrappers/log-metric-filter/main.tf b/wrappers/log-metric-filter/main.tf index d3392e8..3c2331b 100644 --- a/wrappers/log-metric-filter/main.tf +++ b/wrappers/log-metric-filter/main.tf @@ -12,4 +12,5 @@ module "wrapper" { metric_transformation_value = try(each.value.metric_transformation_value, var.defaults.metric_transformation_value, "1") metric_transformation_default_value = try(each.value.metric_transformation_default_value, var.defaults.metric_transformation_default_value, null) metric_transformation_unit = try(each.value.metric_transformation_unit, var.defaults.metric_transformation_unit, null) + metric_transformation_dimensions = try(each.value.metric_transformation_dimensions, var.defaults.metric_transformation_dimensions, {}) } diff --git a/wrappers/query-definition/README.md b/wrappers/query-definition/README.md new file mode 100644 index 0000000..2af5ccb --- /dev/null +++ b/wrappers/query-definition/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/query-definition` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/cloudwatch/aws//wrappers/query-definition" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-cloudwatch.git//wrappers/query-definition?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/cloudwatch/aws//wrappers/query-definition" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/query-definition/main.tf b/wrappers/query-definition/main.tf new file mode 100644 index 0000000..7dfc4ea --- /dev/null +++ b/wrappers/query-definition/main.tf @@ -0,0 +1,10 @@ +module "wrapper" { + source = "../../modules/query-definition" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + name = try(each.value.name, var.defaults.name) + query_string = try(each.value.query_string, var.defaults.query_string) + log_group_names = try(each.value.log_group_names, var.defaults.log_group_names, null) +} diff --git a/wrappers/query-definition/outputs.tf b/wrappers/query-definition/outputs.tf new file mode 100644 index 0000000..5da7c09 --- /dev/null +++ b/wrappers/query-definition/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/query-definition/variables.tf b/wrappers/query-definition/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/wrappers/query-definition/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/query-definition/versions.tf b/wrappers/query-definition/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/wrappers/query-definition/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +}