X

Best Practices from Oracle Development's A‑Team

  • December 4, 2020

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.

Outputs:

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


# This terraform code parses the ~/.oci/config file to populate configured profiles into terraform allowing values in there to be used in terraform
locals {
  # Read the file from disk and split it into an array of lines
  cflines = split("\n", file(pathexpand("~/.oci/config")))
  # 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 compact(v) : flatten(regexall("(?P[\\w]+)=(?P.+)$", line))[0]["key"] => flatten(regexall("(?P[\\w]+)=(?P.+)$", line))[0]["value"] } }
}

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

# Initialize the OCI provider with specified profile name
provider "oci" {
  config_file_profile = local.profile
}

# Build the data from the oci file profile into oci_data. Can be queried later
locals {
  oci_data = { for k, v in local.oci_profiles[local.profile] : k => v if !contains(["fingerprint", "key_file", "passphrase"], k) }
}

# Build a data query for the user information for the OCI user
data "oci_identity_user" "user_info" {
    user_id = local.oci_data.user
}

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

The local.oci_data variable will contain all the elements of the selected oci_profile, including custom strings, for query and use inside terraform, which, as demonstrated, is very handy.

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