Slide 1

Slide 1 text

MEET TERRAFORM

Slide 2

Slide 2 text

DEVELOPER ADVOCATE @NEXMO MICHAEL @MHEAP

Slide 3

Slide 3 text

IN A PREVIOUS LIFE…

Slide 4

Slide 4 text

IN A PREVIOUS LIFE… I RAN AN OPERATIONS TEAM

Slide 5

Slide 5 text

WHEN I JOINED No Infrastructure

Slide 6

Slide 6 text

THEN I STARTED ADDING THINGS

Slide 7

Slide 7 text

THEN I STARTED ADDING THINGS

Slide 8

Slide 8 text

THEN I STARTED ADDING THINGS

Slide 9

Slide 9 text

THEN I STARTED ADDING THINGS

Slide 10

Slide 10 text

THEN I STARTED ADDING THINGS

Slide 11

Slide 11 text

THEN I STARTED ADDING THINGS

Slide 12

Slide 12 text

THERE HAS TO BE A BETTER WAY

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

WHAT IS TERRAFORM?

Slide 17

Slide 17 text

INFRASTRUCTURE AS CODE

Slide 18

Slide 18 text

INFRASTRUCTURE AS CODE resource "aws_instance" "web" { ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }

Slide 19

Slide 19 text

1207 CONTRIBUTORS, 103 RELEASES 11,797 STARS, 3817 FORKS CREATED 22ND MAY 2014

Slide 20

Slide 20 text

TERRAFORM ALLOWS US TO DEFINE WHAT WE NEED IN A DECLARATIVE MANNER

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

Alicloud Archive Arukas AWS Bitbucket CenturyLinkCloud Chef Circonus Cloudflare CloudStack Cobbler Consul Datadog DigitalOcean DNS DNSMadeEasy DNSimple Docker Dyn External Fastly GitHub Gitlab Google Cloud Grafana Heroku HTTP Icinga2 Ignition InfluxDB Kubernetes Librato Local Logentries Mailgun New Relic Nomad NS1 Microsoft Azure Microsoft Azure (Legacy ASM) MySQL 1&1 Oracle Public Cloud OpenStack OpsGenie OVH Packet PagerDuty PostgreSQL PowerDNS ProfitBricks RabbitMQ Rancher Random Rundeck Scaleway SoftLayer StatusCake Spotinst Template Terraform Terraform Enterprise TLS Triton UltraDNS Vault VMware vCloud Director VMware vSphere

Slide 23

Slide 23 text

TERRAFORM IS NOT AN API ABSTRACTION LAYER. EACH CLOUD HAS IT’S OWN RESOURCES

Slide 24

Slide 24 text

TERRAFORM ALLOWS US TO DEFINE WHAT WE NEED IN A DECLARATIVE MANNER

Slide 25

Slide 25 text

TERRAFORM ALLOWS US TO DEFINE WHAT WE NEED IN A DECLARATIVE MANNER

Slide 26

Slide 26 text

WHAT’S THE PLAN?

Slide 27

Slide 27 text

THE TERRAFORM CLI YOUR FIRST PLAN INSTALLING TERRAFORM

Slide 28

Slide 28 text

MODULES PROVISIONING VARIABLES

Slide 29

Slide 29 text

LIFECYCLE MANAGEMENT IMMUTABLE INFRASTRUCTURE USING IT AS A TEAM

Slide 30

Slide 30 text

INSTALLING TERRAFORM

Slide 31

Slide 31 text

RUN IT THERE IS NO STEP 3 DOWNLOAD THE BINARY

Slide 32

Slide 32 text

THE TERRAFORM CLI

Slide 33

Slide 33 text

$ terraform Usage: terraform [--version] [--help] [args] The available commands for execution are listed below. The most common, useful commands are shown first, followed by less common or more advanced commands. If you're just getting started with Terraform, stick with the common commands. For the other commands, please read the help and docs before usage. Common commands: apply Builds or changes infrastructure console Interactive console for Terraform interpolations destroy Destroy Terraform-managed infrastructure env Workspace management fmt Rewrites config files to canonical format get Download and install modules for the configuration graph Create a visual graph of Terraform resources import Import existing infrastructure into Terraform init Initialize a Terraform working directory output Read an output from a state file plan Generate and show an execution plan providers Prints a tree of the providers used in the configuration push Upload this Terraform module to Atlas to run refresh Update local state file against real resources show Inspect Terraform state or plan taint Manually mark a resource for recreation untaint Manually unmark a resource as tainted validate Validates the Terraform files version Prints the Terraform version workspace Workspace management All other commands: debug Debug output management (experimental) force-unlock Manually unlock the terraform state state Advanced state management

Slide 34

Slide 34 text

$ terraform Usage: terraform [--version] [--help] [args] The available commands for execution are listed below. The most common, useful commands are shown first, followed by less common or more advanced commands. If you're just getting started with Terraform, stick with the common commands. For the other commands, please read the help and docs before usage. Common commands: apply Builds or changes infrastructure console Interactive console for Terraform interpolations destroy Destroy Terraform-managed infrastructure env Workspace management fmt Rewrites config files to canonical format get Download and install modules for the configuration graph Create a visual graph of Terraform resources import Import existing infrastructure into Terraform init Initialize a Terraform working directory output Read an output from a state file plan Generate and show an execution plan providers Prints a tree of the providers used in the configuration push Upload this Terraform module to Atlas to run refresh Update local state file against real resources show Inspect Terraform state or plan taint Manually mark a resource for recreation untaint Manually unmark a resource as tainted validate Validates the Terraform files version Prints the Terraform version workspace Workspace management All other commands: debug Debug output management (experimental) force-unlock Manually unlock the terraform state state Advanced state management

Slide 35

Slide 35 text

$ terraform Usage: terraform [--version] [--help] [args] The available commands for execution are listed below. The most common, useful commands are shown first, followed by less common or more advanced commands. If you're just getting started with Terraform, stick with the common commands. For the other commands, please read the help and docs before usage. Common commands: apply Builds or changes infrastructure console Interactive console for Terraform interpolations destroy Destroy Terraform-managed infrastructure env Workspace management fmt Rewrites config files to canonical format get Download and install modules for the configuration graph Create a visual graph of Terraform resources import Import existing infrastructure into Terraform init Initialize a Terraform working directory output Read an output from a state file plan Generate and show an execution plan providers Prints a tree of the providers used in the configuration push Upload this Terraform module to Atlas to run refresh Update local state file against real resources show Inspect Terraform state or plan taint Manually mark a resource for recreation untaint Manually unmark a resource as tainted validate Validates the Terraform files version Prints the Terraform version workspace Workspace management All other commands: debug Debug output management (experimental) force-unlock Manually unlock the terraform state state Advanced state management

Slide 36

Slide 36 text

TERRAFORM APPLY TERRAFORM DESTROY TERRAFORM PLAN

Slide 37

Slide 37 text

TERRAFORM APPLY TERRAFORM DESTROY

Slide 38

Slide 38 text

YOUR FIRST PLAN

Slide 39

Slide 39 text

YOUR FIRST PLAN $ export AWS_ACCESS_KEY_ID="anaccesskey" $ export AWS_SECRET_ACCESS_KEY="asecretkey" $ mkdir terraform-example $ cd terraform-example $ touch main.tf

Slide 40

Slide 40 text

VARIABLES.TF OUTPUTS.TF MAIN.TF

Slide 41

Slide 41 text

LONGHORN.TF PHP.TF BANANAS.TF

Slide 42

Slide 42 text

DEFINE A PROVIDER (MAIN.TF) provider "aws" { region = "us-west-2" }

Slide 43

Slide 43 text

DEFINE A PROVIDER (MAIN.TF) provider "aws" { access_key = "KEY" secret_key = "SEKRIT" region = "us-west-2" }

Slide 44

Slide 44 text

CREATE AN INSTANCE (MAIN.TF) resource "aws_instance" "web" { ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }

Slide 45

Slide 45 text

CREATE AN INSTANCE resource "aws_instance" "web" { ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }

Slide 46

Slide 46 text

CREATE AN INSTANCE resource "aws_instance" "web" { ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }

Slide 47

Slide 47 text

CREATE AN INSTANCE resource "aws_instance" "web" { ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }

Slide 48

Slide 48 text

CREATE AN INSTANCE resource "aws_instance" "web" { ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }

Slide 49

Slide 49 text

CREATE AN INSTANCE resource "aws_instance" "web" { ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }

Slide 50

Slide 50 text

TERRAFORM PLAN $ terraform plan -out tfplan Plugin reinitialization required. Please run "terraform init". Reason: Could not satisfy plugin requirements. Plugins are external binaries that Terraform uses to access and manipulate resources. The configuration provided requires plugins which can't be located, don't satisfy the version constraints, or are otherwise incompatible. 1 error(s) occurred: * provider.aws: no suitable version installed version requirements: "(any version)" versions installed: none Terraform automatically discovers provider requirements from your configuration, including providers used in child modules. To see the requirements and constraints from each module, run "terraform providers".

Slide 51

Slide 51 text

TERRAFORM INIT $ terraform init Initializing provider plugins... - Checking for available provider plugins on https://releases.hashicorp.com... - Downloading plugin for provider "aws" (1.0.0)... 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.aws: version = "~> 1.0" Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work.

Slide 52

Slide 52 text

TERRAFORM PLAN An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: + aws_instance.web id: ami: "ami-70728c08" associate_public_ip_address: source_dest_check: "true" subnet_id: tags.%: "1" tags.Name: "HelloWorld" Plan: 1 to add, 0 to change, 0 to destroy.

Slide 53

Slide 53 text

TERRAFORM APPLY $ terraform apply tfplan aws_instance.web: Creating... ami: "" => "ami-70728c08" instance_type: "" => "t2.micro" source_dest_check: "" => "true" subnet_id: "" => "" tags.%: "" => "1" tags.Name: "" => "HelloWorld" tenancy: "" => "" volume_tags.%: "" => "" vpc_security_group_ids.#: "" => "" aws_instance.web: Still creating... (10s elapsed) aws_instance.web: Still creating... (20s elapsed) aws_instance.web: Creation complete after 22s (ID: i-0c0b4a732311b8bce) Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Slide 54

Slide 54 text

INSTANCE CREATED

Slide 55

Slide 55 text

TERRAFORM PLAN $ terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. aws_instance.web: Refreshing state... (ID: i-0c0b4a732311b8bce) ------------------------------------------------------------------------ No changes. Infrastructure is up-to-date. This means that Terraform did not detect any differences between your configuration and real physical resources that exist. As a result, no actions need to be performed.

Slide 56

Slide 56 text

TERRAFORM.TFSTATE { "version": 3, "terraform_version": "0.10.4", "serial": 2, "lineage": "348d2909-5e43-4421-bd65-4f7ab6b11632", "modules": [ { "path": [ "root" ], "outputs": {}, "resources": { "aws_instance.web": { "type": "aws_instance", "depends_on": [], "primary": { "id": "i-0fc83946a097d8e27", "attributes": { "ami": "ami-70728c08", "associate_public_ip_address": "true", "availability_zone": "us-west-2a", "disable_api_termination": "false", "ebs_block_device.#": "0", "ebs_optimized": "false", "ephemeral_block_device.#": "0", "iam_instance_profile": "", "id": "i-0fc83946a097d8e27", "instance_state": "running", "instance_type": "t2.small", "ipv6_addresses.#": "0", "key_name": "", "monitoring": "false", "network_interface.#": "0", "network_interface_id": "eni-de1229f0", "primary_network_interface_id": "eni-de1229f0", "private_dns": "ip-172-31-21-4.us-west-2.compute.internal", "private_ip": "172.31.21.4", "public_dns": "ec2-34-215-64-201.us-west-2.compute.amazonaws.com", "public_ip": "34.215.64.201", "root_block_device.#": "1", "root_block_device.0.delete_on_termination": "true", "root_block_device.0.iops": "100", "root_block_device.0.volume_size": "8", "root_block_device.0.volume_type": "gp2", "security_groups.#": "1", "security_groups.3814588639": "default", "source_dest_check": "true", "subnet_id": "subnet-3e5ef758", "tags.%": "1", "tags.Name": "HelloWorld", "tenancy": "default", "volume_tags.%": "0", "vpc_security_group_ids.#": "0" }, "meta": { "e2bfb730-ecaa-11e6-8f88-34363bc7c4c0": { "create": 600000000000, "delete": 600000000000, "update": 600000000000 }, "schema_version": "1" }, "tainted": false }, "deposed": [], "provider": "" } }, "depends_on": [] } ] } "aws_instance.web": { "type": "aws_instance", "depends_on": [], "primary": { "id": "i-0fc83946a097d8e27", "attributes": { "ami": "ami-70728c08", "associate_public_ip_address": "true", "availability_zone": "us-west-2a", "disable_api_termination": "false", "ebs_block_device.#": "0", "ebs_optimized": "false", "ephemeral_block_device.#": "0", "iam_instance_profile": "", "id": "i-0fc83946a097d8e27",

Slide 57

Slide 57 text

SCALING UP resource "aws_instance" "web" { count = "3" ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }

Slide 58

Slide 58 text

SCALING UP resource "aws_instance" "web" { count = "3" ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }

Slide 59

Slide 59 text

TERRAFORM PLAN An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: + aws_instance.web[1] ami: "ami-70728c08" tags.Name: "HelloWorld" + aws_instance.web[2] ami: "ami-70728c08" tags.Name: “HelloWorld" Plan: 2 to add, 0 to change, 0 to destroy.

Slide 60

Slide 60 text

ADDING A LOAD BALANCER resource "aws_elb" "lb" { name = "example-lb" availability_zones = ["us-west-2a"] instances = ["${aws_instance.web.*.id}"] listener { instance_port = "8000" instance_protocol = "http" lb_port = "80" lb_protocol = "http" } }

Slide 61

Slide 61 text

ADDING A LOAD BALANCER resource "aws_elb" "lb" { name = "example-lb" availability_zones = ["us-west-2a"] instances = ["${aws_instance.web.*.id}"] listener { instance_port = "8000" instance_protocol = "http" lb_port = "80" lb_protocol = "http" } }

Slide 62

Slide 62 text

TERRAFORM APPLY PROFIT TERRAFORM PLAN

Slide 63

Slide 63 text

OUTPUTS.TF output "lb_dns" { value = "${aws_elb.lb.dns_name}" }

Slide 64

Slide 64 text

OUTPUTS.TF $ terraform refresh aws_instance.web[0]: Refreshing state... (ID: i-0c0b4a732311b8bce) aws_instance.web[1]: Refreshing state... (ID: i-0b8bde17992d4dc0c) aws_instance.web[2]: Refreshing state... (ID: i-06e74e8dc04eb1843) aws_elb.lb: Refreshing state... (ID: example-elb) Outputs: lb_dns = example-elb-1640688516.us-west-2.elb.amazonaws.com

Slide 65

Slide 65 text

OUTPUTS.TF $ terraform output -json { "lb_dns": { "sensitive": false, "type": "string", "value": "example-elb-1640688516.us-west-2.elb.amazonaws.com" } }

Slide 66

Slide 66 text

TERRAFORM DESTROY $ terraform destroy An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: - aws_elb.lb - aws_instance.web[0] - aws_instance.web[1] - aws_instance.web[2] Plan: 0 to add, 0 to change, 4 to destroy. Do you really want to destroy? Terraform will destroy all your managed infrastructure, as shown above. There is no undo. Only 'yes' will be accepted to confirm. Enter a value:

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

CONGRATULATIONS! YOU JUST CREATED YOUR FIRST PLAN

Slide 69

Slide 69 text

VARIABLES

Slide 70

Slide 70 text

SCALING UP resource "aws_instance" "web" { count = "3" ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "HelloWorld" } }

Slide 71

Slide 71 text

SCALING UP resource "aws_instance" "web" { count = "3" ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "web" } }

Slide 72

Slide 72 text

SCALING UP resource "aws_instance" "web" { count = "3" ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "web-${count.index}" } }

Slide 73

Slide 73 text

SCALING UP resource "aws_instance" "web" { count = "3" ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "web-${count.index}" Environment = "PirateNinjas" } }

Slide 74

Slide 74 text

SCALING UP resource "aws_elb" "lb" { name = "example-lb" …snip… listener { …snip… } tags { Environment = "PirateNinjas" } }

Slide 75

Slide 75 text

VARIABLES.TF variable "environment" { type = "string" }

Slide 76

Slide 76 text

SCALING UP resource "aws_instance" "web" { count = "3" ami = "ami-70728c08" instance_type = "t2.micro" tags { Name = "web-${count.index}" Environment = "${var.environment}" } }

Slide 77

Slide 77 text

SCALING UP resource "aws_elb" "lb" { name = "lb-${var.environment}" …snip… listener { …snip… } tags { Environment = "${var.environment}" } }

Slide 78

Slide 78 text

TERRAFORM -VAR ENVIRONMENT=NAME TERRAFORM -VAR-FILE MY.TFVARS EXPORT TF_VAR_ENVIRONMENT=NAME

Slide 79

Slide 79 text

CONGRATULATIONS! YOUR PLAN FILE IS NICE AND DRY

Slide 80

Slide 80 text

MODULES

Slide 81

Slide 81 text

CREATING A MODULE $ tree . !"" main.tf !"" output.tf #"" variables.tf

Slide 82

Slide 82 text

CREATING A MODULE $ tree . !"" main.tf !"" output.tf #"" variables.tf $ tree . #"" demo !"" main.tf !"" output.tf #"" variables.tf

Slide 83

Slide 83 text

USING A MODULE (LOCAL) module "pirate" { source = "./demo" environment = "Pirate" }

Slide 84

Slide 84 text

USING A MODULE (LOCAL) module "pirate" { source = "./demo" environment = "Pirate" } module "ninja" { source = "./demo" environment = "Ninja" }

Slide 85

Slide 85 text

USING A MODULE (REGISTRY) module "pirate" { source = "mheap/aws/full-env" environment = "Pirate" } module "ninja" { source = "mheap/aws/full-env" environment = "Ninja" } https://registry.terraform.io/

Slide 86

Slide 86 text

USING A MODULE (GIT) module "pirate" { source = "git::https://hashicorp.com/consul.git? ref=1.0.3" environment = "Pirate" } module "ninja" { source = “git::https://hashicorp.com/consul.git? ref=1.8.14" environment = "Ninja" }

Slide 87

Slide 87 text

LOCAL GITHUB BITBUCKET GIT MERCURIAL S3 HTTP

Slide 88

Slide 88 text

MODULE OUTPUTS output "pirate_lb" { value = "${module.pirate.lb_dns}" } output "ninja_lb" { value = "${module.ninja.lb_dns}" }

Slide 89

Slide 89 text

CONGRATULATIONS! YOU JUST CREATED A MODULE!

Slide 90

Slide 90 text

WARNING WARNING WARNING

Slide 91

Slide 91 text

BAD . !"" demo $ !"" main.tf $ !"" output.tf $ #"" variables.tf !"" main.tf !"" outputs.tf #"" terraform.tfstate

Slide 92

Slide 92 text

GOOD . !"" environments $ !"" ninja $ $ !"" main.tf $ $ !"" output.tf $ $ #"" terraform.tfstate $ #"" pirate $ !"" main.tf $ !"" output.tf $ #"" terraform.tfstate #"" modules #"" demo !"" main.tf !"" output.tf #"" variables.tf

Slide 93

Slide 93 text

PROVISIONING

Slide 94

Slide 94 text

RUNNING ANSIBLE resource "aws_instance" "web" { count = "3" ami = "ami-70728c08" instance_type = "t2.micro" tags { …snip… } provisioner "local-exec" { command = "ansible-playbook -u ubuntu --private-key ./aws-key.pem -i '$ {self.public_ip},' playbook.yml" } }

Slide 95

Slide 95 text

RUNNING APT-GET resource "aws_instance" "web" { …snip… provisioner "remote-exec" { connection { type = "ssh" user = "root" password = "${var.root_password}" } inline = [ "sudo apt-get install nginx my-app", "sudo systemctl restart nginx" ] } }

Slide 96

Slide 96 text

OTHER PROVISIONERS HABITAT SALT-MASTERLESS CHEF

Slide 97

Slide 97 text

USING IT AS A TEAM

Slide 98

Slide 98 text

CONSUL TERRAFORM ENTERPRISE AMAZON S3

Slide 99

Slide 99 text

REMOTE STATE (S3) terraform { backend "s3" { bucket = "mybucket" key = "path/to/my/key" region = “us-west-2" } }

Slide 100

Slide 100 text

REMOTE STATE (CONSUL) terraform { backend "consul" { address = "demo.consul.io" path = "example_app/ terraform_state" } }

Slide 101

Slide 101 text

REMOTE STATE (TF ENTERPRISE)

Slide 102

Slide 102 text

LIFECYCLE MANAGEMENT

Slide 103

Slide 103 text

LIFECYCLE lifecycle { create_before_destroy = true }

Slide 104

Slide 104 text

IMMUTABLE INFRASTRUCTURE

Slide 105

Slide 105 text

No content

Slide 106

Slide 106 text

BONUS CONTENT

Slide 107

Slide 107 text

IMPORTING EXISTING INFRASTRUCTURE

Slide 108

Slide 108 text

MIX/MATCH PROVIDERS E.G. HEROKU + DNSIMPLE provider "heroku" { email = "[email protected]" api_key = "${var.heroku_api_key}" } provider "dnsimple" { token = "${var.dnsimple_token}" account = "${var.dnsimple_account}" } resource "heroku_app" "default" { … } resource "dnsimple_record" "foobar" { domain = "${var.dnsimple_domain}" name = "" value = “${heroku_app.default.heroku_hostname}" type = "CNAME" ttl = 3600 }

Slide 109

Slide 109 text

TERRAFORM CONFIGURATON BLOCK REMOTE STATE / MINIMUM VERSIONS terraform { required_version = "> 0.11.0" }

Slide 110

Slide 110 text

59 FUNCTIONS CHOMP/COALESCE/ELEMENT/FILE + MORE

Slide 111

Slide 111 text

READ ONLY DATA PROVIDERS E.G. AWS_AMI FOR SEARCHING AMIS data "aws_ami" "ubuntu" { most_recent = true filter { name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*"] } filter { name = "virtualization-type" values = ["hvm"] } owners = ["099720109477"] # Canonical }

Slide 112

Slide 112 text

OVERRIDE.TF MERGE CONTENT INTO A PLAN RATHER THAN APPENDING A NEW RESOURCE

Slide 113

Slide 113 text

TESTING AWSSPEC / INSPEC require "spec_helper" before do @client = Aws::EC2::Client.new @ec2 = Aws::EC2::Resource.new(client: @client) end describe "Instances" do subject { @ec2.instances.count } it { is_expected.to eq(3) } end

Slide 114

Slide 114 text

No content

Slide 115

Slide 115 text

ANY QUESTIONS? @MHEAP HTTPS://JOIND.IN/TALK/2173C