Best Practices from Oracle Development's A‑Team

A small terraform trick to make your OCI terraforming life a bit easier

Terraform (https://www.terraform.io/) is a powerful way to manage your OCI environments. It allows for "infrastructure as code" to be a reality in many ways, and you can create and manage advanced cloud environments using it.

One of the first problems when using terraform is managing your OCI credentials to avoid duplication and inadvertently checking them into your source control.

This post shows a quick trick for reading the pre-existing OCI CLI configuration into your terraform environment, avoiding duplication of sensitive data. It is just a bit of terraform slice and dice magic that allows for all the profiles in your OCI config file to be read, and selected based on a variable.

To use, simply paste the following into a file named oci.tf in an empty directory and then run terraform init, followed by terraform plan in the same directory. You'll see that terraform will read your existing OCI CLI configuration file and select the default profile from it, connect to OCI, and present you with your user's information.

Example output

> terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/oci from the dependency lock file
- Installing hashicorp/oci v4.5.0...
- Installed hashicorp/oci v4.5.0 (signed by HashiCorp)

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.

> terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:

Terraform will perform the following actions:

Plan: 0 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + oci_user = {
      + capabilities                   = [
          + {
              + can_use_api_keys                 = true
              + can_use_auth_tokens              = true
              + can_use_console_password         = true
              + can_use_customer_secret_keys     = true
              + can_use_oauth2client_credentials = true
              + can_use_smtp_credentials         = true
      + compartment_id                 = "ocid1.tenancy.oc1.."
      + defined_tags                   = {}
      + description                    = "local user"
      + email                          = "@oracle.com"
      + email_verified                 = true
      + external_identifier            = null
      + freeform_tags                  = {}
      + id                             = "ocid1.user.oc1.."
      + identity_provider_id           = null
      + inactive_state                 = null
      + last_successful_login_time     = "2020-11-26 19:17:15.03 +0000 UTC"
      + name                           = ""
      + previous_successful_login_time = null
      + state                          = "ACTIVE"
      + time_created                   = "2020-10-14 21:09:29.945 +0000 UTC"
      + user_id                        = "ocid1.user.oc1.."

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: 0 added, 0 changed, 0 destroyed.


oci_user = {
  "capabilities" = tolist([
      "can_use_api_keys" = true
      "can_use_auth_tokens" = true
      "can_use_console_password" = true
      "can_use_customer_secret_keys" = true
      "can_use_oauth2client_credentials" = true
      "can_use_smtp_credentials" = true
  "compartment_id" = "ocid1.tenancy.oc1.."
  "defined_tags" = tomap({})
  "description" = "local user"
  "email" = "@oracle.com"
  "email_verified" = true
  "external_identifier" = tostring(null)
  "freeform_tags" = tomap({})
  "id" = "ocid1.user.oc1.."
  "identity_provider_id" = tostring(null)
  "inactive_state" = tostring(null)
  "last_successful_login_time" = "2020-11-26 19:17:15.03 +0000 UTC"
  "name" = ""
  "previous_successful_login_time" = tostring(null)
  "state" = "ACTIVE"
  "time_created" = "2020-10-14 21:09:29.945 +0000 UTC"
  "user_id" = "ocid1.user.oc1.."

File content

# A terraform demonstrating how to process the OCI config file and use it to populate the OCI terraform provider
locals {
    # Read the file from disk and split it into an array of lines
    cflines = split("\n", file(pathexpand(var.oci_file)))
    # First, identify the profile headings - they're standard INI format surrounded by square braces e.g [DEFAULT]
    ocipf = [for line in local.cflines: flatten(regexall("\\[([\\w]+)\\]", line))]
    # turn the profile headings into a compact list of the line indices of the headings
    ociln = compact([for i in range(length(local.ocipf)) : length(local.ocipf[i])>0 ? i : ""])
    # generate an off-by-one copy of the above list and append the "end of the file" length to it
    ociln2 = concat(slice(local.ociln,1,length(local.ociln)),[length(local.cflines)])
    # slice the original lines into an indexed map keyed by the profile name
    ocilns = {for i in range(length(local.ociln)):element(local.ocipf, local.ociln[i])[0]=>slice(local.cflines, local.ociln[i]+1, local.ociln2[i]-1)}
    # for each profile, flatten out the key-value pairs in each profile into a key=>value map
    oci_profiles = {for k,v in local.ocilns:k=>{for line in v: flatten(regexall("(?P[\\w]+)=(?P.+)$", line))[0]["key"]=>flatten(regexall("(?P[\\w]+)=(?P.+)$", line))[0]["value"]}}

# Define a couple of input variables
variable "oci_file" {
    description = <<-EOD
    Location of OCI config file (defaults to ~/.oci/config)
    type = string
    default = "~/.oci/config"
    validation {
        condition = fileexists(var.oci_file)
        error_message = "The oci config file does not exist."

variable "profile_name" {
    description = <<-EOD
    Name of profile from OCI file (defaults to DEFAULT)
    type = string
    default = "DEFAULT"

# Initialize the OCI provider with data from the oci_profiles array we read in from the config file. Note the passphrase may not be present and is handled optionally
provider "oci" {
    tenancy_ocid         = local.oci_profiles[var.profile_name].tenancy
    user_ocid            = local.oci_profiles[var.profile_name].user
    fingerprint          = local.oci_profiles[var.profile_name].fingerprint
    private_key_path     = local.oci_profiles[var.profile_name].key_file
    region               = local.oci_profiles[var.profile_name].region
    private_key_password = try(local.oci_profiles[var.profile_name].pass_phrase,null)

# Build a data query for the user information for the OCI user
data "oci_identity_user" "user_info" {
    user_id = local.oci_profiles[var.profile_name].user

# Output the found user information
output "oci_user" {
    value = data.oci_identity_user.user_info

I hope some find utility in this. I know I will. There'll be more terraform and functions to come soon. Stay tuned!

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha