AMG(Managed Grafana)のworkspace API KeyのTerraformリソースがリリースされたのでIaC化が捗ると思ったけどそんなことなかった話

2022.09.25

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

以前、AMG(Amazon Managed Grafana)がGrafana APIトークン作成のAPIをサポートしてIaC化しやすくなった、というエントリを書きました。

このときはまだAPI Keyに対応するTerraformリソースがなかったので、External Data Source providerを使ってAWS CLI経由でトークンを作成し、それをTerraformに取り込むという、ややトリッキーな、少しわかりにくい実装を行ないました。

しかしその後2022/08/27にリリースされたAWS Provider v4.28.0にて、aws_grafana_workspace_api_keyリソースが追加されました?

これを使えばより簡潔にわかりやすくIaC化できるのでは、と思って挑戦してみました。が、結果うまく行かないことがわかりましたので、レポートします。

テストコード

以下のリソースをプロビジョニングするコードです。

  • AMG workspace
  • AMG workspace API Key
  • 上記workspace上にプロビジョニングする、AMP(Managed Prometheus) workspaceをソースにしたGrafana Data Source
  • ※ AMG workspaceが使うロールと、AMP workspaceも必要です。 が、今回は既に作成済みのものがあったのでそちらを利用することにし、これらの情報はvariablesにすることにしました。

main.tf

terraform {
  required_version = "= 1.2.1"

  required_providers {
    aws = {
      version = "4.32.0"
    }
    grafana = {
      source  = "grafana/grafana"
      version = "1.16.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

provider "grafana" {
  url  = "https://${aws_grafana_workspace.main.endpoint}/"
  auth = aws_grafana_workspace_api_key.main.key
}

variables.tf

variable "grafana_workspace_role_arn" {
  type = string
}

variable "amp_workspace_url" {
  type = string
}

managed_grafana.tf

resource "aws_grafana_workspace" "main" {
  account_access_type      = "CURRENT_ACCOUNT"
  authentication_providers = ["AWS_SSO"]
  permission_type          = "SERVICE_MANAGED"

  name         = "kazue-test"
  description  = "test"
  role_arn     = var.grafana_workspace_role_arn
  data_sources = ["PROMETHEUS", "CLOUDWATCH"]
}

resource "aws_grafana_workspace_api_key" "main" {
  key_name        = "test-key"
  key_role        = "ADMIN"
  seconds_to_live = 180
  workspace_id    = aws_grafana_workspace.main.id
}

grafana_datasource.tf

resource "grafana_data_source" "prometheus" {
  type       = "prometheus"
  name       = "prometheus_datasource"
  is_default = true
  url        = var.amp_workspace_url
  json_data {
    http_method     = "POST"
    sigv4_auth      = true
    sigv4_auth_type = "workspace-iam-role"
    sigv4_region    = data.aws_region.current.name
  }
}

data "aws_region" "current" {}

初回 apply → OK

% terraform apply
data.aws_region.current: Reading...
data.aws_region.current: Read complete after 0s [id=ap-northeast-1]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # aws_grafana_workspace.main will be created
  + resource "aws_grafana_workspace" "main" {
      + account_access_type       = "CURRENT_ACCOUNT"
      + arn                       = (known after apply)
      + authentication_providers  = [
          + "AWS_SSO",
        ]
      + data_sources              = [
          + "PROMETHEUS",
          + "CLOUDWATCH",
        ]
      + description               = "test"
      + endpoint                  = (known after apply)
      + grafana_version           = (known after apply)
      + id                        = (known after apply)
      + name                      = "kazue-test"
      + permission_type           = "SERVICE_MANAGED"
      + role_arn                  = "arn:aws:iam::123456789012:role/service-role/AmazonGrafanaServiceRole-kijC1Ssfn"
      + saml_configuration_status = (known after apply)
      + tags_all                  = (known after apply)
    }

  # aws_grafana_workspace_api_key.main will be created
  + resource "aws_grafana_workspace_api_key" "main" {
      + id              = (known after apply)
      + key             = (known after apply)
      + key_name        = "test-key"
      + key_role        = "ADMIN"
      + seconds_to_live = 180
      + workspace_id    = (known after apply)
    }

  # grafana_data_source.prometheus will be created
  + resource "grafana_data_source" "prometheus" {
      + access_mode        = "proxy"
      + basic_auth_enabled = false
      + id                 = (known after apply)
      + is_default         = true
      + name               = "prometheus_datasource"
      + type               = "prometheus"
      + url                = "https://aps-workspaces.ap-northeast-1.amazonaws.com/workspaces/ws-ecc816a4-f6da-488b-85b4-aaaaaaaaaaaa/"

      + json_data {
          + http_method     = "POST"
          + sigv4_auth      = true
          + sigv4_auth_type = "workspace-iam-role"
          + sigv4_region    = "ap-northeast-1"
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_grafana_workspace.main: Creating...
aws_grafana_workspace.main: Still creating... [10s elapsed]
aws_grafana_workspace.main: Still creating... [20s elapsed]
aws_grafana_workspace.main: Still creating... [30s elapsed]
aws_grafana_workspace.main: Still creating... [40s elapsed]
aws_grafana_workspace.main: Still creating... [50s elapsed]
aws_grafana_workspace.main: Creation complete after 56s [id=g-f2520a71cc]
aws_grafana_workspace_api_key.main: Creating...
aws_grafana_workspace_api_key.main: Creation complete after 0s [id=g-f2520a71cc/test-key]
grafana_data_source.prometheus: Creating...
grafana_data_source.prometheus: Creation complete after 1s [id=1]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

↑ 依存関係が理由で、以下の順で処理が行われていますね。

  1. AMG workspace作成
  2. AMG workspace API Key作成
  3. (Grafana providerの設定)
  4. Grafana Data Source作成

2回目以降のapply → OK

% terraform apply
data.aws_region.current: Reading...
aws_grafana_workspace.main: Refreshing state... [id=g-f2520a71cc]
data.aws_region.current: Read complete after 0s [id=ap-northeast-1]
aws_grafana_workspace_api_key.main: Refreshing state... [id=g-f2520a71cc/test-key]
grafana_data_source.prometheus: Refreshing state... [id=1]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are
needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

API Keyがexpireしたら? → NG

作成したaws_grafana_workspace_api_key.mainseconds_to_liveattributeは180つまり3分に設定したので、3分以上待ってもう一度コマンド実行します。

 % terraform apply
data.aws_region.current: Reading...
aws_grafana_workspace.main: Refreshing state... [id=g-f2520a71cc]
data.aws_region.current: Read complete after 0s [id=ap-northeast-1]
aws_grafana_workspace_api_key.main: Refreshing state... [id=g-f2520a71cc/test-key]
grafana_data_source.prometheus: Refreshing state... [id=1]
╷
│ Error: status: 401, body: {"message":"Expired API key"}
│ 
│ 
│   with grafana_data_source.prometheus,
│   on grafana_data_source.tf line 1, in resource "grafana_data_source" "prometheus":
│    1: resource "grafana_data_source" "prometheus" {
│ 
╵

terraform applyすると、まず現状のリソースの状況を確認する処理(Refreshing state)が走ります。grafana_data_source.prometheusも既にプロビジョニングされているリソースなのでこの処理が必要です。この際、Grafana provider経由でこの処理が行われるのですが、Grafana providerで使っているAPI Keyは既にexpire(期限切れ)しているためgrafana_data_source.prometheusの実態にアクセスして状況を確認することができません。

まとめ

新リソースaws_grafana_workspace_api_keyを使ってGrafana Providerの設定をするのはうまくいかないことがわかりました。aws_grafana_workspace_api_keyはあくまでひとつAPI Keyをプロビジョニングするものであり、expireしたら新しいkeyを作り直してくれたりみたいな便利な処理は当然スコープ外です。(なんならexpireしているのかどうかすらわからない。)新リソースaws_grafana_workspace_api_keyに対する私の誤解(過度な期待)でした。。 というわけで前回の構成を使い続けようと思います。