segmentio / terraform-provider-segment

Terraform provider for Segment, using the Public API
https://registry.terraform.io/providers/segmentio/segment/latest
MIT License
25 stars 3 forks source link

Trying to add two GA4 Web destinations results in a "not supported" error from empty Terraform state. #104

Closed dossy closed 2 months ago

dossy commented 3 months ago

Description

Trying to create two GA4 Web destinations when Terraform state is first initialized results in the following error:

│ Error: Unable to create Destination
│ 
│   with segment_destination.ga4_web_2,
│   on main.tf line 48, in resource "segment_destination" "ga4_web_2":
│   48: resource "segment_destination" "ga4_web_2" {
│ 
│ 400 Bad Request
│ {
│   "errors": [
│     {
│       "type": "bad-request",
│       "message": "Multiple configurations of this destination type not supported."
│     }
│   ]
│ }

According to the documentation:

Segment allows you to connect a source to multiple instances of a destination. You can use this to set up a single Segment source that sends data into different instances of your analytics and other tools.

I interpret this to mean that it should be possible to do this using the Segment Terraform provider, then.

To Reproduce

$ terraform version
Terraform v1.7.5
on linux_amd64
+ provider registry.terraform.io/segmentio/segment v1.0.0

Example Terraform code that triggers the issue:

# main.tf

terraform {
  required_providers {
    segment = {
      source  = "segmentio/segment"
      version = "1.0.0"
    }
  }
}

provider "segment" {
  # PUBLIC_API_TOKEN environment variable contains the Public API token
}

resource "segment_source" "test_source" {
  slug    = "test_source"
  name    = "Test Source"
  enabled = true
  metadata = {
    id = "IqDTy1TpoU" # Javascript
  }
  settings = jsonencode({})
}

resource "segment_destination" "test_destination_1" {
  name      = "Test Destination 1"
  enabled   = true
  source_id = segment_source.test_source.id
  metadata = {
    id = "63ed446fe60a1b56c5e6f130" # Google Analytics 4 Web
  }
  settings = jsonencode({
    adPersonalizationConsentState       = "granted"
    adUserDataConsentState              = "granted"
    allowAdPersonalizationSignals       = true
    allowGoogleSignals                  = true
    cookieDomain                        = "auto"
    cookieExpirationInSeconds           = 63072000
    cookieFlags                         = []
    cookiePath                          = "/"
    cookiePrefix                        = [""]
    cookieUpdate                        = true
    defaultAdsStorageConsentState       = "granted"
    defaultAnalyticsStorageConsentState = "granted"
    enableConsentMode                   = false
    measurementID                       = "G-XXXXXXXXXX"
    pageView                            = false
    waitTimeToUpdateConsentStage        = 0
  })
}

resource "segment_destination" "test_destination_2" {
  name      = "Test Destination 2"
  enabled   = true
  source_id = segment_source.test_source.id
  metadata = {
    id = "63ed446fe60a1b56c5e6f130" # Google Analytics 4 Web
  }
  settings = jsonencode({
    adPersonalizationConsentState       = "granted"
    adUserDataConsentState              = "granted"
    allowAdPersonalizationSignals       = true
    allowGoogleSignals                  = true
    cookieDomain                        = "auto"
    cookieExpirationInSeconds           = 63072000
    cookieFlags                         = []
    cookiePath                          = "/"
    cookiePrefix                        = [""]
    cookieUpdate                        = true
    defaultAdsStorageConsentState       = "granted"
    defaultAnalyticsStorageConsentState = "granted"
    enableConsentMode                   = false
    measurementID                       = "G-XXXXXXXXXX"
    pageView                            = false
    waitTimeToUpdateConsentStage        = 0
  })
}
# Make sure there's no Terraform state file.
$ rm terraform.tfstate*

# Set PUBLIC_API_TOKEN to your Segment Public API token.
$ export PUBLIC_API_TOKEN="<insert your token here>"

# Initialize Terraform.
$ terraform init -upgrade

# Apply the Terraform config, watch it throw the error.
$ terraform apply -auto-approve
...
Plan: 3 to add, 0 to change, 0 to destroy.
segment_source.test_source: Creating...
segment_source.test_source: Creation complete after 1s [id=ikU9a4gcc4TG8vWPcSPN5F]
segment_destination.test_destination_2: Creating...
segment_destination.test_destination_1: Creating...
segment_destination.test_destination_1: Creation complete after 1s [id=66161e6dfee6bd69274a19de]
╷
│ Error: Unable to create Destination
│ 
│   with segment_destination.test_destination_2,
│   on main.tf line 51, in resource "segment_destination" "test_destination_2":
│   51: resource "segment_destination" "test_destination_2" {
│ 
│ 400 Bad Request
│ {
│   "errors": [
│     {
│       "type": "bad-request",
│       "message": "Multiple configurations of this destination type not supported."
│     }
│   ]
│ }
╵

# Try applying it again, notice it still fails.
$ terraform apply -auto-approve
...
Plan: 1 to add, 0 to change, 0 to destroy.
segment_destination.test_destination_2: Creating...
╷
│ Error: Unable to create Destination
│ 
│   with segment_destination.test_destination_2,
│   on main.tf line 51, in resource "segment_destination" "test_destination_2":
│   51: resource "segment_destination" "test_destination_2" {
│ 
│ 400 Bad Request
│ {
│   "errors": [
│     {
│       "type": "bad-request",
│       "message": "Multiple configurations of this destination type not supported."
│     }
│   ]
│ }
╵

# Destroy the test source and first destination that actually got created.
$  terraform destroy -auto-approve
...
Plan: 0 to add, 0 to change, 2 to destroy.
segment_destination.test_destination_1: Destroying... [id=66161e6dfee6bd69274a19de]
segment_destination.test_destination_1: Destruction complete after 1s
segment_source.test_source: Destroying... [id=ikU9a4gcc4TG8vWPcSPN5F]
segment_source.test_source: Destruction complete after 0s

Destroy complete! Resources: 2 destroyed.

# Now, using the Terraform state file, although the resources have
# been destroyed, to try and create the resources again, succeeds.
$ cat terraform.tfstate
{
  "version": 4,
  "terraform_version": "1.7.5",
  "serial": 15,
  "lineage": "54d333b2-3050-5ae2-dd52-6c34827159bb",
  "outputs": {},
  "resources": [],
  "check_results": null
}

$  terraform apply -auto-approve
...
Plan: 3 to add, 0 to change, 0 to destroy.
segment_source.test_source: Creating...
segment_source.test_source: Creation complete after 1s [id=ejkWZpCVoL9xtV7rigUeKV]
segment_destination.test_destination_1: Creating...
segment_destination.test_destination_2: Creating...
segment_destination.test_destination_2: Creation complete after 0s [id=66161f09e332fcf240151e41]
segment_destination.test_destination_1: Creation complete after 0s [id=66161f09b51c954992253a67]

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

There seems to be a kind of race condition, as it only works if you start with a non-existent terraform.tfstate file, you do the first terraform apply that fails, then terraform destroy the source and first destination, and now using the terraform.tfstate that now exists, quickly do another terraform apply and it works, creating the source and two destinations.

I don't know if this is a problem with the Segment Terraform provider, or the Segment Public API, or what, but this is pretty consistently reproducible for me.

deanhuynh commented 2 months ago

Thanks for raising! It seems like the docs are a little misleading, in that there are some destination types that allow multiple instances and some that do not. You should not be able to create to GA4 Web instances for the same source. More info here. Feel free to reopen this if you have any further questions.