Use a bootstrapped controller¶
To bootstrap and use a controller you need two plans:
A bootstrap plan that creates the controller and writes connection info to a JSON file.
A second plan that reads that JSON file to configure the provider and deploy a test model.
Start with the bootstrap guidance in Bootstrap a controller from Manage controllers.
Plan 1: Bootstrap and write connection info¶
In the bootstrap plan, add a local_file resource to write a JSON file with the controller connection details. This uses the outputs from juju_controller.
terraform {
required_providers {
juju = {
source = "juju/juju"
}
local = {
source = "hashicorp/local"
}
}
}
provider "juju" {
controller_mode = true
}
locals {
lxd_creds = yamldecode(file("~/lxd-credentials.yaml"))
}
resource "juju_controller" "controller" {
name = "controller"
juju_binary = "/snap/juju/current/bin/juju"
cloud = {
name = "localhost"
auth_types = ["certificate"]
type = "lxd"
endpoint = local.lxd_creds.endpoint
region = {
name = "localhost"
}
}
cloud_credential = {
name = "localhost"
auth_type = "certificate"
attributes = {
server-cert = local.lxd_creds.server-cert
}
}
lifecycle {
ignore_changes = [
cloud_credential.attributes["client-cert"],
cloud_credential.attributes["client-key"],
]
}
}
resource "local_file" "conn_info_json" {
filename = "${path.module}/conn_info.json"
content = jsonencode({
username = juju_controller.controller.username
password = juju_controller.controller.password
addresses = juju_controller.controller.api_addresses
ca_cert = juju_controller.controller.ca_cert
})
}
Plan 2: Read JSON and deploy a test model¶
In the second plan, read the JSON file to configure the provider, then add a model resource (for example, test-model).
terraform {
required_providers {
juju = {
source = "juju/juju"
}
}
}
locals {
conn_info = jsondecode(file("${path.module}/../bootstrap/conn_info.json"))
}
provider "juju" {
controller_addresses = join(",", local.conn_info.addresses)
username = local.conn_info.username
password = local.conn_info.password
ca_certificate = local.conn_info.ca_cert
}
resource "juju_model" "test" {
name = "test-model"
}
Note
Keep conn_info.json private because it contains credentials.
Why this must be two plans¶
Terraform does not support configuring a provider block from resource outputs in the same plan. While it may appear to work in some cases, it is not a supported Terraform feature and may break at any time. For that reason, keep bootstrapping (controller creation) and controller use (provider configuration for resources) in separate plans.
Controller replacement and state cleanup¶
If the controller created by the bootstrap plan is destroyed or replaced, the second plan’s state will still reference resources tied to the old controller. In that case, you must manually remove the second plan’s state (or the affected resources) before reapplying. As a rule of thumb, destroy the second plan first, then destroy or replace the controller in the bootstrap plan.