Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turning prefetch on changes the view of the world in DNSimple #80

Closed
lawrencewilson opened this issue Feb 28, 2023 · 2 comments
Closed
Assignees
Labels

Comments

@lawrencewilson
Copy link

Terraform Version

Terraform v1.3.6
on darwin_arm64
+ provider registry.terraform.io/dnsimple/dnsimple v0.15.0

Affected Resource(s)

  • dnsimple_zone_record

Terraform Configuration Files

main.tf

terraform {
  required_providers {
    dnsimple = {
      source  = "dnsimple/dnsimple"
      version = "0.15.0"
    }
  }

  required_version = ">= 1.1.0"

  backend "remote" {
    organization = "example"

    workspaces {
      prefix = "example-"
    }
  }
}

provider "dnsimple" {
  token    = var.DNSIMPLE_TOKEN
  account  = var.DNSIMPLE_ACCOUNT
  sandbox  = var.DNSIMPLE_SANDBOX_ENVIRONMENT
  prefetch = var.DNSIMPLE_PREFETCH
}

locals {
  domain1_com      = "domain1.com"
  domain1_com_json = jsondecode(file("${path.root}/${local.domain1_com}.json"))
  domain1_com_set = flatten([for each in local.domain1_com_json :
    {
      "id"       = base64encode("${each.value}-${each.type}-${each.name}")
      "name"     = each.name
      "value"    = each.value
      "type"     = each.type
      "ttl"      = each.ttl
      "priority" = can(each.priority) ? each.priority : null
  }])
}

resource "dnsimple_zone_record" "domain1_com" {
  for_each = {
    for each in local.domain1_com_set : each.id => each
  }
  zone_name = local.domain1_com
  name      = each.value.name
  value     = each.value.value
  type      = each.value.type
  ttl       = each.value.ttl
  priority  = each.value.priority
}

variable "DNSIMPLE_TOKEN" {
  description = "DNsimple api token"
}

variable "DNSIMPLE_ACCOUNT" {
  description = "DNsimple account ID"
}

variable "DNSIMPLE_SANDBOX_ENVIRONMENT" {
  description = "true/false to specify if this is using sandbox"
}

variable "DNSIMPLE_PREFETCH" {
  description = "true/false to specify if this should save api requests to avoid rate-limiting"
}

domain1.com.json

[
  {
    "name": "",
    "value": "1.1.1.1",
    "type": "A",
    "zone": "domain1.com",
    "ttl": 600
  },
  {
    "name": "1a",
    "value": "1.1.1.1",
    "type": "A",
    "zone": "domain1.com",
    "ttl": 600
  },
  {
    "name": "2a",
    "value": "1.1.1.1",
    "type": "A",
    "zone": "domain1.com",
    "ttl": 600
  },
  {
    "name": "3a",
    "value": "1.1.1.1",
    "type": "A",
    "zone": "domain1.com",
    "ttl": 600
  },
  {
    "name": "1cname",
    "value": "www.example.com",
    "type": "CNAME",
    "zone": "domain1.com",
    "ttl": 600
  },
  {
    "name": "2cname",
    "value": "www.example.com",
    "type": "CNAME",
    "zone": "domain1.com",
    "ttl": 600
  },
  {
    "name": "3cname",
    "value": "www.example.com",
    "type": "CNAME",
    "zone": "domain1.com",
    "ttl": 600
  },
  {
    "name": "",
    "value": "https://www.example.com",
    "type": "URL",
    "zone": "domain1.com",
    "ttl": 600
  },
  {
    "name": "",
    "value": "mail.example.com",
    "type": "MX",
    "zone": "domain1.com",
    "ttl": 3600,
    "priority": 11
  },
  {
    "name": "1mx",
    "value": "mail.example.com",
    "type": "MX",
    "zone": "domain1.com",
    "ttl": 3600,
    "priority": 10
  },
  {
    "name": "2mx",
    "value": "mail25.example.com",
    "type": "MX",
    "zone": "domain1.com",
    "ttl": 3600,
    "priority": 5
  },
  {
    "name": "2mx",
    "value": "mail21.example.com",
    "type": "MX",
    "zone": "domain1.com",
    "ttl": 3600,
    "priority": 1
  },
  {
    "name": "example",
    "value": "ns-1.example.com",
    "type": "NS",
    "zone": "domain1.com",
    "ttl": 3600
  },
  {
    "name": "example",
    "value": "ns-2.example.com",
    "type": "NS",
    "zone": "domain1.com",
    "ttl": 3600
  },
  {
    "name": "example",
    "value": "ns-3.example.com",
    "type": "NS",
    "zone": "domain1.com",
    "ttl": 3600
  },
  {
    "name": "example",
    "value": "ns-4.example.com",
    "type": "NS",
    "zone": "domain1.com",
    "ttl": 3600
  },
  {
    "name": "_example",
    "value": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeee=",
    "type": "TXT",
    "zone": "domain1.com",
    "ttl": 600
  },
  {
    "name": "_example.e",
    "value": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeey",
    "type": "TXT",
    "zone": "domain1.com",
    "ttl": 600
  }
]

Debug Output

https://gist.github.com/lawrencewilson/c74945564685cb46241718a2db56a5b9

Panic Output

If Terraform produced a panic, please provide a link to a GitHub Gist containing the output of the crash.log.

Expected Behavior

What should have happened?

When you enable prefetch without changing the state, we should see No changes in terraform.

Actual Behavior

When you enable prefetch without changing the state, terraform detects drift where there is none and will attempt to delete and re-create records.

Steps to Reproduce

Please list the steps required to reproduce the issue, for example:

  1. Set the values as necessary (Ensure the prefetch is set to false):
$Env:TF_VAR_DNSIMPLE_TOKEN="secretvalue"
$Env:TF_VAR_DNSIMPLE_SANDBOX_ENVIRONMENT="true"
$Env:TF_VAR_DNSIMPLE_ACCOUNT="0000"
$Env:TF_WORKSPACE="localdev"
$Env:TF_VAR_DNSIMPLE_PREFETCH="false"
  1. Initialise the back end and apply the configuration
terraform init
terraform apply
  1. run terraform plan to ensure no state changes
terraform plan                                               
[WARN] Invalid log level: "TRUE". Defaulting to level: TRACE. Valid levels are: [TRACE DEBUG INFO WARN ERROR OFF][WARN] Invalid log level: "TRUE". Defaulting to level: TRACE. Valid levels are: [TRACE DEBUG INFO WARN ERROR 

... redacted to save space ...

dnsimple_zone_record.domain1_com["bWFpbC5leGFtcGxlLmNvbS1NWC0xbXg="]: Refreshing state... [id=2940594]
dnsimple_zone_record.domain1_com["aHR0cHM6Ly93d3cuZXhhbXBsZS5jb20tVVJMLQ=="]: Refreshing state... [id=2940578]

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.
  1. set the prefetch to true and re-initialize
PS > $Env:TF_VAR_DNSIMPLE_PREFETCH="true" 
PS > terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of dnsimple/dnsimple from the dependency lock file
- Using previously-installed dnsimple/dnsimple v0.15.0

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
  1. run another plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # dnsimple_zone_record.domain1_com["MS4xLjEuMS1BLQ=="] must be replaced
-/+ resource "dnsimple_zone_record" "domain1_com" {
      ~ id             = "2940587" -> (known after apply)
      ~ priority       = "0" -> (known after apply)
      ~ qualified_name = "domain1.com" -> (known after apply)
      ~ ttl            = "3600" -> "600"
      ~ type           = "SOA" -> "A" # forces replacement
      ~ value          = "ns1.dnsimple.com admin.dnsimple.com 1677551382 86400 7200 604800 300" -> "1.1.1.1"
      ~ zone_id        = "domain1.com" -> (known after apply)
        # (1 unchanged attribute hidden)
    }

  # dnsimple_zone_record.domain1_com["aHR0cHM6Ly93d3cuZXhhbXBsZS5jb20tVVJMLQ=="] must be replaced
-/+ resource "dnsimple_zone_record" "domain1_com" {
      ~ id             = "2940578" -> (known after apply)
      ~ priority       = "0" -> (known after apply)
      ~ qualified_name = "domain1.com" -> (known after apply)
      ~ ttl            = "3600" -> "600"
      ~ type           = "SOA" -> "URL" # forces replacement
      ~ value          = "ns1.dnsimple.com admin.dnsimple.com 1677551382 86400 7200 604800 300" -> "https://www.example.com"
      ~ zone_id        = "domain1.com" -> (known after apply)
        # (1 unchanged attribute hidden)
    }

  # dnsimple_zone_record.domain1_com["bWFpbC5leGFtcGxlLmNvbS1NWC0="] must be replaced
-/+ resource "dnsimple_zone_record" "domain1_com" {
      ~ id             = "2940580" -> (known after apply)
      ~ priority       = "0" -> "11"
      ~ qualified_name = "domain1.com" -> (known after apply)
      ~ type           = "SOA" -> "MX" # forces replacement
      ~ value          = "ns1.dnsimple.com admin.dnsimple.com 1677551382 86400 7200 604800 300" -> "mail.example.com"
      ~ zone_id        = "domain1.com" -> (known after apply)
        # (2 unchanged attributes hidden)
    }

  # dnsimple_zone_record.domain1_com["bWFpbDIxLmV4YW1wbGUuY29tLU1YLTJteA=="] will be updated in-place
  ~ resource "dnsimple_zone_record" "domain1_com" {
        id             = "2940600"
        name           = "2mx"
      ~ priority       = "5" -> "1"
      ~ value          = "mail25.example.com" -> "mail21.example.com"
        # (5 unchanged attributes hidden)
    }

  # dnsimple_zone_record.domain1_com["bnMtMS5leGFtcGxlLmNvbS1OUy1leGFtcGxl"] will be updated in-place
  ~ resource "dnsimple_zone_record" "domain1_com" {
        id             = "2940591"
        name           = "example"
      ~ value          = "ns-3.example.com" -> "ns-1.example.com"
        # (6 unchanged attributes hidden)
    }

  # dnsimple_zone_record.domain1_com["bnMtMi5leGFtcGxlLmNvbS1OUy1leGFtcGxl"] will be updated in-place
  ~ resource "dnsimple_zone_record" "domain1_com" {
        id             = "2940584"
        name           = "example"
      ~ value          = "ns-3.example.com" -> "ns-2.example.com"
        # (6 unchanged attributes hidden)
    }

  # dnsimple_zone_record.domain1_com["bnMtNC5leGFtcGxlLmNvbS1OUy1leGFtcGxl"] will be updated in-place
  ~ resource "dnsimple_zone_record" "domain1_com" {
        id             = "2940582"
        name           = "example"
      ~ value          = "ns-3.example.com" -> "ns-4.example.com"
        # (6 unchanged attributes hidden)
    }

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

Important Factoids

We have split the domain records out of terraform configuration and read them in as a set from a json file.

References

@DXTimer DXTimer added the bug label Mar 1, 2023
@DXTimer
Copy link
Contributor

DXTimer commented Mar 1, 2023

Thank you @lawrencewilson for the detailed steps on reproducing the issue. We will work on a fix in the coming days.

@DXTimer DXTimer self-assigned this Mar 2, 2023
DXTimer added a commit that referenced this issue Mar 6, 2023
It was found that the prefetch feature. Which pulls the entire zone and creates a local cache for the provider to use and determine if a record already exists as a performance improvement and a way to reduce the number of API requests. The lookup algorithm did a naive lookup only based on the name of the Resource Record (RR), however, that can yield false positives and also result in incorrect records being created/updated.

Implementation notes
Unfortunately, the current version of the TF SDK does not support testing of for_each resources. The algorithm was updated to match all attributes that make a resource unique which are name, type, and content. There is also the potential of using the record ids to match existing records but the current implementation allows for some flexibility when using the provider especially since we do not support duplicate records.

Belongs to #80
@DXTimer
Copy link
Contributor

DXTimer commented Mar 6, 2023

Happy to share that we've been able to address the issue and a fix has been released as part of 0.16.2. #84

In case there are further comments, questions, or new discoveries please feel free to comment on the issue and we will re-open it.

@DXTimer DXTimer closed this as completed Mar 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants