terraform-provider-digitalocean icon indicating copy to clipboard operation
terraform-provider-digitalocean copied to clipboard

digitalocean_database_cluster password is always empty

Open razum90 opened this issue 1 month ago • 0 comments

Bug Report

Describe the bug

When creating a digitalocean_database_cluster resource, the password attribute for the admin user (doadmin) is always returned as empty.

Affected Resource(s)

  • digitalocean_database_cluster

Expected Behavior

I expect the password attribute to return the actual value.

Actual Behavior

It is not returning the password.

Steps to Reproduce

terraform apply

Terraform Configuration Files

terraform {
  required_providers {
    digitalocean = {
      source  = "digitalocean/digitalocean"
      version = "~> 2.38.0"
    }
    null = {
      source  = "hashicorp/null"
      version = "~> 3.1.0"
    }
  }
}

provider "digitalocean" {
  token = var.do_token
}

variable "do_token" {}
variable "region" {
  description = "DO region"
  type        = string
  default     = "fra1"
}
variable "services_names" {
  description = "Name of the services you want to create"
  type        = object({
    web = string
    api = string
  })
  default = {
    "web" = "web"
    "api" = "api"
  }
}
variable "app_name" {
  description = "Name of the app"
  type        = string
  default     = "your-app-name"
}
variable "environments" {
  description = "Map of environment names and their attributes"
  type        = map(any)
  default = {
    "prod" = {
      "domain" : "your-domain.com",
      "db" : {
        "production" : true,
        "size" : "db-s-1vcpu-1gb",
        "node_count" : 1,
      },
      "api" : {
        "instance_count" : 1,
        "size_slug" : "apps-s-1vcpu-0.5gb",
        "port" : 80
      },
      "web" : {
        "instance_count" : 1,
        "size_slug" : "basic-xxs",
        "port" : 80
      }
    }
  }
}

resource "digitalocean_database_cluster" "db-cluster" {
  for_each = var.environments

  name       = "${each.key}-cluster"
  engine     = "pg"
  version    = "15"
  size       = var.environments[each.key].db.size
  region     = var.region
  node_count = var.environments[each.key].db.node_count
}

resource "digitalocean_database_user" "api-user" {
  for_each = var.environments

  cluster_id = digitalocean_database_cluster.db-cluster[each.key].id
  name       = "${var.services_names.api}-user"
}

resource "digitalocean_database_db" "api-db" {
  for_each = var.environments

  cluster_id = digitalocean_database_cluster.db-cluster[each.key].id
  name       = "${var.services_names.api}-db"
}

resource "null_resource" "grant_permissions" {
  for_each = var.environments

  provisioner "local-exec" {
    command = <<EOT
      docker run --rm -e PGPASSWORD=${digitalocean_database_cluster.db-cluster[each.key].password} postgres:13 psql -h ${digitalocean_database_cluster.db-cluster[each.key].host} -U ${digitalocean_database_cluster.db-cluster[each.key].user} -p ${digitalocean_database_cluster.db-cluster[each.key].port} -d ${digitalocean_database_db.api-db[each.key].name} -c "GRANT ALL PRIVILEGES ON DATABASE ${digitalocean_database_db.api-db[each.key].name} TO ${digitalocean_database_user.api-user[each.key].name};"
    EOT

    environment = {
      PGPASSWORD = digitalocean_database_cluster.db-cluster[each.key].password
    }
  }

  depends_on = [
    digitalocean_database_cluster.db-cluster,
    digitalocean_database_user.api-user,
    digitalocean_database_db.api-db
  ]
}

resource "digitalocean_database_firewall" "db-cluster-fw" {
  for_each = var.environments

  cluster_id = digitalocean_database_cluster.db-cluster[each.key].id

  rule {
    type  = "app"
    value = digitalocean_app.do-app[each.key].id
  }

  depends_on = [null_resource.grant_permissions]
}

resource "digitalocean_app" "do-app" {
  for_each = var.environments

  lifecycle {
    ignore_changes = [
      spec.0.features,
      spec.0.region,
      spec.0.service.0.image,
      spec.0.service.1.image
    ]
  }

  spec {
    name   = "${var.app_name}-${each.key}"
    region = var.region

    domain {
      name = var.environments[each.key].domain
    }

    alert {
      rule = "DEPLOYMENT_FAILED"
    }

    service {
      name               = var.services_names.api
      instance_count     = var.environments[each.key].api.instance_count
      instance_size_slug = var.environments[each.key].api.size_slug

      image {
        registry_type = "DOCKER_HUB"
        repository    = "nginx"
        tag           = "latest"
      }

      http_port = var.environments[each.key].api.port

      env {
        key   = "DB_PASSWORD"
        value = digitalocean_database_user.api-user[each.key].password
      }

      env {
        key   = "DB_HOST"
        value = digitalocean_database_cluster.db-cluster[each.key].private_host
      }

      env {
        key   = "DB_PORT"
        value = digitalocean_database_cluster.db-cluster[each.key].port
      }

      env {
        key   = "DB_NAME"
        value = digitalocean_database_db.api-db[each.key].name
      }

      env {
        key   = "DB_USER"
        value = digitalocean_database_user.api-user[each.key].name
      }
    }

    service {
      name               = var.services_names.web
      instance_count     = var.environments[each.key].web.instance_count
      instance_size_slug = var.environments[each.key].web.size_slug

      image {
        registry_type = "DOCKER_HUB"
        repository    = "nginx"
        tag           = "latest"
      }

      http_port = var.environments[each.key].web.port
    }

    database {
      name         = digitalocean_database_db.api-db[each.key].name
      db_name      = digitalocean_database_db.api-db[each.key].name
      cluster_name = digitalocean_database_cluster.db-cluster[each.key].name
      production   = var.environments[each.key].db.production
    }

    ingress {
      rule {
        component {
          name = var.services_names.api
        }
        match {
          path {
            prefix = "/api"
          }
        }
      }

      rule {
        component {
          name = var.services_names.web
        }

        match {
          path {
            prefix = "/"
          }
        }
      }
    }
  }
}

Terraform version 1.8.4

Debug Output https://gist.github.com/razum90/780047c0f021b02832b8a67e6be84490

Additional context

I have also tried to save digitalocean_database_cluster.db-cluster[each.key].password as an env variable to my service, and it shows empty there too.

Important Factoids It seems like the initial call POST /v2/databases returns the password. But the subsequent GET /v2/databases/{id} does not. I guess the GET calls is used for some sort of polling. I suppose it would be an issue in case the response of the GET request is used to build the output of the digitalocean_database_cluster resource.

BR

razum90 avatar May 22 '24 22:05 razum90