Skip to content

Instantly share code, notes, and snippets.

@kmcquade
Forked from tuannvm/0.12.tf
Created May 27, 2019
Embed
What would you like to do?
#terraform #hashicorp #cheatsheet #0.12
# first class expresssion
variable "ami" {}
resource "aws_instance" "example" {
ami = var.ami
}
###
# list & map
resource "aws_instance" "example" {
vpc_security_group_ids = var.security_group_id != "" ? [var.security_group_id] : []
}
###
# In 0.12, HCL resolves this by making an explicit distinction between attributes and blocks. Attributes are single-assigned values using the assignment operator =. Blocks are repeated maps of key/value configuration pairs
resource "aws_security_group" "example" {
# "ingress" is parsed as a block because there is no equals sign
ingress {
# ..
}
# "value" is parsed as a attribute as it has equal sign
value = {
# ..
}
}
###
# For Expressions for List and Map Transformations
variable "subnet_numbers" {
description = "List of 8-bit numbers of subnets of base_cidr_block that should be granted access."
default = [1, 2, 3]
}
resource "aws_security_group" "example" {
# For each number in subnet_numbers, extend the CIDR prefix of the
# requested VPC to produce a subnet CIDR prefix.
# For the default value of subnet_numbers above and a VPC CIDR prefix
# of 10.1.0.0/16, this would produce:
# ["10.1.1.0/24", "10.1.2.0/24", "10.1.3.0/24"]
cidr_blocks = [
for num in var.subnet_numbers:
cidrsubnet(data.aws_vpc.example.cidr_block, 8, num)
]
}
}
###
# how to produce a map
output "instance_private_ip_addresses" {
# Result is a map from instance id to private IP address, such as:
# {"i-1234" = "192.168.1.2", "i-5678" = "192.168.1.5"}
value = {
for instance in aws_instance.example:
instance.id => instance.private_ip
# if statement
if instance.associate_public_ip_address
}
}
output "instances_by_availability_zone" {
# Result is a map from availability zone to instance ids, such as:
# {"us-east-1a": ["i-1234", "i-5678"]}
value = {
for instance in aws_instance.example:
# mind the ...
instance.availability_zone => instance.id...
}
}
###
# Dynamic Nested Blocks
variable "subnets" {
default = [
{
name = "a"
number = 1
},
{
name = "b"
number = 2
},
{
name = "c"
number = 3
},
]
}
locals {
base_cidr_block = "10.0.0.0/16"
}
resource "azurerm_virtual_network" "example" {
name = "example-network"
resource_group_name = azurerm_resource_group.test.name
address_space = [local.base_cidr_block]
location = "West US"
dynamic "subnet" {
# [{"name": "a", "prefix": "10.1.0.0/24"},
# {"name": "b", "prefix": "10.2.0.0/24"},
# {"name": "c", "prefix": "10.3.0.0/24"}]
for_each = [for s in subnets: {
name = s.name
prefix = cidrsubnet(local.base_cidr_block, 4, s.number)
}]
content {
name = subnet.name
address_prefix = subnet.prefix
}
}
}
###
# splat operator
# before only work with resource with "count", now work for any list value
output "instance_ip_addrs" {
value = google_compute_instance.example.network_interface.*.address
}
###
# full splat operator
# instead of network_interface.*.
output "instance_net_ip_addrs" {
value = google_compute_instance.example.network_interface[*].access_config[0].assigned_nat_ip
}
###
# Conditional Operator Improvements
locals {
first_id = length(azurerm_virtual_machine.example) > 0 ? azurerm_virtual_machine.example[0].id : ""
buckets = (var.env == "dev" ? [var.build_bucket, var.qa_bucket] : [var.prod_bucket])
}
###
# Conditionally Omitted Arguments
variable "override_private_ip" {
type = string
default = null
}
resource "aws_instance" "example" {
# By using the new null value, the aws_instance resource can dynamically assign a private IP address when no override is provided,
# but set an explicit IP address if the calling module provides a value for override_private_ip.
private_ip = var.override_private_ip
}
###
# Complex Values
# complex objects can now be passed to child modules as inputs, and returned to parent modules as outputs
# access with `for network in var.networks`
module "subnets" {
source = "./subnets"
parent_vpc_id = "vpc-abcd1234"
networks = {
production_a = {
network_number = 1
availability_zone = "us-east-1a"
}
production_b = {
network_number = 2
availability_zone = "us-east-1b"
}
staging_a = {
network_number = 1
availability_zone = "us-east-1a"
}
}
}
###
# Rich types
variable "networks" {
# custom type
# no quote
type = map(object({
network_number = number
availability_zone = string
tags = map(string)
}))
}
###
# Resources and Modules as Values
# return the whole object
output "vpc" {
value = aws_vpc.example
}
###
# New Template syntax
locals {
lb_config = <<EOT
%{ for instance in opc_compute_instance.example ~}
server ${instance.label} ${instance.ip_address}:8080
%{ endfor }
EOT
}
###
# JSON comment
{
"variable": {
"example": {
"#": "This property is ignored",
"default": "foo"
}
}
}

Terraform provider

Source

├── main.go
├── provider.go
├── resource_server.go

provider.go

package main

import (
    "github.com/hashicorp/terraform/helper/schema"
)

func Provider() *schema.Provider {
  return &schema.Provider{
    ResourcesMap: map[string]*schema.Resource{
      "example_server": resourceServer(),
    },
  }
}

main.go

package main

import (
    "github.com/hashicorp/terraform/plugin"
    "github.com/hashicorp/terraform/terraform"
)

func main() {
  plugin.Serve(&plugin.ServeOpts{
    ProviderFunc: func() terraform.ResourceProvider {
      return Provider()
    },
  })
}

As a general convention, Terraform providers put each resource in their own file, named after the resource, prefixed with resource_

resource_server.go

package main

import (
    "github.com/hashicorp/terraform/helper/schema"
)

func resourceServer() *schema.Resource {
  return &schema.Resource{
    Create: resourceServerCreate,
    Read:   resourceServerRead,
    Update: resourceServerUpdate,
    Delete: resourceServerDelete,

    Schema: map[string]*schema.Schema{
      "address": &schema.Schema{
        Type:     schema.TypeString,
        Required: true,
      },
    },
  }
}

func resourceServerCreate(d *schema.ResourceData, m interface{}) error {
  address := d.Get("address").(string)
  d.SetId(address)
  return nil
}

func resourceServerRead(d *schema.ResourceData, m interface{}) error {
    return nil
}

func resourceServerUpdate(d *schema.ResourceData, m interface{}) error {
    return nil
}

func resourceServerDelete(d *schema.ResourceData, m interface{}) error {
    return nil
}

main.tf

resource "example_server" "my-server" {
  address = "1.2.3.4"
}

Cheatsheet

Map Loop

For e.g, we have the following values:

vpc_peering_list = {
  k8s-production = "vpc-111111"
  k8s-service = "vpc-222222"
  k8s-zdata = "vpc-333333"
}

IMPORTANT Map follows alphanumeric sequence.

We can loop through this map with:

resource "aws_vpc_peering_connection" "vpc_peering" {
  count = "${length(keys(var.vpc_peering_list))}"
  vpc_id = "${element(values(var.vpc_peering_list), count.index)}"
  peer_vpc_id   = "${aws_vpc.vpc.id}"

  tags {
    Name = "${element(keys(var.vpc_peering_list), count.index)}-vpc-peering-${var.service_name}"
  }
}

List Loop

variable "bucket_name" {
  description = "Bucket Name"
  type        = "list"
  default     = ["develop", "staging", "prod"]
}

resource "aws_route53_record" "www" {
  count = "${length(var.bucket_name)}"
  name  = "${var.bucket_name[count.index]}"

Default values for external module output

https://github.com/hashicorp/terraform/issues/11566#issuecomment-289417805

https://github.com/coreos/tectonic-installer/blob/master/modules/aws/vpc/vpc.tf#L24

External module should return output as a list

id = "${var.external_vpc_id == "" ? join(" ", aws_vpc.new_vpc.*.id) : var.external_vpc_id }"

Default value when using conditional check. See this

resource "example" "conditional" {
  count = "${var.enabled ? 1 : 0}"
}

resource "example" "dependent" {
  argument = "${join("", example.conditional.*.attribute)}"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment