1 of 58

What's new in Terraform 0.12

Erik R. Rygg

Enterprise Architect

erik@hashicorp.com | @errygg

1

© 2019 HashiCorp

2 of 58

Terraform

Write, Plan, and Create Infrastructure as Code

2

© 2019 HashiCorp

3 of 58

Infrastructure as Code

  • Provide a codified workflow to create infrastructure
  • Expose a workflow for managing updates to existing infrastructure
  • Integrate with application code workflows (Git, SCM, Code Review)
  • Provide modular, sharable components for separation of concerns

Infrastructure as Code

3

© 2019 HashiCorp

4 of 58

Configuration Syntax

  • Configurations are written in HashiCorp Configuration Language (HCL)
  • Designed to strike a balance between human-readable and machine-parsable
  • Terraform can also read syntax in JSON, but we recommend using HCL

Infrastructure as Code

4

© 2019 HashiCorp

5 of 58

State

  • Terraform stores the state of your managed infrastructure from the last time Terraform was run
  • Terraform uses this state to create plans and make changes to your infrastructure
  • It is critical that this state is maintained appropriately so future runs operate as expected

Infrastructure as Code

5

© 2019 HashiCorp

6 of 58

Terraform 0.11

`random_pet` Simple Example

6

© 2019 HashiCorp

7 of 58

variable "count" {� default = 1�}��variable "default_prefix" {� default = "linus"�}��resource "random_pet" "my_pet" {� count = "${var.count}"� prefix = "${var.default_prefix}"�}

Terraform 0.11 Examples

Terraform

0.11

main.tf

7

CODE EDITOR

© 2019 HashiCorp

8 of 58

terraform apply��random_pet.my_pet: Creating...� length: "" => "2"� prefix: "" => "linus"� separator: "" => "-"�random_pet.my_pet: Creation complete after 0s (ID: linus-excited-goldfish)��Apply complete! Resources: 1 added, 0 changed, 0 destroyed.��Outputs:��pet_names = [� Pet: linus-excited-goldfish�]�

Terraform 0.11 Examples

Terraform

0.11

8

TERMINAL

© 2019 HashiCorp

9 of 58

Terraform 0.11

`random_pet` Example - a bit more complicated

9

© 2019 HashiCorp

10 of 58

Terraform 0.11

main.tf

Terraform 0.11 Examples

variable "count" { default = 1 }�variable "default_prefix" { default = "linus" }��variable "zoo_enabled" {� default = "0"�}��variable "prefix_list" {� default = []�}��resource "random_pet" "my_pet" {� count = "${var.count}"� prefix = "${var.zoo_enabled == "0" ?

var.default_prefix :

element(var.prefix_list, count.index)}"�}��

10

CODE EDITOR

© 2019 HashiCorp

11 of 58

Terraform 0.11

terraform.tfvars

Terraform 0.11 Examples

zoo_enabled = "1"�prefix_list = [ "linus", "cheetarah", "li-shou"]�count = 3

11

CODE EDITOR

© 2019 HashiCorp

12 of 58

Terraform 0.11

Terraform 0.11 Examples

random_pet.my_pet[1]: Creating...� length: "" => "2"� prefix: "" => "cheetarah"� separator: "" => "-"�[…] ��Apply complete! Resources: 3 added, 0 changed, 0 destroyed.��Outputs:��pet_names = [� Pet: linus-special-goblin,� Pet: cheetarah-loyal-drake,� Pet: li-shou-witty-turtle�]�

12

TERMINAL

© 2019 HashiCorp

13 of 58

Terraform 0.11

Terraform 0.11 Examples

$ terraform apply��Error: random_pet.my_pet: 1 error(s) occurred:��* random_pet.my_pet: element: element() may not be used with an empty list in:��${var.zoo_enabled == "0" ? var.default_prefix : element(var.prefix_list, count.index)}

13

TERMINAL

© 2019 HashiCorp

14 of 58

The hack

14

© 2019 HashiCorp

15 of 58

Terraform 0.11

terraform.tfvars

Terraform 0.11 Examples

variable "count" { default = 1 }�variable "default_prefix" { default = "linus" }��variable "zoo_enabled" {� default = "0"�}��variable "prefix_list" {� default = []�}��resource "random_pet" "my_pet" {� count = "${var.count}"� prefix = "${var.zoo_enabled == "0" ?

var.default_prefix :

element(concat(var.prefix_list, list("")),

count.index)}"�}�

15

CODE EDITOR

© 2019 HashiCorp

16 of 58

Terraform 0.11

Terraform 0.11 Examples

$ terraform apply��random_pet.my_pet: Creating...� length: "" => "2"� prefix: "" => "linus"� separator: "" => "-"�random_pet.my_pet: Creation complete after 0s (ID: linus-driving-giraffe)��Apply complete! Resources: 1 added, 0 changed, 0 destroyed.��Outputs:��pet_names = [� Pet: linus-driving-giraffe�]

16

TERMINAL

© 2019 HashiCorp

17 of 58

Terraform 0.12

17

© 2019 HashiCorp

18 of 58

Terraform 0.11 & Earlier

HCL - HashiCorp Configuration Language

Terraform 0.12

variable "foo" { � default = "bar"�}�

18

CODE EDITOR

© 2019 HashiCorp

19 of 58

Terraform 0.11 & Earlier

HIL - HashiCorp Interpolation Language

Terraform 0.12

foo = "hello ${var.world}"��"${format(“Hello %s”, var.world)}"

19

CODE EDITOR

© 2019 HashiCorp

20 of 58

Terraform Configuration Language - Terraform v0.12

Terraform 0.12

HCL2

  • Merges HCL and HIL
  • Introduces a robust type system
  • Addresses large list of enhancements and feature requests

20

© 2019 HashiCorp

21 of 58

So many improvements...

Terraform 0.12

21

  • First-class expressions. Prior to 0.12, expressions had to be wrapped in interpolation sequences with double quotes, such as "${var.foo}". With 0.12, expressions are a native part of the language and can be used directly. Example: ami = var.ami[1]
  • For expressions. A for expression is available for iterating and filtering lists and map values. This expression always can be used anywhere a list or map is expected.
  • Dynamic blocks. Child blocks such as rule in aws_security_group can now be dynamically generated based on lists/maps and support iteration.
  • Generalized "Splat" Operator. The special resource.*.field syntax used to only work for resources with countset. This is now a generalized operator that works for any list value.
  • Conditional improvements. The conditional operator ... ? ... : ... now supports any value type and lazily evaluates results, as those familiar with this operator in other languages would expect. Also, the special value nullcan now be assigned to any field to represent the absence of a value. This causes Terraform to omit the field from upstream API calls, which is important in some cases for triggering certain default behaviors.
  • Rich types in module inputs and outputs. Terraform has supported basic lists and maps as inputs/outputs since Terraform 0.7, but elements were limited to only simple values. Terraform 0.12 allows arbitrarily complex lists and maps for any inputs and outputs, including with modules.
  • Template syntax. Within string values, a new template syntax can be used for looping without complex nested interpolation. Example: %{ for instance in aws_instance.example ~}server ${instance.id}%{ endfor }.
  • Reliable JSON syntax. Terraform 0.12 HCL configuration has an exact 1:1 mapping to and from JSON.
  • References as first-class values. References to resources and modules for fields such as depends_on used to be arbitrary strings. In Terraform 0.12, the resource identifier can be used exactly such as aws_kms_grant.example (no quotes!). This improves the validation and error messages we can provide. Similarly, a resource reference can be returned from a module as an output or accepted as a parameter.

© 2019 HashiCorp

22 of 58

Terraform 0.12

First Class Expressions

22

© 2019 HashiCorp

23 of 58

First-Class Expressions

First Class Expressions

  • Operations and variables can be used outside of string interpolation
    • buh-bye "${ }"
  • Lists and maps can be used directly with expressions
    • less list(""), more []

23

© 2019 HashiCorp

24 of 58

Terraform 0.11

main.tf

First Class Expressions

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}"�}�

24

CODE EDITOR

© 2019 HashiCorp

25 of 58

Terraform 0.12

main.tf

First Class Expressions

variable "ami" {}�variable "instance_type" {}�variable "vpc_security_group_ids" {� type = list(string)�}��resource "aws_instance" "example" {� ami = var.ami� instance_type = var.instance_type�� vpc_security_group_ids = var.vpc_security_group_ids�}�

25

CODE EDITOR

© 2019 HashiCorp

26 of 58

Terraform 0.11

main.tf

First Class Expressions

variable "ami" {}�variable "instance_type" {}�variable "vpc_security_group_id" {� type = "string"� default = ""�}��resource "aws_instance" "example" {� ami = "${var.ami}"� instance_type = "${var.instance_type}"�� vpc_security_group_ids = "${var.vpc_security_group_id != "" ?

[var.vpc_security_group_id] :

list("")

}"�}�

26

CODE EDITOR

© 2019 HashiCorp

27 of 58

Terraform 0.12

main.tf

First Class Expressions

variable "ami" {}�variable "instance_type" {}�variable "vpc_security_group_id" {� type = string� default = ""�}��resource "aws_instance" "example" {� ami = var.ami� instance_type = var.instance_type�� vpc_security_group_ids = var.security_group_id != "" ?

[var.security_group_id] :

[]�}�

27

CODE EDITOR

© 2019 HashiCorp

28 of 58

Terraform 0.12

Rich Value Types

28

© 2019 HashiCorp

29 of 58

Rich Value Types

Rich Value Types

  • enhances simple type system
  • complex values
    • maps and lists!
    • maps of maps!!
    • maps of lists of maps of lists!!!
  • entire resources and modules as values

29

© 2019 HashiCorp

30 of 58

Terraform

Module Layout

Rich Value Types

$ tree�.�├── main.tf�├── terraform.tfvars�└── modules� └── subnets� └── subnets.tf�

30

TERMINAL

© 2019 HashiCorp

31 of 58

Terraform 0.11

main.tf

Rich Value Types

variable "networks" {� type = "list"�}��module "subnets" {� source = "./modules/subnets"� networks = "${var.networks}"� ...�}��output "vpc_id" {� value = "${module.subnets.vpc_id}"�}�

31

CODE EDITOR

© 2019 HashiCorp

32 of 58

Terraform 0.11

main.tf

Rich Value Types

# "subnets" module��variable "networks" {� type = "list"�}��resource "aws_vpc" "example" {� networks = "${var.networks}"� ...�}��output "vpc_id" {� value = "${aws_vpc.example.id}"�}�

32

CODE EDITOR

© 2019 HashiCorp

33 of 58

Terraform 0.12

main.tf

Rich Value Types

variable "networks" {� type = map(object({� network_number = number� availability_zone = string� tags = map(string)� }))�}��module "subnets" {� source = "./modules/subnets"� networks = var.networks�}��output "vpc_id" {� value = module.subnets.vpc_id�}�

33

CODE EDITOR

© 2019 HashiCorp

34 of 58

Terraform 0.12

terraform.tfvars

Rich Value Types

networks = {� "production" = {� network_number = 1� availability_zone = "us-east-1a"� }� "staging" = {� network_number = 2� availability_zone = "us-east-1a"� }�}�

34

CODE EDITOR

© 2019 HashiCorp

35 of 58

Terraform 0.12

subnets.tf

Rich Value Types

# "subnets" modules��variable "networks" {� type = map(object({� network_number = number� availability_zone = string� tags = map(string)� }))�}��resource "aws_vpc" "example" {� networks = var.networks� ...�}��output "vpc" {� value = aws_vpc.example�}�

35

CODE EDITOR

© 2019 HashiCorp

36 of 58

Terraform 0.12

main.tf

Rich Value Types

variable "networks" {� type = map(object({� network_number = number� availability_zone = string� tags = map(string)� }))�}��module "subnets" {

source = "./modules/subnets"� networks = var.networks�}��resource "aws_instance" "my_server" {� subnet_id = module.subnets.vpc.id� az = module.subnets.vpc.availability_zone� ... �}

36

CODE EDITOR

© 2019 HashiCorp

37 of 58

Terraform 0.12

Improved Error Messages

37

© 2019 HashiCorp

38 of 58

Terraform 0.11

Improved Error Messages

$ terraform plan��Error: Error parsing main.tf: object expected closing RBRACE got: EOF

38

TERMINAL

© 2019 HashiCorp

39 of 58

Terraform 0.12

Improved Error Messages

$ terraform plan��Error: Argument or block definition required�� on main.tf line 24:� 24: :wq��An argument or block definition is required here.�

39

TERMINAL

© 2019 HashiCorp

40 of 58

Terraform 0.12

Improved Error Messages

$ terraform plan��Error: Invalid operand�� on main.tf line 16, in output "foobar":� 16: value = "${1 + var.foo}"��Unsuitable value for right operand: a number is required.�

40

TERMINAL

© 2019 HashiCorp

41 of 58

Terraform 0.12

Improved Error Messages

$ terraform plan��Error: Unsupported block type�� on main.tf line 36, in outptu "item":� 1: outptu "item" {��Blocks of type "outptu" are not expected here. Did you mean "output"?�

41

TERMINAL

© 2019 HashiCorp

42 of 58

Terraform 0.12

For Expressions

42

© 2019 HashiCorp

43 of 58

For Expressions

For Expressions

  • for: list and map transformations
  • for_each: for dynamic nested blocks
  • dynamic nested blocks

43

© 2019 HashiCorp

44 of 58

Terraform 0.11

main.tf

For Expressions

output "instance_private_ip_addresses_map" {� value = "${zipmap(aws_instance.id,aws_instance.private_ip)}"�}�

44

CODE EDITOR

© 2019 HashiCorp

45 of 58

Terraform 0.12

main.tf

For Expressions

output "instance_private_ip_addresses" {� value = {� for instance in aws_instance.example:� instance.id => instance.private_ip� }�}�

45

CODE EDITOR

© 2019 HashiCorp

46 of 58

Terraform 0.12

For Expressions

$ terraform output��instance_private_ip_addresses = {� "i-1234" = "192.168.1.1""i-5678" = "192.168.1.2""i-9876" = "192.168.1.3"�}�

46

TERMINAL

© 2019 HashiCorp

47 of 58

Terraform 0.11

main.tf

For Expressions

resource "aws_autoscaling_group" "example" {� # ...� tag {� key = "Component"� value = "user-service"� propagate_at_launch = true� }��� tag {� key = "Environment"� value = "production"� propagate_at_launch = false� }���tag { ... }�tag { ... }�tag { ... }�tag { ... }�}�

47

CODE EDITOR

© 2019 HashiCorp

48 of 58

Terraform 0.12

main.tf

For Expressions

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� }� }�}�

48

CODE EDITOR

© 2019 HashiCorp

49 of 58

Terraform 0.12

Back to `random_pet` Example

49

© 2019 HashiCorp

50 of 58

Terraform 0.11

main.tf

Terraform 0.12 Examples

variable "count" { default = 1 }�variable "default_prefix" { default = "linus" }��variable "zoo_enabled" {� default = "0"�}��variable "prefix_list" {� default = []�}��resource "random_pet" "my_pet" {� count = "${var.count}"� prefix = "${var.zoo_enabled == "0" ?

var.default_prefix :

element(concat(var.prefix_list, list("")), count.index)}"�}�

50

CODE EDITOR

© 2019 HashiCorp

51 of 58

The same example - HCL2

51

© 2019 HashiCorp

52 of 58

Terraform 0.12

main.tf

Terraform 0.12 Examples

variable "pet_count" { default = 1 }��variable "default_prefix" { default = “linus” }��variable "zoo_enabled" {default = false�}��variable "prefix_list" {default = []�}��resource "random_pet" "my_pet" {count = var.pet_count� prefix = var.zoo_enabled ?

element(var.prefix_list,count.index) :

var.default_prefix�}

52

CODE EDITOR

© 2019 HashiCorp

53 of 58

Terraform 0.12

Terraform 0.12 Examples

$ terraform apply�random_pet.my_pet[0]: Creating...�random_pet.my_pet[0]: Creation complete after 0s��Apply complete! Resources: 1 added, 0 changed, 0 destroyed.��Outputs:��pet_names = [� Pet: linus-beloved-mako�]�

53

TERMINAL

© 2019 HashiCorp

54 of 58

But what does it mean?

54

© 2019 HashiCorp

55 of 58

Terraform 0.12 Enhancements

  • configuration is easier to read and reason about
  • consistent, predictable behavior in complex functions
  • improved support for loosely-coupled modules

55

© 2019 HashiCorp

56 of 58

Demo?

56

© 2019 HashiCorp

57 of 58

CFPs Open:

HashiConf USA

(closes 2nd April)

57

© 2019 HashiCorp

58 of 58

hello@hashicorp.com

58

Thank you