OCI Terraform Part 5 - Understanding Inline and Dynamic Blocks in Terraform

Understanding Inline and Dynamic Blocks in Terraform

In this blog, we will see more details around inline and dynamic blocks in Terraform. Managing configurations efficiently is crucial for large-scale projects. Inline blocks and dynamic blocks allow you to simplify and optimize your Terraform code, improving readability and reducing repetition.

What are Inline Blocks in Terraform?

Inline blocks are nested blocks defined within a resource or module. They are used to configure multiple related settings in a single resource without duplicating the resource itself. This makes code more concise and easier to manage.

Benefits of using inline blocks:

  • Simplifies configuration by avoiding the need to create multiple resources.
  • Improves code readability.
  • Helps manage logically related configurations (e.g., multiple security rules, tags, etc.) within a single resource.

What are Dynamic Blocks in Terraform?

Dynamic blocks allow you to dynamically generate multiple nested blocks based on external data like lists or maps. This feature helps avoid repetitive code when defining multiple similar configurations.

Benefits of using dynamic blocks:

  • Reduces redundancy by allowing loops within resource definitions.
  • Useful for scenarios where the number of blocks depends on dynamic inputs, such as multiple route rules in a route table.
  • Makes Terraform code flexible and scalable for complex infrastructure.
  • When to Use Inline and Dynamic Blocks
  • Inline blocks should be used when there are multiple related configurations (like security rules or tags) that are logically grouped under a single resource.
  • Dynamic blocks are ideal when the number or type of nested blocks depends on variables or dynamic input data (e.g., dynamically creating route rules or firewall rules based on external data).

Example 1: Inline Blocks for Multiple Security Rules in a Security List

In Oracle Cloud Infrastructure (OCI), managing security lists involves adding multiple security rules. Instead of creating multiple resources, you can use inline blocks to define security rules directly within a single oci_core_security_list resource.

resource "oci_core_security_list" "cms_security_list" {

    compartment_id = var.compartment_id
    vcn_id         = var.vcn_id
    display_name   = "cms-security-list"

    # Ingress security rules
    ingress_security_rules {
        protocol = "6"
        source   = "0.0.0.0/0"
        tcp_options {
            max = 22
            min = 22
        }
        stateless = false
    }

    ingress_security_rules {
        protocol = "6"
        source   = "0.0.0.0/0"
        tcp_options {
            max = 80
            min = 80
        }
        stateless = false
    }

    # Egress security rule
    egress_security_rules {
        protocol  = "all"
        destination = "0.0.0.0/0"
        stateless = false
    }
}

In this example, two ingress rules are defined directly inside the oci_core_security_list resource as inline blocks, making the configuration concise.


Example 2: Inline Block for Defining Multiple Subnets in a VCN

Here’s another example where you can use inline blocks to define multiple subnets within a single VCN (Virtual Cloud Network):

resource "oci_core_vcn" "cms_vcn" {

    compartment_id = var.compartment_id
    cidr_block     = "10.0.0.0/16"
    display_name   = "cms-vcn"

    # Inline block to define multiple subnets
    subnet {
        cidr_block     = "10.0.1.0/24"
        display_name   = "subnet-1"
        availability_domain = "1"
    }

    subnet {
        cidr_block     = "10.0.2.0/24"
        display_name   = "subnet-2"
        availability_domain = "2"
    }
}

In this case, two subnets are defined within the same VCN resource.

Example 3: Dynamic Block for Route Rules in a Route Table

When defining route rules for an OCI route table, it’s common to have multiple route rules with similar configurations. Using a dynamic block, you can loop over a list of route details and create route rules dynamically.

variable "route_rules" {

    type = list(object({
        cidr_block   = string
        destination  = string
        route_target  = string
    }))
}

resource "oci_core_route_table" "cms_route_table" {
    compartment_id = var.compartment_id
    vcn_id        = var.vcn_id
    display_name   = "example-route-table"

    dynamic "route_rules" {
        for_each = var.route_rules
        content {
            cidr_block         = route_rules.value.cidr_block
            destination        = route_rules.value.destination
            network_entity_id  = route_rules.value.route_target
        }
    }
}

Here, the dynamic block loops over the route_rules variable to generate multiple route rules based on a list of objects, making the route table configuration much more flexible and scalable.

Example 4: Dynamic Block for Creating Multiple Compute Instances

Let’s say you want to create multiple compute instances based on a list of instance details. Dynamic blocks can help generate these instances dynamically without hardcoding each one.

variable "instances" {

    type = list(object({
        display_name = string
        shape        = string
        image_id     = string
    }))
}

resource "oci_core_instance" "cms_instance" {
    for_each = var.instances

    compartment_id      = var.compartment_id
    availability_domain  = var.availability_domain
    display_name         = each.value.display_name
    shape                = each.value.shape

    source_details {
        source_type = "image"
        image_id    = each.value.image_id
    }
}

In this example, the dynamic block automatically generates multiple compute instances based on a list of instance configurations, avoiding the need to write individual resource blocks.

Conclusion

Both inline blocks and dynamic blocks in Terraform offer flexibility and scalability in managing infrastructure. Inline blocks are great for grouping logically related configurations, while dynamic blocks are perfect for reducing redundancy when dealing with multiple similar resources. By incorporating these features into your Terraform scripts, you can write more efficient and scalable infrastructure code, making it easier to manage even the most complex OCI environments.

OCI Terraform Part 4 - Terraform State File Management

 Terraform State File Management with OCI

State files are a critical component in Terraform as they track the resources created and allow Terraform to manage infrastructure consistently. Proper state file management is essential for smooth infrastructure operations. This blog explores various types of state management: local, centralized, and OCI bucket.

1. Local State File Management

Steps:

By default, Terraform stores the state file locally in the working directory. For instance, when you run terraform apply, a file named terraform.tfstate is created locally.

Advantages:

  • Simple to set up; no extra configuration required.
  • Ideal for small, personal projects.

Disadvantages:

  • Not suitable for team environments—risk of conflicts.
  • Lack of backup in case of accidental deletion.
  • Manual handling required for versioning.

Code Example:


terraform { backend "local" { path = "terraform.tfstate" } }

2. Centralized State Management

Steps:

Utilize a remote backend for centralized state management, such as Terraform Cloud or another supported remote backend. Ensure all users and CI/CD pipelines have access to the remote state.

Advantages:

  • Collaborate across teams with the same source of truth.
  • Automatically locks the state file during operations to avoid conflicts.
  • Backup and recovery options are available.

Disadvantages:

  • Additional setup complexity.
  • May incur additional costs for hosted backends like Terraform Cloud.

Code Example:


terraform { backend "remote" { hostname = "terraform-host" organization = "cms-org" workspaces { name = "Dev" } } }

3. State Management in OCI Object Storage Bucket

Storing state in an OCI bucket is a good practice when managing larger infrastructures across multiple teams. You can leverage Object Storage for state file management and ensure collaboration, security, and disaster recovery.

Steps:

  1. Create a dedicated bucket in OCI Object Storage.

  2. Create a Pre-Authenticated Request for the bucket.

  3. Upload Existing State:

    curl -X PUT -H "Content-Type: text/plain" --data-binary "@Path_to_the_state_file" https://<Object_storage_uri>

  4. Configure the backend in Terraform to use OCI’s Object Storage.

  5. Define the required variables such as compartment OCID, bucket name, and authentication details (profile, auth tokens, or instance principals).

Advantages:

  • Automatically backed up in OCI.
  • Multi-region availability and data redundancy.
  • Suitable for OCI-specific infrastructure.

Disadvantages:

  • Requires setting up bucket policies for access control.
  • Slightly more complex than local state storage.

Code Example:


terraform { backend "http" { address = "<Object Storage uri>" update_method = "PUT" } }





Once you complete all the pre-requisites, execute terraform init. It will successfully configure the "http" backend. After the init, you can run terraform apply - all the changes will be migrated and saved to the http backend.



Versioning: If versioning is enabled, changes to the .tfstate file will be backed up, providing an additional layer of protection against data loss.

Conclusion

Managing state files effectively is crucial, especially in team environments. Remote state management offers a more secure, scalable, and redundant solution. OCI integration provides a robust option for those leveraging OCI infrastructure. The choice of state management approach depends on your project's size, team collaboration needs, and infrastructure requirements.


Pune's Largest Gaming Zone - Now in Wagholi

    


OCI Terraform Part 3 - Terraform: Loops, Modules, and Infrastructure Automation

Mastering Terraform: Leveraging Loops and Modules for Efficient Infrastructure Management

Terraform is a declarative language that doesn't have typical for-loops for iterating over a list of objects or creating repetitive resources. However, Terraform offers meta-parameters like count, for_each, and for expressions that achieve similar functionality.

Methods to Store Values

There are three types of methods to store values:

  • List → Ordered, indexed access, and allows duplicates.
  • Map → Unordered, key-based access, and unique key values.
  • Set → Unordered, no duplicates, easy to find the presence of the value.

Iterations: Loops

Count

The count parameter allows iteration over each resource provided in a list or module. The count.index helps in iterating over specific resources or looping through each resource.

Let's create three users with different names using a single resource block and count:

variable "usernames" {
  type = list(string)
  default = ["cms", "AM", "AC"]
}

resource "oci_identity_user" "user" {
  count = 3
  name = var.usernames[count.index]
  description = "OCI User"
}

output "username_name" {
  value = var.usernames[*]
}

This will create three users with the following output:

+ username_name = [
    + "cms",
    + "AM",
    + "AC",
]

for_each

The for_each loop allows iteration over lists and maps to create multiple copies of a resource or module. It supports only sets and maps on resources.

variable "usernames" {
  type = list(string)
  default = ["cms", "AM", "AC"]
}

resource "oci_identity_user" "user" {
  for_each = toset(var.usernames)
  name = each.value
  description = "OCI User"
}

output "username_name" {
  value = oci_identity_user.user
}

This code displays all the information related to the users created with the block of code. There are additional methods, such as conditional statements.

Using Modules to Create Infrastructure

Let's use the same concepts in modules to create our infrastructure. First, let's understand what Terraform modules are.

What Are Terraform Modules?

Terraform modules are essential building blocks that allow you to organize and reuse your infrastructure code. Instead of repeating the same code across different Terraform configurations, you can encapsulate it within a module and call that module wherever it's needed. This approach improves code maintainability, reduces errors, and enhances collaboration by creating standardized infrastructure components.

Modules help to keep your Terraform configurations DRY (Don't Repeat Yourself). For example, if you need to create multiple Virtual Cloud Networks (VCNs) or subnets with similar configurations, instead of duplicating the code, you can define the logic in a module and reuse it.

Why Are Modules Necessary?

  • Reusability: Modules allow you to reuse the same configuration across different environments, such as development, staging, and production.
  • Organization: By breaking down complex configurations into smaller, focused modules, you make your Terraform code easier to manage and understand.
  • Consistency: Using modules ensures that your infrastructure components are created consistently, reducing the likelihood of errors.
  • Scalability: As your infrastructure grows, modules help scale the management of resources by making it easier to add, modify, or remove components.

Example 1: Single VCN Creation Using a Module

Variable Definitions (variable.tf)

variable "compartment_id" {
  description = "Network compartment id"
  default = "<compartment-ocid>"
  type = string
}

variable "cidr_block" {
  description = "VCN CIDR block"
  type = list(string)
}

variable "display_name" {
  description = "Name of my VCN"
  type = list(string)
}

Explanation: In this block, we define variables that will be used in the VCN module. The compartment_id is the ID of the OCI compartment where the VCN will be created. The cidr_block variable is a list of CIDR blocks that the VCN will use, and the display_name is a list containing the name of the VCN.

Output Definitions (output.tf)

output "vcn_ids" {
  value = [for vcn in oci_core_vcn.vcn : vcn.id]
  description = "VCN ID of the VCN"
}

Explanation: This block outputs the ID of the VCN created by the module. The vcn_ids output is a list of IDs for all the VCNs created by this module.

Main Configuration (main.tf)

resource "oci_core_vcn" "vcn" {
  compartment_id = var.compartment_id
  for_each = {for idx, cidr in var.cidr_block : idx => cidr}
  cidr_block = each.value
  display_name = var.display_name[each.key]
}

Explanation: Here, we define the main logic for creating a VCN. The for_each loop iterates over the list of CIDR blocks, creating a VCN for each entry. The compartment_id is passed in from the variables, and the display_name is used to name the VCN.

Calling the Module in Main Configuration (vcn.tf)

module "vcn" {
  source = "../Dev/module_vcn"
  vcn_cidr = ["10.0.0.0/16"]
  display_name = ["cms_vcn1"]
}

Explanation: In this block, we call the VCN module from our main Terraform configuration. We specify the source of the module and provide the necessary inputs, such as the CIDR block and the display name. This call will create a single VCN based on the logic defined in the module.

Example 2: Creating Multiple Subnets in a Single VCN Using Modules

Variable Definitions (variable.tf)

variable "compartment_id" { 
 description = "subnet compartment id"
 default = "<compartment-ocid>"
 type = string
}

variable "cidr_block" {
  description = "Subnet CIDR block"
  type = list(string)
}

variable "vcn_ids" {
  description = "VCN ID of the subnet"
  type = list(string)
}

variable "display_name" {
  type = list(string)
  description = "Display name of the subnet"
}

variable "prohibit_public_ip_on_vnic" {
  description = "Whether VNICs in this subnet can have public IP addresses."
  type = bool
  default = false
}

Output Definitions (output.tf)

output "subnet_id" {
  value = [for subnet in oci_core_subnet.subnet : subnet.id]
  description = "Subnet ID of the Subnet"
}

Main Configuration (main.tf)

resource "oci_core_subnet" "subnet" {
  compartment_id = var.compartment_id
  for_each = {for idx, cidr in var.cidr_block : idx => cidr}
  vcn_id = var.vcn_ids[each.key]
  cidr_block = each.value
  prohibit_public_ip_on_vnic = var.prohibit_public_ip_on_vnic
  display_name = var.display_name[each.key]
}

Explanation: This block creates subnets within a VCN. The for_each parameter determines how many subnets to create based on the length of the cidr_block list. Each subnet is assigned a CIDR block, a VCN ID, a name, and a public/private designation based on the provided variables.

Calling the Module in Main Configuration (subnet.tf)

module "subnet" {
  source = "../module_subnet"
  vcn_ids = ["10.0.0.0/16"]
  display_name = ["cms_subnet1"]
  cidr_block = ["10.0.0.0/24"]
}

Explanation: In this block, we call the subnet module to create multiple subnets within a single VCN. The module uses the CIDR blocks, VCN IDs, display names, and public/private settings provided in the inputs to create each subnet.

Conclusion

Terraform's loops, if-statements, and modules offer powerful tools for managing cloud infrastructure efficiently. By mastering these concepts, one can create flexible, reusable configurations that scale with the needs.