Eclipsys Blog

OCI, Terraform & IaC: Creating a Compartment – Eclipsys

Written by Gustavo Rene Antunez | Feb 29, 2024 11:53:00 PM

In my previous post, I talked about the setup of Terraform and a primer on what it is. In this blog post, I will create a simple resource in OCI.

One of the things that I will note is that in this day and age, Terraform (TF) is a mature IaC (Infrastructure as Code) tool, so I will be relying on things that other people have done. I will credit them when I use their code, as this is more of a tutorial on how to use TF, not on how I created code for it.

One of the repositories that I like is Martin Linxfeld, he does a great tutorial on how to use TF and has a cool repository. So this first part, I’m basing on this code, with my modifications so that I keep it quite simple and hope you understand what TF does.

It is important to know that Terraform operates on a declarative basis rather than a procedural one. Once Terraform parses all files in the directory, it generates a dependency graph encompassing all declared resources and data sources detected in the .tf files. During the ‘apply’ phase, Terraform executes this graph, prioritizing any necessary changes based on dependencies detected. 

So now that we understand how Terraform creates its execution graph, we can start talking about the files we are using for this first example:

[opc@oracle-rene-cloud-ace vol01_compartment]$ tree
.
├── compartment.tf
├── datasources.tf
├── provider.tf
├── setup_oci_tf_vars.sh
└── variables.tf

Let’s start with the setup_oci_tf_vars.sh, this is where we will source our TF_VAR environment variables, these will be checked last for a value. All of this information, we gathered it in the previous post for our Application Key and the queries we did when we tested the OCI CLI commands.

Terraform variables allow you to write configurations that are flexible and easier to reuse. The variables.tf file in Terraform serves as a dedicated location for defining input variables within infrastructure provisioning projects. This file essentially encapsulates parameters essential for configuring infrastructure, enabling adjustments to deployment settings without cluttering the main Terraform scripts.

[opc@oracle-rene-cloud-ace vol01_compartment]$ cat setup_oci_tf_vars.sh
export TF_VAR_user_ocid="ocid1.user.oc1..aaaaaaaaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export TF_VAR_tenancy_ocid="ocid1.tenancy.oc1..aaaaaaaaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export TF_VAR_compartment_ocid="ocid1.compartment.oc1..aaaaaaaaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export TF_VAR_fingerprint="ee:xxxxxxxxx:03"
export TF_VAR_private_key_path="xxxxxxxxxxxxxx"
export TF_VAR_availablity_domain_name="LVfX:CA-TORONTO-1-AD-1"
export TF_VAR_region="ca-toronto-1"
[opc@oracle-rene-cloud-ace vol01_compartment]$ source ./setup_oci_tf_vars.sh
[opc@oracle-rene-cloud-ace vol01_compartment]$ cat variables.tf
# All variables used by Terraform
variable "tenancy_ocid" {}
variable "user_ocid" {}
variable "fingerprint" {}
variable "private_key_path" {}
variable "compartment_ocid" {}
variable "region" {}
variable "availablity_domain_name" {
default = ""
}

The tree below illustrates the priority order from the highest precedence at the top to the lowest precedence at the bottom. Variables set through environment variables override those set in Terraform configuration files, which in turn override variables set in .tfvars files, and so forth. Command-line flags take precedence over all of the above, and if no other values are provided, Terraform uses default values specified within the configuration files.

├── Environment Variables
├── Terraform Configuration Files (main.tf, variables.tf)
├── tfvars Files
├── Command-Line Flags
└── Default Values

As per the Terraform definition: Data sources allow Terraform to use information defined outside of Terraform, defined by another separate Terraform configuration, or modified by functions. A data block requests that Terraform read from a given data source (“oci_identity_region_subscriptions”) and export the result under the given local name (“home_region_subscriptions”). The name is used to refer to this resource from elsewhere in the same Terraform module.

# Home Region Subscription DataSource
data "oci_identity_region_subscriptions" "home_region_subscriptions" {
tenancy_id = var.tenancy_ocid
filter {
name = "is_home_region"
values = [true]
}
}
# Availability Domains DataSource
data "oci_identity_availability_domains" "ADs" {
compartment_id = var.tenancy_ocid
}

In the above datasources.tf file, you will see a filter, which is a way to query one or more name/value pairs from the data source. What we are looking for is to create this resource in our home region. The value of var.tenancy_ocid comes from the environment variable we sourced from setup_oci_tf_vars.sh.

Just as a reminder, a provider is a plugin that enables Terraform to interact with a specific cloud or infrastructure platform. Providers are responsible for translating Terraform configurations into API calls to manage resources within the targeted platform. I really like what Martin does in his code, where he is setting up 2 providers for OCI, one where it is treated as a global and the other one which is for the home region.

terraform {
required_version = ">= 0.15.0"
required_providers {
oci = {
source = "hashicorp/oci"
version = "= 4.48.0"
}
}
}
# General Provider
provider "oci" {
tenancy_ocid = var.tenancy_ocid
user_ocid = var.user_ocid
fingerprint = var.fingerprint
private_key_path = var.private_key_path
region = var.region
}
# Home Region Provider
provider "oci" {
alias = "homeregion"
tenancy_ocid = var.tenancy_ocid
user_ocid = var.user_ocid
fingerprint = var.fingerprint
private_key_path = var.private_key_path
region = data.oci_identity_region_subscriptions.home_region_subscriptions.region_subscriptions[0].region_name
disable_auto_retries = "true"
}

One very cool thing that you see here, is this line below

region = data.oci_identity_region_subscriptions.home_region_subscriptions.region_subscriptions[0].region_name

Where what he is doing is basically querying the datasource oci_identity_region_subscriptions.home_region_subscriptions , then getting the key values of region_subscriptions, and then getting the name of the home region name. We can add a file called outputs.tf and do a plan to see what that query is doing and how the filter is being done

[opc@oracle-rene-cloud-ace vol01_compartment]$ cat outputs.tf
output "homeregion" {
description = "Key Values for datasource home_region_subscriptions"
value = data.oci_identity_region_subscriptions.home_region_subscriptions.region_subscriptions[0]
}
output "homeregionname" {
description = "Name of home region from datasource home_region_subscriptions"
value = data.oci_identity_region_subscriptions.home_region_subscriptions.region_subscriptions[0].region_name
}
[opc@oracle-rene-cloud-ace vol01_compartment]$ terraform plan
...
Changes to Outputs:
+ homeregion = {
+ is_home_region = true
+ region_key = "YYZ"
+ region_name = "ca-toronto-1"
+ state = "READY"
+ tenancy_id = ""
}
+ homeregionname = "ca-toronto-1"

Last, we are going to create a file called compartment.tf and declare our resource, which is a compartment called ReneACECompartment, this will be created under the compartment that we declared in the TF variable we sourced called var.compartment_ocid and using the home region value from provider oci.homeregion.

resource "oci_identity_compartment" "ReneACECompartment" {
provider = oci.homeregion
name = "ReneACECompartment"
description = "Compartment for Rene ACE Terraform Tests"
compartment_id = var.compartment_ocid
provisioner "local-exec" {
command = "sleep 60"
}
}

Now that we have done this, we can run a plan and if everything is ok, we apply it to create our resource.

[opc@oracle-rene-cloud-ace vol01_compartment]$ terraform plan
data.oci_identity_availability_domains.ADs: Reading...
data.oci_identity_region_subscriptions.home_region_subscriptions: Reading...
data.oci_identity_availability_domains.ADs: Read complete after 1s [id=IdentityAvailabilityDomainsDataSource-2545584872]
data.oci_identity_region_subscriptions.home_region_subscriptions: Read complete after 1s [id=IdentityRegionSubscriptionsDataSource-2545584872]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# oci_identity_compartment.ReneACECompartment will be created
+ resource "oci_identity_compartment" "ReneACECompartment" {
+ compartment_id = "ocid1.compartment.oc1.. aaaaaaaaxxxxxxxxxxxxxxxxxxdd"
+ defined_tags = (known after apply)
+ description = "Compartment for Rene ACE Terraform Tests"
+ freeform_tags = (known after apply)
+ id = (known after apply)
+ inactive_state = (known after apply)
+ is_accessible = (known after apply)
+ name = "ReneACECompartment"
+ state = (known after apply)
+ time_created = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ homeregion = {
+ is_home_region = true
+ region_key = "YYZ"
+ region_name = "ca-toronto-1"
+ state = "READY"
+ tenancy_id = ""
}
+ homeregionname = "ca-toronto-1"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
[opc@oracle-rene-cloud-ace vol01_compartment]$ terraform apply
data.oci_identity_availability_domains.ADs: Reading...
data.oci_identity_region_subscriptions.home_region_subscriptions: Reading...
data.oci_identity_availability_domains.ADs: Read complete after 0s [id=IdentityAvailabilityDomainsDataSource-2545584872]
data.oci_identity_region_subscriptions.home_region_subscriptions: Read complete after 1s [id=IdentityRegionSubscriptionsDataSource-2545584872]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# oci_identity_compartment.ReneACECompartment will be created
+ resource "oci_identity_compartment" "ReneACECompartment" {
+ compartment_id = "ocid1.compartment.oc1.. aaaaaaaaxxxxxxxxxxxxxxxxxxdd"
+ defined_tags = (known after apply)
+ description = "Compartment for Rene ACE Terraform Tests"
+ freeform_tags = (known after apply)
+ id = (known after apply)
+ inactive_state = (known after apply)
+ is_accessible = (known after apply)
+ name = "ReneACECompartment"
+ state = (known after apply)
+ time_created = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ homeregion = {
+ is_home_region = true
+ region_key = "YYZ"
+ region_name = "ca-toronto-1"
+ state = "READY"
+ tenancy_id = ""
}
+ homeregionname = "ca-toronto-1"
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
oci_identity_compartment.ReneACECompartment: Creating...
oci_identity_compartment.ReneACECompartment: Provisioning with 'local-exec'...
oci_identity_compartment.ReneACECompartment (local-exec): Executing: ["/bin/sh" "-c" "sleep 60"]
...
[id=ocid1.compartment.oc1..aaaaaaaaxxxxxxxxxxxxxxxxxxdd]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
homeregion = {
"is_home_region" = true
"region_key" = "YYZ"
"region_name" = "ca-toronto-1"
"state" = "READY"
"tenancy_id" = ""
}

After the apply command finishes, we can see the compartment in OCI.

 

Conclusion

Hope this first exercise has helped in understand the basics of Terraform, and how we can use data sources, providers, and TF variables in our execution plan. In the next set of blogs will go into more complex scenarios of using Terraform.