Manage models¶
See also: Juju | Model
Reference an externally managed model¶
To reference a model that you’ve created outside of the current Terraform plan (read-only access), in your Terraform plan add a data source of the juju_model type, specifying the name of the model. For example:
data "juju_model" "mymodel" {
name = "development"
}
See more:
juju_model(data source)
Add a model¶
To add a model to the controller specified in the juju provider definition, in your Terraform plan create a resource of the juju_model type, specifying, at the very least, a name. For example:
resource "juju_model" "testmodel" {
name = "machinetest"
}
In the case of a multi-cloud controller, you can specify which cloud you want the model to be associated with by defining a cloud block. To specify a model configuration, include a config block.
See more:
juju_model(resource)
Configure a model¶
See also: Juju | Model configuration , Juju | List of model configuration keys
See related: Juju | Configure a controller
With the Terraform Provider for Juju you can only set configuration values, only for a specific model, and only a workload model; for anything else, please use the juju CLI.
To configure a specific workload model, in your Terraform plan, in the model’s resource definition, specify a config block, listing all the key=value pairs you want to set. For example:
resource "juju_model" "this" {
name = "development"
cloud {
name = "aws"
region = "eu-west-1"
}
config = {
logging-config = "<root>=INFO"
development = true
no-proxy = "jujucharms.com"
update-status-hook-interval = "5m"
}
}
See more:
juju_model(resource)
Manage constraints for a model¶
See also: Juju | Constraint
With the Terraform Provider for Juju you can only set constraints – to view them, please use the juju CLI.
To set constraints for a model, in your Terraform, in the model’s resource definition, specify the constraints attribute (value is a quotes-enclosed space-separated list of key=value pairs). For example:
resource "juju_model" "this" {
name = "development"
cloud {
name = "aws"
region = "eu-west-1"
}
constraints = "cores=4 mem=16G"
}
See more:
juju_model(resource)
Manage annotations for a model¶
To set annotations for a model, in your Terraform, in the model’s resource definition, specify an annotations block, listing all the key=value pairs you want to set. For example:
resource "juju_model" "testmodel" {
name = "model"
annotations = {
test = "test"
}
}
See more:
juju_model(resource)
Manage access to a model¶
Your model access management options depend on whether the controller you are applying the Terraform plan to is a regular Juju controller or rather a Juju controller added to JIMM – for the former you can grant access only to a user, but for the latter you can grant access to a user, a service account, a role, or a group.
For a regular Juju controller¶
To grant one or more users access to a model, in your Terraform plan add a juju_access_model resource. You must specify the model, the Juju access level, and the user(s) to which you want to grant access. For example:
resource "juju_access_model" "this" {
model = juju_model.dev.name
access = "write"
users = [juju_user.dev.name, juju_user.qa.name]
}
See more:
juju_access_model, Juju | Model access levels
For a Juju controller added to JIMM¶
To grant one or more users, service accounts, roles, and/or groups access to a model, in your Terraform plan add a resource type juju_jaas_access_model. You must specify the model UUID, the JAAS model access level, and the desired list of users, service accounts, roles, and/or groups. For example:
resource "juju_jaas_access_model" "development" {
model_uuid = juju_model.development.uuid
access = "administrator"
users = ["foo@domain.com"]
service_accounts = ["Client-ID-1", "Client-ID-2"]
roles = [juju_jaas_role.development.uuid]
groups = [juju_jaas_group.development.uuid]
}
See more:
juju_jaas_access_model, JAAS | Model access levels
Migrate a model¶
This section highlights what changes, if any, are needed to your Terraform plan after migrating a model(s) between Juju controllers and/or JAAS. The Juju provider itself does not currently support migrating a model to a new controller, use the Juju CLI instead.
Migrating to another Juju controller¶
After a model is migrated to a new Juju controller, no changes are needed. Simply update your plan to connect to the new controller.
The only exception to this scenario is when cross-model relations are involved. If both models involved in a cross-model relation are moved, no changes are necessary. If only one model involved in the relation is moved, see below.
Cross-controller relations¶
If the providing model is moved, your applications will continue to work but the Juju provider has limitations on creating cross-controller relations.
This means that, if you modify your plan in a way that causes recreation of the relation, the creation operation will fail.
Migrating to JAAS¶
Migrating models to a JAAS environment requires some updates to your Terraform plan when cross-model relations are involved, even if both models in the relation are migrated.
When a model is migrated to JAAS, the model’s name and offer URLs will change. The JAAS documentation on model management provides more detail.
Our recommended way of resolving your Terraform state for this scenario is described below.
While it is recommended to migrate all models involved in a relation to JAAS, it is not a requirement, and migration can be done slowly over the course of days and weeks. See the section on cross-controller relations for the provider’s limitations on cross-controller relations.
Migration order
There is no recommended order to migrate your models. Regardless of the order you decide to migrate your models, i.e. providing model first or consuming model first, cross-model application offers are expected to continue working.
Handling the provider model¶
When the model providing an application offer is migrated to JAAS, its offer URL changes. Running your Terraform plan against the new controller will attempt to recreate the offer, breaking any applications with existing relations.
To resolve this we suggest removing the resource from Terraform’s state and re-importing it using the following commands:
terraform state rm juju_offer.<resource-name>
terraform import juju_offer.<resource-name> <new-offer-url>
The new offer URL can be obtained by running juju show-offer <offer-name> in the model hosting the offer.
Handling the consuming model¶
When the model consuming an application is migrated to JAAS, there may be changes required depending on how your plan is designed.
The following snippet will cause an error after migration:
data "juju_offer" "source-offer" {
url = "admin/source-model.dummy-source"
}
resource "juju_integration" "sink_source_integration" {
model = juju_model.this.name
application {
offer_url = juju_offer.source-offer.url
}
application {
name = juju_application.sink.name
endpoint = "source"
}
}
The juju_offer data source will return an error because this offer no longer exists at the same URL. However, the relation itself will continue to work because this URL is only used once, during the creation of the relation.
To resolve this error we suggest one of 2 options:
Recreate the relation using the new URL.
Change the plan to resemble the example below.
The simplest solution is to recreate the relation. Replace the URL in the data source with the new offer URL and allow Terraform to recreate the relation. Note that issue https://github.com/juju/juju/issues/20630 highlights a bug that causes issues for integrations on migrated models.
If the application cannot tolerate any downtime, we suggest modifying the plan to hard-code the offer URL into the integration resource and recreate the relation during a maintenance window.
Example:
resource "juju_integration" "sink_source_integration" {
model = juju_model.this.name
application {
offer_url = "admin/source-model.dummy-source"
}
application {
name = juju_application.sink.name
endpoint = "source"
}
}
Upgrade a model¶
See also: Juju | Upgrading things
To migrate a model to another controller, use the juju CLI to perform the migration, then, in your Terraform plan, reconfigure the juju provider to point to the destination controller (we recommend the method where you configure the provider using static credentials). You can verify your configuration changes by running terraform plan and noticing no change: Terraform merely compares the plan to what it finds in your deployment – if model migration with juju has been successful, it should detect no change.
See more: Use the Terraform Provider for Juju
Destroy a model¶
To destroy a model, remove its resource definition from your Terraform plan.
See more:
juju_model(resource)
Export a model¶
To export the Terraform configuration from a model, you can use terraform query.
See also: terraform query
Ensure the provider is connected to the model’s host controller¶
To export the Terraform configuration for a model, configure the provider to connect to the controller that hosts the model.
For example:
main.tf¶terraform {
required_providers {
juju = {
source = "juju/juju"
}
}
}
provider "juju" {
controller_addresses = "<addr>"
username = "<username>"
password = "<password>"
ca_certificate = "<ca_cert>
}
Define what you want to query for¶
Define the resources you want to export from a model using the list resources in the query file.
The query file must have the
.tfquery.hclextension.
For example, create example.tfquery.hcl with:
model
applications
machines
ssh keys
storage pools
integrations
offers
example.tfquery.hcl¶variable "model_uuid" {
description = "UUID of the model to export"
type = string
}
# ---------------------------------------------------------------------------
# List the model
# ---------------------------------------------------------------------------
list "juju_model" "model" {
provider = juju
include_resource = true
config {
model_uuid = var.model_uuid
}
}
# ---------------------------------------------------------------------------
# List all applications in the test model
# ---------------------------------------------------------------------------
list "juju_application" "all_apps" {
provider = juju
include_resource = true
config {
model_uuid = var.model_uuid
}
}
# ---------------------------------------------------------------------------
# List all offers in the test model
# ---------------------------------------------------------------------------
list "juju_offer" "all_offers" {
provider = juju
include_resource = true
config {
model_uuid = var.model_uuid
}
}
# ---------------------------------------------------------------------------
# List all machines in the test model
# ---------------------------------------------------------------------------
list "juju_machine" "all_machines" {
provider = juju
include_resource = true
config {
model_uuid = var.model_uuid
}
}
# ---------------------------------------------------------------------------
# List all SSH keys in the test model
# ---------------------------------------------------------------------------
list "juju_ssh_key" "all_ssh_keys" {
provider = juju
include_resource = true
config {
model_uuid = var.model_uuid
}
}
# ---------------------------------------------------------------------------
# List all integrations in the test model
# ---------------------------------------------------------------------------
list "juju_integration" "all_integrations" {
provider = juju
include_resource = true
config {
model_uuid = var.model_uuid
}
}
# ---------------------------------------------------------------------------
# List all secrets in the test model
# ---------------------------------------------------------------------------
list "juju_secret" "all_secrets" {
provider = juju
include_resource = true
config {
model_uuid = var.model_uuid
}
}
# ---------------------------------------------------------------------------
# List all storage pools in the test model
# ---------------------------------------------------------------------------
list "juju_storage_pool" "all_storage_pools" {
provider = juju
include_resource = true
config {
model_uuid = var.model_uuid
}
}
Query and generate the configuration file¶
To generate the config with the specified list resources into the test.tf file, run:
TF_VAR_model_uuid="<model-uuid>" terraform query --generate-config-out=test.tf
An example of the generated config:
resource "juju_application" "all_apps_0" {
provider = juju
config = null
constraints = "arch=amd64"
endpoint_bindings = null
machines = ["1"]
model_uuid = "<model-uuid>"
name = "<app-name>"
registry_credentials = null
resources = null
storage_directives = {}
trust = false
charm {
base = "ubuntu@20.04"
channel = "latest/stable"
name = "<charm>"
revision = <revision>
}
}
import {
to = juju_application.all_apps_0
provider = juju
identity = {
id = "<model-uuid>:<app-name>"
}
}
Run terraform apply to import all resources into your Terraform state.
Before confirming, verify that the plan shows no resources being replaced – all changes should be either no-op or in-place.
The terraform apply output should look something like this:
Plan: 19 to import, 0 to add, 6 to change, 0 to destroy.
...
...
...
Apply complete! Resources: 19 imported, 0 added, 6 changed, 0 destroyed.
Note: The generated configuration contains no cross-references between resources¶
The generated configuration contains the literal values for all the fields; that is, there is no reference between resources.
An example is:
resource "juju_model" "model_0" {
provider = juju
annotations = {}
constraints = null
credential = "localhost"
name = "test4"
target_controller = null
cloud {
name = "localhost"
region = "localhost"
}
}
resource "juju_application" "all_apps_0" {
provider = juju
config = null
constraints = "arch=amd64"
endpoint_bindings = null
machines = ["1"]
model_uuid = "c1cecf1e-fe66-4589-8585-e579edd6f58b"
name = "dummy-sink"
registry_credentials = null
resources = null
storage_directives = {}
trust = false
charm {
base = "ubuntu@20.04"
channel = "latest/stable"
name = "juju-qa-dummy-sink"
revision = 7
}
}
As shown above, the model_uuid in the juju_application resource is a hard-coded UUID rather than a reference to the juju_model resource.
If you copy this plan to deploy the same infrastructure on another controller, it will fail because Terraform will attempt to create the model and the application simultaneously without knowing their dependency.
The generated plan therefore requires manual intervention before it can be reused to reproduce the same model elsewhere. You need to cross-reference resources to ensure the correct dependency graph is built.
The generated configuration includes some default resources¶
Some resources are generated because they exist in the Juju model, but they are typically implicit resources that are not ordinarily managed by Terraform.
This includes:
default storage pools
default ssh keys
For example the loop storage pool is default in each model.
resource "juju_storage_pool" "all_storage_pools_3" {
provider = juju
attributes = null
model_uuid = "c1cecf1e-fe66-4589-8585-e579edd6f58b"
name = "loop"
storage_provider = "loop"
}
import {
to = juju_storage_pool.all_storage_pools_3
provider = juju
identity = {
id = "c1cecf1e-fe66-4589-8585-e579edd6f58b:loop"
}
}
Importing it is harmless, but attempting to destroy it via Terraform will result in an error:
juju_storage_pool.all_storage_pools_3: Destroying... [id=c1cecf1e-fe66-4589-8585-e579edd6f58b:loop]
╷
│ Error: Client Error
│
│ Unable to delete storage pool resource, got error: storage pool "loop" not found
╵
It is advisable to either remove these resources from the generated configuration before importing, or to simply avoid deleting them via Terraform.