I have an old post how to create three instances in OCI with modules using Terraform 0.11 but since 0.12 came out I’ve been wanting to rewrite it to show what we can achieve with new features introduced in TF 0.12.

In this post I will once again create those instances but now the modules used are dynamic so they will create as many resources I need based on variables I send.

Terraform 0.12 improvements

You can download the full source code with modules from https://github.com/svilmune/tf-012-create-three-instances-demo. As you can see the root folder contains the files main.tf, variables.tf and outputs.tf. In the main.tf I reference always the module by using module directory which has their own .tf files inside. If I would want I could use also the git link to reference the module but in this example I’ll use local folders.

module "CreateCompartment" {
  source                  = "./module-compartment"
  tenancy_ocid            = var.tenancy_ocid
  compartment_name        = var.compartment_name
  compartment_description = var.compartment_description
}

First visible change with Terraform 0.12 is that we no longer need to set brackets around variables, makes writing code much easier! Also if I take a peak in the Compartment module I can see change in the outputs.tf.

output "compartment" {
  value = oci_identity_compartment.CreateCompartment
}

I can now return all data values from created resource by above code, no longer do I need to return values one by one and can use it easily by defining value of output in the code and picking the correct variable. OCI provider documentation here has these defined.

 compartment_ocid                   = module.CreateCompartment.compartment.id

Route Table and Security List modules

Few notable changes also with route table and security list modules. Sometimes route rules require us to define multiple values, earlier you couldn’t really easily do this within a module but now there are few possibilities. The way I’ve solved it in this example is by using a list and then combining it to a map in the module. From the route table:

module "CreateRouteRule" {

  source                             = "./module-routetable"
  tenancy_ocid                       = var.tenancy_ocid
  compartment_ocid                   = module.CreateCompartment.compartment.id
  vcn_id                             = module.CreateVCN.vcn.id
  network_id                         = [module.CreateIGW.internetgateway.id]
  route_table_display_name           = var.route_table_display_name
  route_table_route_rules_cidr_block = [var.igw_route_table_rules_cidr_block]

}

I’m sending now only one variable to route table module inside the list but could modify that to contain multiple. Let’s take a peak inside the route table module itself!

resource "oci_core_route_table" "CreateRouteTable" {

  compartment_id = var.compartment_ocid
  vcn_id         = var.vcn_id
  display_name   = var.route_table_display_name

  dynamic "route_rules" {

    for_each = zipmap(var.network_id, var.route_table_route_rules_cidr_block)

    content {
      network_entity_id = route_rules.key
      destination       = route_rules.value

    }
  }
}

Very simple with two variables! I’m defining in the route_rules part that the resource creation is dynamic and I create a key/value pair to a new map from the lists I’m sending to the module using zipmap. After that I’m assigning each key/value pair to resource values. If there would be bigger map the route rules would be updated accordingly.

In the security list module I’m doing similar thing with a slightly different approach. In variables.tf I’ve defined map with ingress_ports:

variable "ingress_ports" {
  description = "all the ports for security list"
  default = [
    { minport     = 22
      maxport     = 22
      source_cidr = "0.0.0.0/0"
    },
    { minport     = 0
      maxport     = 0
      source_cidr = "172.27.0.0/16"
  }]
}

If I would need more ports I would now just add new item in the map without need to edit the module itself at all, this has been one of the biggest things which helped us reducing amount of work with Terraform. As you might have noticed Security Lists get updated every now and then..

In the module I’m using the variable like this.

dynamic "ingress_security_rules" {
    iterator = port
    for_each = [for y in var.ingress_ports : {
      minport     = y.minport
      maxport     = y.maxport
      source_cidr = y.source_cidr
    }]
    content {
      protocol  = var.ingress_protocol
      source    = port.value.source_cidr
      stateless = var.ingress_stateless
      tcp_options {
        // These values correspond to the destination port range.
        min = port.value.minport
        max = port.value.maxport
      }
    }
  }

Lot of similarities to route table but as we now have three items in the map we can’t use simply key+value but instead have multiple key/value pairs. I’ve simply assigned iterator and go through the map with for_each and in the content section notice how the value is defined with port.value.value_name always. Now regardless of amount of items we get all done in one loop. For example one of our lists has already 30 different ports which are easily managed through similar module.

Creating those instances

We still need to look on the creation of instances. With Terraform 0.11 we used count variable to create multiple resources but now we can again utilize for_each, only this time we use it for the “whole” resource.

I’ve created variable instance_variables which has following:

variable "instance_variables" {
  description = "Map instance name to hostname"
  default = {
    "ForEach1" = "fe-1"
    "ForEach2" = "fe-2"
    "ForEach3" = "fe-3"
  }
}

I’m using this key/value pair for display name and hostname label when creating the instance. Perhaps you would need to define private ip’s so this is one value also which could be added etc. In the instance module I can just simply define to loop this variable through using for_each and assign values where needed.

resource "oci_core_instance" "CreateInstance" {
  for_each            = var.instance_variables
  availability_domain = var.instance_availability_domain
  compartment_id      = var.compartment_id
  shape               = var.shape_id
  source_details {
    source_id   = var.image_id
    source_type = "image"
  }

  create_vnic_details {
    subnet_id              = var.subnet_id
    display_name           = each.key
    hostname_label         = each.value
    skip_source_dest_check = var.instance_create_vnic_details_skip_source_dest_check
    assign_public_ip       = var.assign_public_ip
  }

Running Terraform

Now that all the new features have been gone through I’m ready to run Terraform and create these resources. I’ll start by running terraform init which will initialize modules and provider.

terraform init
Initializing modules...

Initializing the backend...

Initializing provider plugins...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.oci: version = "~> 3.39"

Terraform has been successfully initialized!

Once this has been done I will run terraform plan to see what resources will get created. I can see each of my three instances being in the plan with specific names which I assigned to them. Since I’m happy with the plan I will run terraform apply.

While running apply I can observe all three instances are being created in parallel by using the module:

module.CreateInstances.oci_core_instance.CreateInstance["ForEach2"]: Creating...
module.CreateInstances.oci_core_instance.CreateInstance["ForEach3"]: Creating...
module.CreateInstances.oci_core_instance.CreateInstance["ForEach1"]: Creating...

After bit over minute I have the instances running by using new Terraform 0.12 features and I produce the output of all instance ip’s in the end. See also how you can use for loop in output with Terraform 0.12.

output "instance_private_and_public_ips" {
  value = {
    for instance in module.CreateInstances.instances:
    instance.private_ip => instance.public_ip
  }
}
Apply complete! Resources: 9 added, 0 changed, 0 destroyed.

Outputs:

instance_private_and_public_ips = {
  "172.27.0.2" = "130.61.82.84"
  "172.27.0.3" = "130.61.114.168"
  "172.27.0.4" = "130.61.38.163"
}

Summary

In my opinion getting Terraform 0.12 available has been a huge factor on our goal to write code which can be reused and reduce time on writing Terraform. There are many new features and I’ve only displayed a few but perhaps this give you an idea what can be done with it. At this point in time OCI Resource Manager doesn’t yet support Terraform 0.12, once it does I will update git repository to contain also version which can be run in Resource Manager.

Modules and code is freely used, just let me know if you find any bugs or have any major improvements, it’s always great to learn new things!

2 thoughts on “Create three instances with Terraform 0.12 and dynamic modules in Oracle Cloud Infrastructure”

Leave a Reply

Your email address will not be published. Required fields are marked *