Skip to content

Commit

Permalink
Add AWS Managed Grafana module
Browse files Browse the repository at this point in the history
Now that AWS Managed Grafana has an API for managing workspaces and the
Terraform AWS provider supports it, we can initialize our Grafana
workspaces using Terraform rather than doing it manually through the AWS
console.

This introduces a new module to create the workspace. It also updates
the API token, telemetry, and data source modules to take advantage of
the new workspace module.
  • Loading branch information
jferris committed May 23, 2023
1 parent eae257a commit 10bd932
Show file tree
Hide file tree
Showing 31 changed files with 908 additions and 68 deletions.
56 changes: 56 additions & 0 deletions aws/grafana-api-token/README.md
@@ -0,0 +1,56 @@
# Grafana API token

Fetches the URL and authentication necessary to initialize the Grafana provider
for an AWS Managed Grafana workspace.

Example:

```terraform
module "grafana_api_key" {
source = "github.com/thoughtbot/flightdeck//aws/grafana-api-token?ref=VERSION"
}
provider "grafana" {
url = module.grafana_api_key.url
auth = module.grafana_api_key.auth
}
```

<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.4 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 4.12 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~> 4.12 |

## Resources

| Name | Type |
|------|------|
| [aws_grafana_workspace.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/grafana_workspace) | data source |
| [aws_secretsmanager_secret_version.api_token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/secretsmanager_secret_version) | data source |
| [aws_ssm_parameter.grafana_api_token_secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |
| [aws_ssm_parameter.grafana_workspace_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_grafana_workspace_name"></a> [grafana\_workspace\_name](#input\_grafana\_workspace\_name) | Name of this Grafana workspace | `string` | `"Grafana"` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_arn"></a> [arn](#output\_arn) | ARN for this Grafana workspace |
| <a name="output_auth"></a> [auth](#output\_auth) | Auth header for connecting to this Grafana workspace |
| <a name="output_url"></a> [url](#output\_url) | URL for accessing this Grafana workspace |
<!-- END_TF_DOCS -->
39 changes: 39 additions & 0 deletions aws/grafana-api-token/main.tf
@@ -0,0 +1,39 @@
data "aws_ssm_parameter" "grafana_workspace_id" {
name = join(
"/",
concat([
"",
"flightdeck",
"grafana",
var.grafana_workspace_name,
"workspace_id"
])
)
}

data "aws_ssm_parameter" "grafana_api_token_secret" {
name = join(
"/",
concat([
"",
"flightdeck",
"grafana",
var.grafana_workspace_name,
"secret_arn"
])
)
}

data "aws_grafana_workspace" "this" {
workspace_id = data.aws_ssm_parameter.grafana_workspace_id.value
}

data "aws_secretsmanager_secret_version" "api_token" {
secret_id = data.aws_ssm_parameter.grafana_api_token_secret.value
}

locals {
auth = jsondecode(
data.aws_secretsmanager_secret_version.api_token.secret_string
).GRAFANA_API_KEY
}
File renamed without changes.
15 changes: 15 additions & 0 deletions aws/grafana-api-token/outputs.tf
@@ -0,0 +1,15 @@
output "arn" {
description = "ARN for this Grafana workspace"
value = data.aws_grafana_workspace.this.arn
}

output "auth" {
description = "Auth header for connecting to this Grafana workspace"
value = local.auth
}

output "url" {
description = "URL for accessing this Grafana workspace"
value = "https://${data.aws_grafana_workspace.this.endpoint}"
}

5 changes: 5 additions & 0 deletions aws/grafana-api-token/variables.tf
@@ -0,0 +1,5 @@
variable "grafana_workspace_name" {
description = "Name of this Grafana workspace"
type = string
default = "Grafana"
}
10 changes: 10 additions & 0 deletions aws/grafana-api-token/versions.tf
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.4"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.12"
}
}
}
29 changes: 25 additions & 4 deletions aws/grafana-data-sources/README.md
@@ -1,3 +1,17 @@
# AWS Managed Grafana Data Sources

Adds data sources from a workload account to an AWS Managed Grafana workspace.

Example:

```terraform
module "grafana_production" {
source = "github.com/thoughtbot/flightdeck//aws/grafana-data-sources?ref=VERSION"
name = "production"
}
```

<!-- BEGIN_TF_DOCS -->
## Requirements

Expand All @@ -18,15 +32,22 @@

| Name | Type |
|------|------|
| [grafana_data_source.alertmanager](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/data_source) | resource |
| [grafana_data_source.cloudwatch](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/data_source) | resource |
| [grafana_data_source.prometheus](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/data_source) | resource |
| [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_role.grafana](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_role) | data source |
| [aws_prometheus_workspace.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/prometheus_workspace) | data source |
| [aws_region.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
| [aws_s3_bucket_object.prometheus](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/s3_bucket_object) | data source |
| [aws_ssm_parameter.prometheus_workspace_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_prometheus_workspace_names"></a> [prometheus\_workspace\_names](#input\_prometheus\_workspace\_names) | Names of the Prometheus workspaces | `list(string)` | `[]` | no |
<!-- END_TF_DOCS -->
| <a name="input_alertmanager_data_source_name"></a> [alertmanager\_data\_source\_name](#input\_alertmanager\_data\_source\_name) | Name of the AlertManager data source | `string` | `null` | no |
| <a name="input_cloudwatch_data_source_name"></a> [cloudwatch\_data\_source\_name](#input\_cloudwatch\_data\_source\_name) | Name of the CloudWatch data source | `string` | `null` | no |
| <a name="input_grafana_role_name"></a> [grafana\_role\_name](#input\_grafana\_role\_name) | Name of the Grafana role | `string` | `"grafana"` | no |
| <a name="input_name"></a> [name](#input\_name) | Namespace for data sources in this account | `string` | n/a | yes |
| <a name="input_prometheus_data_source_name"></a> [prometheus\_data\_source\_name](#input\_prometheus\_data\_source\_name) | Name of the Prometheus Grafana data source | `string` | `null` | no |
| <a name="input_prometheus_workspace_name"></a> [prometheus\_workspace\_name](#input\_prometheus\_workspace\_name) | Name of the Prometheus workspace for Grafana data source | `string` | `null` | no |
<!-- END_TF_DOCS -->
89 changes: 57 additions & 32 deletions aws/grafana-data-sources/main.tf
@@ -1,51 +1,76 @@
resource "grafana_data_source" "prometheus" {
for_each = local.prometheus_workspaces

is_default = length(var.prometheus_workspace_names) == 1
name = each.key
is_default = false
name = local.prometheus_data_source
type = "prometheus"
url = each.value.url

json_data {
http_method = "POST"
sigv4_auth = true
sigv4_auth_type = "default"
sigv4_region = each.value.region
}
url = data.aws_prometheus_workspace.this.prometheus_endpoint

json_data_encoded = jsonencode({
alertmanagerUid = grafana_data_source.alertmanager.uid
httpMethod = "POST"
sigV4Auth = true
sigV4AuthType = "default"
sigV4Region = data.aws_region.this.name
sigV4AssumeRoleArn = data.aws_iam_role.grafana.arn
})
}

resource "grafana_data_source" "cloudwatch" {
type = "cloudwatch"
name = "cloudwatch"
name = local.cloudwatch_data_source

json_data {
default_region = data.aws_region.this.name
auth_type = "default"
}
json_data_encoded = jsonencode({
defaultRegion = data.aws_region.this.name
authType = "default"
assumeRoleArn = data.aws_iam_role.grafana.arn
})
}

data "aws_s3_bucket_object" "prometheus" {
for_each = toset(var.prometheus_workspace_names)
resource "grafana_data_source" "alertmanager" {
type = "alertmanager"
name = local.alertmanager_data_source
url = "${data.aws_prometheus_workspace.this.prometheus_endpoint}alertmanager"

json_data_encoded = jsonencode({
implementation = "prometheus"
sigV4Auth = true
sigV4AuthType = "default"
sigV4Region = data.aws_region.this.name
sigV4AssumeRoleArn = data.aws_iam_role.grafana.arn
})
}

bucket = join("-", concat([
"prometheus",
each.value,
data.aws_caller_identity.this.account_id
]))
data "aws_iam_role" "grafana" {
name = var.grafana_role_name
}

key = "ingestion.json"
data "aws_prometheus_workspace" "this" {
workspace_id = data.aws_ssm_parameter.prometheus_workspace_id.value
}

data "aws_caller_identity" "this" {}
data "aws_ssm_parameter" "prometheus_workspace_id" {
name = join("/", concat(["", "flightdeck", "prometheus", local.prometheus_workspace_name, "workspace_id"]))
}

data "aws_region" "this" {}

locals {
prometheus_workspaces = zipmap(
var.prometheus_workspace_names,
[
for name in var.prometheus_workspace_names :
jsondecode(data.aws_s3_bucket_object.prometheus[name].body)
]
alertmanager_data_source = coalesce(
var.alertmanager_data_source_name,
"AlertManager (${var.name})"
)

cloudwatch_data_source = coalesce(
var.cloudwatch_data_source_name,
"Cloudwatch (${var.name})"
)

prometheus_data_source = coalesce(
var.prometheus_data_source_name,
"Prometheus (${var.name})"
)

prometheus_workspace_name = coalesce(
var.prometheus_workspace_name,
"flightdeck-${var.name}"
)
}
37 changes: 33 additions & 4 deletions aws/grafana-data-sources/variables.tf
@@ -1,5 +1,34 @@
variable "prometheus_workspace_names" {
description = "Names of the Prometheus workspaces"
type = list(string)
default = []
variable "alertmanager_data_source_name" {
description = "Name of the AlertManager data source"
type = string
default = null
}

variable "cloudwatch_data_source_name" {
description = "Name of the CloudWatch data source"
type = string
default = null
}

variable "name" {
description = "Namespace for data sources in this account"
type = string
}

variable "prometheus_data_source_name" {
description = "Name of the Prometheus Grafana data source"
type = string
default = null
}

variable "prometheus_workspace_name" {
description = "Name of the Prometheus workspace for Grafana data source"
type = string
default = null
}

variable "grafana_role_name" {
description = "Name of the Grafana role"
type = string
default = "grafana"
}

0 comments on commit 10bd932

Please sign in to comment.