Presentation

Heat and Terraform are IAC projects (Infrastructure as Code).
According to the documentation :

Heat is the main project in the OpenStack Orchestration program. It implements an orchestration engine to launch multiple composite cloud applications based on templates in the form of text files that can be treated like code.

On the other side,

Terraform (Hashicorp product) is agnostic and provides multiple integration (AWS, Google Cloud, Azure and of course OpenStack) :HashiCorp Terraform enables you to safely and predictably create, change, and improve infrastructure. It is an open source tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned. Note : there is multiple way to write your template and maybe you'll not see the same syntax in the documentation (inline, etc).

Set your params

In Heat

parameters:
  flavor_name:
    type: string
    default: m1.small
  image_name:
    type: string
    default: Centos7
  key_name:
    type: string
    default: mykey
  public_net:
    type: string
    default: public

# shortened version
parameters:
  flavor_name: {default: m1.small, type: string}
  image_name: {default: Centos7, type: string}
  key_name: {default: mykey, type: string}
  public_net: {default: external, type: string}

In Terraform

variable "flavor_name" {
  default = "m1.small"
}

variable "image_name" {
  default = "Centos7"
}

variable "key_name" {
  default = "mykey"
}

variable "public_net" {
  default = "public"
}

Network examples

Create a subnet

In this example we will create two resources, a network called internal_network and its associated subnet called internal_subnet. The subnet will be 172.16.10.0/24 and the DHCP allocation pool 172.16.10.10 to 172.16.10.250.

In Heat

  network-1:
    type: OS::Neutron::Net
    properties:
      admin_state_up: true
      name: internal_network
  network-1-subnet-1:
    type: OS::Neutron::Subnet
    properties:
      network: { get_resource: network-1 }
      name: internal_subnet
      ip_version: 4
      cidr: 172.16.10.0/24
      allocation_pools:
      - {end: 172.16.10.10, start: 172.16.10.250}
      enable_dhcp: true
      dns_nameservers: [{ 1.1.1.1 }, { 8.8.8.8 }]

In Terraform

resource "openstack_networking_network_v2" "network-1" {
  name           = "internal_network"
  admin_state_up = "true"
}

resource "openstack_networking_subnet_v2" "network-1-subnet-1" {
  name       = "internal_subnet"
  network_id = "${openstack_networking_network_v2.network-1.id}"
  cidr       = "172.16.10.0/24"
  ip_version = 4
  dns_nameservers = ["1.1.1.1","8.8.8.8"]
  allocation_pools {
      start = "172.16.10..10"
      end = "172.16.10.250"
  }
}

Create a Security Group

For this example we will create a security group resource called sec_group that will allow connection on ports 22 (SSH), 80 (http), 443 (https) and ICMP protocol (ping).

In Heat

  sec-group-1:
    type: OS::Neutron::SecurityGroup
    properties:
      name: sec_group
      rules:
      - {port_range_max: 22, port_range_min: 22, protocol: tcp, remote_ip_prefix: 0.0.0.0/0}
      - {port_range_max: 80, port_range_min: 80, protocol: tcp, remote_ip_prefix: 0.0.0.0/0}
      - {port_range_max: 443, port_range_min: 443, protocol: tcp, remote_ip_prefix: 0.0.0.0/0}
      - {protocol: icmp, remote_ip_prefix: 0.0.0.0/0}

In Terraform

resource "openstack_compute_secgroup_v2" "sec-group-1" {
  name        = "sec_group"

  rule {
    from_port   = 22
    to_port     = 22
    ip_protocol = "tcp"
    cidr        = "0.0.0.0/0"
  }
  rule {
    from_port   = 80
    to_port     = 80
    ip_protocol = "tcp"
    cidr        = "0.0.0.0/0"
  }
  rule {
    from_port   = 443
    to_port     = 443
    ip_protocol = "tcp"
    cidr        = "0.0.0.0/0"
  }
  rule {
    from_port = -1
    to_port = -1
    ip_protocol = "icmp"
    cidr = "0.0.0.0/0"
  }
}

Request a floating IP

In Heat

  fip-1:
    type: OS::Nova::FloatingIP
    properties:
      pool: public

  fip-1-association:
    type: OS::Nova::FloatingIPAssociation
    properties:
      floating_ip: { get_resource: fip-1 }
      server_id: { get_resource: instance-1 }

In Terraform

resource "openstack_networking_floatingip_v2" "fip-1" {
  pool = "public"
}

resource "openstack_compute_floatingip_associate_v2" "fip-1-association" {
  floating_ip = "${openstack_networking_floatingip_v2.fip-1.address}"
  instance_id = "${openstack_compute_instance_v2.instance-1.id}"
}

Create a router

In Heat

  router-1:
    type: OS::Neutron::Router
    properties:
      name: router
      external_gateway_info:
        network: public
  router-1-interface-1:
    type: OS::Neutron::RouterInterface
    properties:
      router_id: { get_resource: router-1 }
      subnet_id: { get_resource: network-1-subnet-1 }

In Terraform

resource "openstack_networking_router_v2" "router-1" {
  name                = "router"
  admin_state_up      = true
  external_network_id = "$PUBLIC_ID"
}
resource "openstack_networking_router_interface_v2" "router-1-interface-1" {
  router_id = "${openstack_networking_router_v2.router-1.id}"
  subnet_id = "${openstack_networking_subnet_v2.network-1-subnet-1.id}"
}

Compute examples

In this example we will create a compute resource that will launch a machine named cirros_instance, on the previously created internal_network network and the security group sec_group, with the image cirros and the flavor m1.small, with your key yourkey which in this example is already present on your OpenStack.

We will launch an user_data script named setup.sh that will initialize the instance in the
way you want (install and configure a web server and so on) :

#!/bin/bash

# some configuration...

Create an instance

In Heat

  instance-1:
    type: OS::Nova::Server
    properties:
      name = "cirros_instance"
      key_name: { get_param: key_name }
      image: { get_param: image_name }
      flavor: { get_param: flavor_name }
      networks:
        - network : { get_param : network-1 }
      security_groups:
        - { get_resource: sec-group-1 }
      user_data_format: RAW
      user_data:
        get_file: setup.sh

In Terraform

resource "openstack_compute_instance_v2" "cirros_instance" {
  name = "cirros_instance"
  image_name = "${var.image_name}"
  flavor_name = "${var.flavor_name}"
  key_pair = "${var.key_name}"
  security_groups = ["${openstack_compute_secgroup_v2.sec_group.name}"]
  network {
    name = "${openstack_networking_network_v2.internal_network.name}"
  }
  user_data = "${file("setup.sh")}"
}

Create a volume

Add some parameters

In Heat

parameters:
  availability_zone:
    type: string
    default: nova
  volume_size:
    type: number
    default: 10

In Terraform

variable "availability_zone" {
  default = "nova"
}

variable "volume_size" {
  default = "10"
}

Create a volume

In Heat

resources:
  cinder_volume:
    type: OS::Cinder::Volume
    properties:
      size: { get_param: volume_size }
      availability_zone: { get_param: availability_zone }

In Terraform

resource "openstack_blockstorage_volume_v3" "volume-1" {
  region      = "RegionOne"
  name        = "volume_1"
  size        = 10
}

Now I hope you have a better idea of what you can do with Heat and Terraform and how to do it :)

Feel free to correct me if you see any typo or if something seems wrong to you.