Create Database Tools Connections With Terraform

In this post we will look at setting up a Database Tools connection resource in Oracle Cloud Infrastructure (OCI) using the OCI Terraform provider. Database Tools connections in OCI are first-class resources just like other cloud primitives such as virtual cloud networks (VCN), compute instances, or databases. As such, you can manage connection resources using Terraform!

If you are not familiar with what a Database Tools connection in OCI does or how to set one up you can find more information in these related posts:

And of course, there is the official documentation:

TL/DR: If you want to skip the rest of this post and just jump straight to code scroll to the end or, OCI provides more examples here:

terraform-provider-oci/examples/databaseTools at master · oracle/terraform-provider-oci
Terraform Oracle Cloud Infrastructure provider. Contribute to oracle/terraform-provider-oci development by creating an account on GitHub.

You may find the following related links helpful for future reference:

Terraform is both a command line tool and a language for declaring resources that you create, often in the cloud, but cloud is not a hard requirement given providers can do non-cloud things like spin up a container locally.

💡
Terraform supports declaring resources using a configuration language or a JSON configuration syntax but in this post I am only showing the native Terraform configuration language.

Providers extend Terraform with new functionality for a given task. For example, they are responsible for converting your configuration into actual resources on a cloud service. The OCI providers for Terraform make REST API calls to the various control planes of OCI services.

To create OCI resources using Terraform I need to configure the OCI provider (tell it how to authenticate) and then configure OCI resources using resource blocks. What follows is a light tutorial on setting up Terraform locally, configuring it and then using it to create a resource. If you are only interested in the syntax for creating a connection skip to the end or see the examples linked above.

Prerequisites

If you want to follow along with this post, there are a number of pre-requisites you need to take care of which I will not provide steps for in this post.

I assume the following:

  • Terraform is installed and you can run terraform without error, for example:
$ terraform version
Terraform v1.10.5
...
  • An Oracle Cloud Infrastructure tenancy exists
  • A compartment is created in the tenancy where the Database Tools connection resource will be created
  • Policies are in place that authorize your authenticated principal to manage resources in the target compartment, or perhaps you are the administrator
  • An Autonomous Database exists in the same compartment for this demo with Public Access enabled (in practice the database would be in a different compartment but I will keep it simple for the demo)
  • An SSO wallet for that ADB database stored in a secret in a vault
💡
A wallet is required for ADB databases with public access without an ACL. (We aren't using private endpoints or an ACL in this example.) You need the SSO wallet if you want to follow along with the examples in this post but, of course, experiment with other configurations if your setup is different. At the time of this post, the Database Tools service supported all common Oracle Database and MySQL use cases in OCI.

  • A valid database username
  • A valid database password (for the database user) stored in a secret in a vault
  • An OCI configuration profile that you are able to use to call OCI services (used for authenticating via Terraform later)

About Secrets in Vaults

Regarding creating secrets in a vault, I don't cover the steps in this post but you can review OCI's documentation for Secrets in Vault if you need some pointers on how to create secrets. You can roughly compare secrets in a vault to a password manager, but for an OCI cloud tenancy.

Secrets in a vault are a hard requirement for the Database Tools service for connections because we don't store your passwords or wallet contents in the Database Tools service directly, only an opaque identifier that points to a secret.

Secrets are used at runtime by IAM principals that have been granted access via policies of your tenancy. At this point I'd like to recommend that if you have never created a Database Tools connection in OCI, it can be helpful to go through the process manually at least once to learn how it all works. The Database Tools UI in the OCI console has some nice features to make this all a little easier.

Finally, we are ready to begin. Lets start with setting up Terraform!

Configure Terraform

For this example I created a new folder called dbtools-demo and used that for all the Terraform code in the example.

The way I setup the configuration is not required. Terraform is pretty flexible with respect to configuration files. Feel free to dump everything in two files (main.tf and terraform.tfvars if you are following along)

I am going to keep it simple in this example (no sub modules) but extract some bits into separate files in the "root module" for my own benefit.

  • First, inside my working directory I created a new file called config.tf with the following contents:
variable "oci_profile" {}

terraform {
  required_version = ">= 1.10"

  required_providers {
    oci = {
      source  = "oracle/oci"
      version = ">= 6.21.0"
    }
  }
}

provider "oci" {
  config_file_profile = var.oci_profile
}

If you are targeting an older version you may need to substitute hashicorp/oci for the source property of the OCI provider. I am using oracle/oci.

  • Next I added a new file called terraform.tfvars with the following contents.
oci_profile = "DEFAULT"

This file will be used by terraform at runtime to populate input variable declarations with actual values. You could pass these in via command line arguments or environment variables if you prefer.

  • Next I ran terraform init from my dbtools-demo directory to download the OCI provider and initialize the Terraform backend. (a local backend by default)
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding oracle/oci versions matching ">= 6.21.0"...
- Installing oracle/oci v6.23.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.
⚠️
I used a lower bound for the Terraform and OCI provider versions but in production systems you should determine if you need to be more restrictive / specific. Upgrading critical infrastructure tools automatically requires care and attention.

Query OCI With a Data Source

Next we will add something to the Terraform state so that we have a value to look at for debugging. This will also validate the OCI provider is working as expected.

  • I added a file to the working directory called main.tf with the following contents. Here I introduced two new variables so I added these to my terraform.tfvars file with appropriate values specified for compartment_name and tenancy_ocid.
variable "tenancy_ocid" {}
variable "compartment_name" {}

data "oci_identity_compartments" "demo_compartment" {
  compartment_id = var.tenancy_ocid
  name = var.compartment_name
}

output "compartment_id" {
  value = data.oci_identity_compartments.demo_compartment.compartments[0].id
}

main.tf example for testing

Let's break this down. First I declare some variables that I will pass in when I run terraform on the command line.

Then I used a data source to ask OCI Identity for a list of compartments (effectively one) with the specified name.

Finally an output block defined a value compartment_id that is saved in the Terraform state. I did this just for debugging but in complex configurations with sub modules outputs are often useful.

A note about the data source above, it requires a compartment_id parameter. This is not the ID of the compartment I am searching for, but rather the ID of the parent compartment to search within (usually the tenancy). Technically this is a compartment list operation but later I grab the first compartment returned because compartment names are unique.

Remember those prerequisites? I already have a tenancy, I created a compartment in my tenancy called icodealot_dev for the purpose of the demo. And, I already have an OCI configuration file setup with a DEFAULT profile. For reference my terraform.tfvars file now looks like this:

oci_profile = "DEFAULT"
tenancy_ocid = "ocid1.tenancy.oc1..aaaabbbbccccmytenancyocid"
compartment_name = "icodealot_dev"
  • With all of that in place, I ran terraform plan
$ terraform plan
data.oci_identity_compartments.demo_compartment: Reading...
data.oci_identity_compartments.demo_compartment: Read complete after 2s ...

Changes to Outputs:
  + compartment_id = "ocid1.compartment.oc1..aaaabbbccccdemocompartmentocid"

...

Success! This is great and now we know that the OCI provider is configured correctly and Terraform is able to talk to OCI using my OCI configuration file.

💡
If you are following along and get errors at this step you should fix those before moving on. Check the OCI profile is working as expected.

To validate an OCI profile (i.e. with a developer's key pair) is correct, you can use the OCI command line to do something like:

$ oci --profile <profile> iam compartment list --name icodealot_dev
{
  "data": [
    {
      "compartment-id": "ocid1.tenancy.oc1..aaaabbbccccmytenancyocidappearshere",
      "defined-tags": {
...

If the config profile uses temporary session tokens instead of an uploaded key pair you will need to add oci options such as -auth security_token, etc. If you don't have it yet, or you are are not sure that you have the correct compartment name (icodealot_dev in the example above) you can just list compartments with a --limit 1 and make sure you get back a successful response.

Create a Connection with Terraform

Now it is time to add the configuration for a Database Tools connection to the Terraform code. I updated main.tf to add the following variables at the top:

...
variable "db_name" {}
variable "db_user" {}
variable "db_password_secret_id" {}
variable "db_wallet_secret_id" {}

There are four new variables here but I would also like to note, you can also just hard-code known values in your config.tf and main.tf files for testing purposes. In addition to new variables defined in main.tf I updated my terraform.tfvars file to set the correct values. For example:

oci_profile = "DEFAULT"
tenancy_ocid = "ocid1.tenancy.oc1..aaaabbbbccccmytenancyocid"
compartment_name = "icodealot_dev"
db_name = "TESTFOR23AI"
db_user = "ADMIN"
db_password_secret_id = "ocid1.vaultsecret.oc1.phx.aaabbbccccpasswordsecretid"
db_wallet_secret_id = "ocid1.vaultsecret.oc1.phx.aaabbbccccwalletsecretid"

Recall from the prerequisites that we needed a password and SSO wallet secret in a vault as well as an existing ADB database.

Next we will use data sources to lookup connection related details that we will need for the Database Tools connection instead of hard-coding values such as a connection string. I also use this data source to provide an optional "related resource" on the Database Tools connection so that we get a nice link from the connection to the database in the UI of the OCI console.

I added the following Terraform code to my main.tf file.

...
data "oci_database_autonomous_databases" "demo_database" {
  compartment_id = local.demo_compartment_id
  display_name = var.db_name
}

locals {
  demo_compartment_id = data.oci_identity_compartments.demo_compartment.compartments[0].id
  demo_database = data.oci_database_autonomous_databases.demo_database.autonomous_databases[0]
  database_id = local.demo_database.id
  adb_connection_profiles = local.demo_database.connection_strings[0].profiles
  connection_string = [for p in local.adb_connection_profiles : p.value if p.consumer_group == "LOW"][0]
}
⚠️
The demo_database lookup shown here assumes the ADB database exists in the same compartment where the connection will be created. If your database is in a different compartment then update your Terraform code to reflect the correct compartment_id for your database instead of reusing the value of local.demo_compartment_id.

Notice that the locals block here comes after the data source but we are still able to use the value of local.demo_compartment_id here. Terraform takes care of resolving references and the order defined in configuration files is generally not significant, though you are also free to define multiple locals blocks in your files if it bothers you to have this visual oddity.

It's finally time to put all that work to good use. Here is our definition of the Database Tools connection at the end of our main.tf file.

...
resource "oci_database_tools_database_tools_connection" "demo_connection" {
  compartment_id = local.demo_compartment_id
  display_name = "dbtools_connection_terraform_demo"
  type = "ORACLE_DATABASE"
  related_resource {
    entity_type = "AUTONOMOUSDATABASE"
    identifier = local.database_id
  }
  user_name = var.db_user
  connection_string = local.connection_string
  user_password {
    value_type = "SECRETID"
    secret_id  = var.db_password_secret_id
  }
  key_stores {
    key_store_type = "SSO"
    key_store_content {
      value_type = "SECRETID"
      secret_id = var.db_wallet_secret_id
    }
  }
}

Finally! I ran terraform plan, validated the changes, and then ran terraform apply.

$ terraform apply
...
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

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

Checking in the OCI console I can see that there is a new connection so I can Validate to make sure everything works as expected:

Example validating a Database Tools connection created with Terraform

Since we took the time to setup a related resource we also get relevant links to the database from the connection screen. Cool!

I'll explore topics like this in future posts that build on the idea of using Terraform to manage resources in the cloud. I hope you found this post useful and I always appreciate feedback letting me know.

Thanks for reading and until next time.

Cheers!