Slide 1

Slide 1 text

ringods @ Terraform 0.12 What does it solve?

Slide 2

Slide 2 text

ringods @ Ringo De Smet Freelance Consultant, Obsessed by flow in software delivery

Slide 3

Slide 3 text

ringods @ Terraform 0.12 HCL: Hashicorp Configuration Language • Unchanged over the past 4 years • Limitations in the language • Inconsistencies in the language • Result: inventive but unintuitive workarounds

Slide 4

Slide 4 text

ringods @ Terraform 0.12: What is announced? • First-class expressions • For expressions • Dynamic blocks • Generalized “Splat” operator • Conditional improvements • Richt types in module inputs and outputs • Template syntax • Reliable JSON Syntax (not covered) • References as first-class values (not covered)

Slide 5

Slide 5 text

ringods @ Terraform 0.12: Intent of the changes • In 0.11 and earlier, a lot happens in the interpolation language • Focus shifts to expressions, outside of the interpolation scope

Slide 6

Slide 6 text

ringods @ # Configuration for Terraform 0.11 and earlier variable "ami" {} variable "instance_type" {} variable "vpc_security_group_ids" { type = "list" } resource "aws_instance" "example" { ami = "${var.ami}" instance_type = "${var.instance_type}" vpc_security_group_ids = "${var.vpc_security_group_ids}" } First-class expressions

Slide 7

Slide 7 text

ringods @ # Configuration for Terraform 0.12 variable "ami" {} variable "instance_type" {} variable "vpc_security_group_ids" { type = "list" } resource "aws_instance" "example" { ami = var.ami instance_type = var.instance_type vpc_security_group_ids = var.vpc_security_group_ids } First-class expressions

Slide 8

Slide 8 text

ringods @ # Configuration for Terraform 0.11 and earlier resource "aws_instance" "example" { # … # First version vpc_security_group_ids = ["${var.security_group_1}", "${var.security_group_2}"] # Second version vpc_security_group_ids = "${var.security_group_id != "" ? [var.security_group_id] : []}" # Third version vpc_security_group_ids = “${var.security_group_id != "" ? list(var.security_group_id) : list()}" } First-class expressions

Slide 9

Slide 9 text

ringods @ # Configuration for Terraform 0.12 resource "aws_instance" "example" { # … vpc_security_group_ids = var.security_group_id != "" ? [var.security_group_id] : [] } First-class expressions

Slide 10

Slide 10 text

ringods @ resource "aws_security_group" "example" { name = "friendly_subnets" description = "Allows access from friendly subnets" vpc_id = var.vpc_id ingress { from_port = 0 to_port = 0 protocol = -1 cidr_blocks = [ for num in var.subnet_numbers: cidrsubnet(data.aws_vpc.example.cidr_block, 8, num) ] } } output "instance_private_ip_addresses" { value = { for instance in aws_instance.example: instance.id => instance.private_ip } } For: create lists or maps

Slide 11

Slide 11 text

ringods @ output "instance_public_ip_addresses" { value = { for instance in aws_instance.example: instance.id => instance.public if instance.associate_public_ip_address } } output "instances_by_availability_zone" { value = { for instance in aws_instance.example: instance.availability_zone => instance.id... } } For: optional if and grouping {"us-east-1a": [“i-1234", “i-5678"]}

Slide 12

Slide 12 text

ringods @ # Configuration for Terraform 0.11 and earlier resource "aws_autoscaling_group" "example" { tag { key = "Component" value = "user-service" propagate_at_launch = true } tag { key = "Environment" value = "production" propagate_at_launch = true } } Dynamic Nested Blocks

Slide 13

Slide 13 text

ringods @ # Configuration for Terraform 0.12 locals { standard_tags = { Component = "user-service" Environment = "production" } } resource "aws_autoscaling_group" "example" { dynamic "tag" { for_each = local.standard_tags content { key = tag.key value = tag.value propagate_at_launch = true } } } Dynamic Nested Blocks

Slide 14

Slide 14 text

ringods @ 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" { for_each = [for s in var.subnets: { name = s.name prefix = cidrsubnet(local.base_cidr_block, 4, s.number) }] content { name = subnet.name address_prefix = subnet.prefix } } } Dynamic Nested Blocks

Slide 15

Slide 15 text

ringods @ variable "subnet_numbers" { default = { "eu-west-1a" = 1 "eu-west-1b" = 2 "eu-west-1c" = 3 } } resource "aws_vpc" "example" { # ... } resource "aws_subnet" "example" { for_each = var.subnet_numbers vpc_id = aws_vpc.example.id availability_zone = each.key cidr_block = cidrsubnet(aws_vpc.example.cidr_block, 8, each.value) } FUTURE: resource for_each Will come post TF 0.12! Also: count in module blocks!

Slide 16

Slide 16 text

ringods @ # Configuration for Terraform 0.11 and earlier output "instance_names" { value = google_compute_instance.main.*.name } # Configuration for Terraform 0.12 output "instance_ip_addrs" { value = google_compute_instance.example.network_interface.*.address } Splat Operator

Slide 17

Slide 17 text

ringods @ # Configuration for Terraform 0.11 and earlier output "instance_names" { value = google_compute_instance.main.*.name[0] } # Configuration for Terraform 0.12 output "instance_ip_addrs" { value = google_compute_instance.example.network_interface[*].access_config[ 0].assigned_nat_ip } Full Splat Operator

Slide 18

Slide 18 text

ringods @ Conditional operator • The operator: … ? … : … • In 0.11: • limited to primitive types • both value expressions where evaluated • In 0.12, both of these restrictions are no more

Slide 19

Slide 19 text

ringods @ # Configuration for Terraform 0.12 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]) } Conditional Operator

Slide 20

Slide 20 text

ringods @ # Configuration for Terraform 0.12 variable "override_private_ip" { type = string default = null } resource "aws_instance" "example" { # ... (other aws_instance arguments) ... private_ip = var.override_private_ip } Conditionally Omitted Arguments

Slide 21

Slide 21 text

ringods @ # Configuration for Terraform 0.12 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" } } } Complex values

Slide 22

Slide 22 text

ringods @ # Configuration for Terraform 0.12 variable "environment_name" { type = string } variable "networks" { type = map(object({ network_number = number availability_zone = string tags = map(string) })) } Rich Types

Slide 23

Slide 23 text

ringods @ # Configuration for Terraform 0.11 output “vpc_id" { value = aws_vpc.example.id } # Configuration for Terraform 0.12 output "vpc" { value = aws_vpc.example } Resources and Modules as values

Slide 24

Slide 24 text

ringods @ # Configuration for Terraform 0.12 locals { lb_config = <

Slide 25

Slide 25 text

ringods @

Slide 26

Slide 26 text

ringods @ We need you! • More people in the core organising team • More presenters • More topics Contact me via Meetup or Twitter