====== Terraform ====== Links:- https://tech.smartling.com/getting-started-with-terraform-and-aws-ec2-first-steps-workshop-1-e38607f0fd4c https://blog.scottlowe.org/2015/11/25/intro-to-terraform/ https://stephenmann.io/post/setting-up-monitoring-and-alerting-on-amazon-aws-with-terraform/ [[terraform:terraform-examples1|Simple example 1]] [[terraform:terraform-examples2|Simple example 2]] ===== Starting ===== Terraform reads all files in a directory to build a project. By convention they end .tf Terrraform used plug in modules to communicate with a cloud provider, eg. AWS, the init phase loads these in a .terraform directory. Phases of build:- $ cd to target directory $ terraform init $ terraform plan $ terraform apply $ terraform destroy ==== init phase ==== The init phase sets up the terraform environment prior to being able to run the apply phase. If you need to run this under a different AWS profile, you need to set this as an env variable:- $ AWS_PROFILE=myprofile terraform init I needed to do this to initialise an s3 backend for a statefile, but my defaut AWS crededtials were for a different account, so it was trying to access a bucket I did not have permission for, setting the env var solved this. The AWS profile info can be found here:- [[aws:aws-cli#setting_up_profiles|Amazon Web Services CLI]] Another option is to include a profile in your ''provider'' statement:- provider "aws" { region = "${var.AWS_REGION}" profile = "myprofile" default_tags { tags = { BuiltBy = "Terraform" } } ==== validate phase ==== validate -json { "format_version": "1.0", "valid": true, "error_count": 0, "warning_count": 0, "diagnostics": [] } ==== plan phase ==== The plan phase will show the resources to be built or changed, you can also see resources to be destroyed. As with ''init'', you can run this as a different AWS profile:- $ AWS_PROFILE=rainsbrook terraform plan $ terraform plan -destroy ==== apply phase ==== apply phase will do a plan and then attempt to apply this plan, you will be prompted to respond ''yes'' to approve. You anc avoid this if you are sure by specifying ''-auto-approve''. $ terraform apply -auto-approve ==== destroy phase ==== destroy phase removes resources Terraform has built, you can use the plan option to see what is to be destroyed. $ terraform plan -destroy $ terraform destroy $ terraform destroy -auto-approve ===== State files and Lock files ===== See [[aws:aws-cloudformation-terraformstate|Cloudformation for Terraform State Files and Lock Table]], this is some Cloudformation code which runs to bootstrap Terraform by creating an S3 bucket, DynamoDB Lock Table and stores the resulting Bucket and Table name in parameter Store where Terraform can retreive it. Use S3 and Dynamo db to store this info. ===== AWS Provisioner code ===== aws_provider.tf # Export key and secret credentials first. # run as -> $ terraform apply # might need to do this if init times out:- # "export https_proxy=http://proxy.company.com:8080" data "aws_ssm_parameter" "TerraformLockTable-SG" { name = "TerraformLockTable-SG" } data "aws_ssm_parameter" "TerraformStateBucket" { name = "TerraformStateBucket-SG" } terraform { required_providers { aws = { source = "hashicorp/aws" version = "3.74.1" } } backend "s3" { region = "eu-west-2" bucket = "vpc-ec2-statefiles-sg" key = "training_vpc_ec2-statefiles-SG/prod/vpc-ec2.tfstate" dynamodb_table = "vpc-ec2-lockfiles-SG" } } provider "aws" { region = "eu-west-2" } ==== Snippets ==== resource "aws_s3_bucket" "aws-vpn-vpc-prod-statefiles" { bucket = "aws-vpn-vpc-prod-statefiles" versioning { enabled = true } } resource "aws_dynamodb_table" "aws-vpn-vpc-prod-locks" { name = "aws-vpn-vpd-prod-locks" billing_mode = "PAY_PER_REQUEST" hash_key = "LockID" attribute { name = "LockID" type = "S" } } terraform { backend "s3" { profile = "rainsbrook-terraform" bucket = "aws-vpn-vpc-prod-statefiles" key = "aws-vpn-vpc-prod/terraform.tfstate" region = "eu-west-1" dynamodb_table = "aws-vpn-vpc-prod-locks" } ===== Visualising what is going to be built ===== ''terraform graph'' will produce a block of text you can upload to [[http://webgraphviz.com]] to draw a diagram of dependencies. digraph { compound = "true" newrank = "true" subgraph "root" { "[root] aws_iam_role.iam-key-age-role (expand)" [label = "aws_iam_role.iam-key-age-role", shape = "box"] "[root] provider[\"registry.terraform.io/hashicorp/aws\"]" [label = "provider[\"registry.terraform.io/hashicorp/aws\"]", shape = "diamond"] "[root] var.AWS_REGION" [label = "var.AWS_REGION", shape = "note"] "[root] aws_iam_role.iam-key-age-role (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/aws\"]" "[root] meta.count-boundary (EachMode fixup)" -> "[root] aws_iam_role.iam-key-age-role (expand)" "[root] provider[\"registry.terraform.io/hashicorp/aws\"] (close)" -> "[root] aws_iam_role.iam-key-age-role (expand)" "[root] provider[\"registry.terraform.io/hashicorp/aws\"]" -> "[root] var.AWS_REGION" "[root] root" -> "[root] meta.count-boundary (EachMode fixup)" "[root] root" -> "[root] provider[\"registry.terraform.io/hashicorp/aws\"] (close)" } } {{:terraform:webgraphviz.png?400|}} ===== What's already built ===== ''terraform state'' ''list & show'' will show what Terraform knows about from the state files. $ terraform state list aws_iam_policy.iam-key-age-policy aws_iam_policy_attachment.iam-key-age-attachment aws_iam_role.iam-key-age-role $ terraform state show aws_iam_role.iam-key-age-role # aws_iam_role.iam-key-age-role: resource "aws_iam_role" "iam-key-age-role" { arn = "arn:aws:iam::581230658448:role/iam-key-age-role" assume_role_policy = jsonencode( ... edited... ) create_date = "2021-12-03T14:48:44Z" force_detach_policies = false id = "iam-key-age-role" managed_policy_arns = [] max_session_duration = 3600 name = "iam-key-age-role" path = "/" tags = { "name" = "iam-key-age-role" } tags_all = { "BuiltBy" = "Terraform" "name" = "iam-key-age-role" } unique_id = "AROAYOVAMAOIMSEBWOIHN" inline_policy {} } ===== Errors & fixes ===== ==== No valid credential sources found for AWS Provider. ==== andrew@mac:prod$ terraform12 init Initializing modules... Initializing the backend... Error: No valid credential sources found for AWS Provider. Please see https://terraform.io/docs/providers/aws/index.html for more information on providing credentials for the AWS Provider Fix:- AWS_PROFILE needed exporting. andrew@mac:prod$ export AWS_PROFILE=MyProfileName andrew@mac:prod$ terraform init Initializing modules... Initializing the backend... Successfully configured the backend "s3"! Terraform will automatically use this backend unless the backend configuration changes. See also:- [[terraform:start#init_phase|init phase]] to include the profile in the Provider section. ===== Testing something to show in a plan ===== resource "null_resource" "temporary" { triggers = { path = "${path.module}/../files" } } ===== Removing a state lock ===== If you have interrupted terraform with a Ctrl-C, there may be a state lock left behind. This can be removed thus:- $ terraform plan 2019/10/16 13:11:19 [INFO] Terraform version: 0.11.11 ac4fff416318bf0915a0ab80e062a99ef3724334 ...edited... Error: Error locking state: Error acquiring the state lock: ConditionalCheckFailedException: The conditional request failed status code: 400, request id: VKFDCAN5H28CSJ3NQKO1CUHT3BVV4KQNSO5AEMVJF66Q9ASUAAJG Lock Info: ID: cece51d7-ee71-0f70-d6cc-0830631ed672 ...edited.... Terraform acquires a state lock to protect the state from being written by multiple users at the same time. Please resolve the issue above and try again. For most commands, you can disable locking with the "-lock=false" flag, but this is not recommended. $ $ terraform force-unlock cece51d7-ee71-0f70-d6cc-0830631ed672 2019/10/16 13:13:04 [INFO] Terraform version: 0.11.11 ac4fff416318bf0915a0ab80e062a99ef3724334 2019/10/16 13:13:04 [INFO] Go runtime version: go1.11.1 2019/10/16 13:13:04 [INFO] CLI args: []string{"/usr/local/bin/terraform", "force-unlock", "cece51d7-ee71-0f70-d6cc-0830631ed672"} ...edited... Do you really want to force-unlock? Terraform will remove the lock on the remote state. This will allow local Terraform commands to modify this state, even though it may be still be in use. Only 'yes' will be accepted to confirm. Enter a value: yes Terraform state has been successfully unlocked! The state has been unlocked, and Terraform commands should now be able to obtain a new lock on the remote state. $ This page has been accessed for:- \\ Today: {{counter|today}} \\ Yesterday: {{counter|yesterday}} \\ Until now: {{counter|total}} \\