Project 01 — Terraform Local

Guided practice44 min
Duration
60 to 90 min (including 30 min of workstation setup the first time)
Module
1/7
You will build
a first complete Terraform project on your machine, a message.txt file created, modified then destroyed by Terraform, and a workstation ready for the cloud projects (Terraform, AWS CLI, lab AWS account)
Deliverable
the projet-01-local folder with its main.tf, the output of terraform state list before and after destroy, and three lines of your own on the role of terraform.tfstate
Cost
none, the local provider calls no cloud API

How to read this page. Each section is collapsed under its title: click "Show …" to open it, close it when you are done. Reading order: Objective, then In short (all the commands, to copy in order), then The code (the main.tf file explained line by line), then Terraform, in the terminal (T1 to T13, one command at a time with its real output). The detailed walkthrough, with the tool installation, the AWS account preparation and the expected output of each step, is in the appendices: Appendix A for Windows (PowerShell), Appendix B for Linux, macOS, WSL 2 and Git Bash. Open a single appendix, the one for your system. Appendix C, shared, gathers the cases where things go wrong.

Objective

You join a team that manages its whole infrastructure with Terraform. Before letting you touch the AWS account, your manager asks you one thing: "Show me that you master the full cycle on your machine. One file is enough. I want to see the plan before the apply, the state after, the modification detected, and a clean folder at the end." That is exactly this project. The local provider writes a text file on your disk instead of creating a server, but the commands, the sentences displayed and the files produced are the same as for an S3 bucket or a GitHub repository. Everything you read here, you will read again in the eight following projects.

The ten steps of this diagram are detailed, with the expected output of each command, in Appendix A (Windows) or Appendix B (Linux, macOS, WSL 2, Git Bash) at the bottom of the page. The Terraform commands themselves, identical on every system, are explained one by one in Terraform, in the terminal.

In short: the project's commands

Show the commands

Course kit: https://github.com/hrhouma2/aiopsatlas-terraform-labo-fr

You clone the kit into a lab-terraform folder, you check your tools, you create an empty working folder, you write main.tf in it yourself, then you run the cycle: init, fmt, validate, plan, apply, verification, state, modification, destroy. At the end, terraform state list displays nothing anymore and etat says Ressources encore gérées : 0 (0 attendu à la fin d'une séance). (resources still managed: 0). The kit contains the solution in projets/01-terraform-local/, to open only if you get stuck: the goal is to write the file by hand.

Windows (PowerShell)

powershell
git clone https://github.com/hrhouma2/aiopsatlas-terraform-labo-fr.git lab-terraform
cd lab-terraform
dir                                  # explorer : labo.ps1, labo.sh, README.md, projets\
.\labo.ps1 prerequis                 # attendu : Prérequis : 4 outils requis présents sur 4.
.\labo.ps1 nouveau projet-01-local   # crée travail\projet-01-local
cd travail\projet-01-local
code .                               # VS Code : créer main.tf, coller le code de la section « Le code »
terraform init                       # attendu : Terraform has been successfully initialized!
terraform fmt
terraform validate                   # attendu : Success! The configuration is valid.
terraform plan                       # attendu : Plan: 1 to add, 0 to change, 0 to destroy.
terraform apply                      # répondre yes ; attendu : Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Get-Content .\message.txt            # attendu : Bonjour, ce fichier a été créé avec Terraform.
Get-ChildItem -Force                 # .terraform, .terraform.lock.hcl, main.tf, message.txt, terraform.tfstate
terraform state list                 # attendu : local_file.message
terraform state show local_file.message
# modifier la ligne content dans main.tf (voir T10), puis :
terraform plan                       # attendu : Plan: 1 to add, 0 to change, 1 to destroy.
terraform apply                      # répondre yes ; attendu : Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
Get-Content .\message.txt            # attendu : Deuxième version du fichier créée avec Terraform.
terraform destroy                    # répondre yes ; attendu : Destroy complete! Resources: 1 destroyed.
terraform state list                 # attendu : rien
cd ..\..
.\labo.ps1 etat                      # attendu : Ressources encore gérées : 0 (0 attendu à la fin d'une séance).

If PowerShell refuses .\labo.ps1 ("running scripts is disabled on this system"): Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, answer Y (O on a French system), run again.

Linux, macOS, WSL 2, Git Bash

bash
git clone https://github.com/hrhouma2/aiopsatlas-terraform-labo-fr.git lab-terraform
cd lab-terraform
ls                                   # explorer : labo.ps1, labo.sh, README.md, projets/
./labo.sh prerequis                  # attendu : Prérequis : 4 outils requis présents sur 4.
./labo.sh nouveau projet-01-local    # crée travail/projet-01-local
cd travail/projet-01-local
code .                               # VS Code : créer main.tf, coller le code de la section « Le code »
terraform init                       # attendu : Terraform has been successfully initialized!
terraform fmt
terraform validate                   # attendu : Success! The configuration is valid.
terraform plan                       # attendu : Plan: 1 to add, 0 to change, 0 to destroy.
terraform apply                      # répondre yes ; attendu : Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
cat message.txt                      # attendu : Bonjour, ce fichier a été créé avec Terraform.
ls -la                               # .terraform, .terraform.lock.hcl, main.tf, message.txt, terraform.tfstate
terraform state list                 # attendu : local_file.message
terraform state show local_file.message
# modifier la ligne content dans main.tf (voir T10), puis :
terraform plan                       # attendu : Plan: 1 to add, 0 to change, 1 to destroy.
terraform apply                      # répondre yes ; attendu : Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
cat message.txt                      # attendu : Deuxième version du fichier créée avec Terraform.
terraform destroy                    # répondre yes ; attendu : Destroy complete! Resources: 1 destroyed.
terraform state list                 # attendu : rien
cd ../..
./labo.sh etat                       # attendu : Ressources encore gérées : 0 (0 attendu à la fin d'une séance).

If ./labo.sh answers Permission denied: chmod +x labo.sh, once only.

The code: file by file

Show the main.tf file and its line-by-line explanation

The project fits in a single file, main.tf, to create in travail/projet-01-local/. Type it or paste it exactly like this (it is word for word the kit's solution, projets/01-terraform-local/main.tf):

hcl
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

provider "local" {}

resource "local_file" "message" {
  filename = "${path.module}/message.txt"
  content  = "Bonjour, ce fichier a été créé avec Terraform."
}

Three blocks, from the most general to the most concrete.

LineWhat it tells Terraform
terraform { … }The settings of Terraform itself for this folder.
required_providers { local = { … } }"This project needs a plugin called local."
source = "hashicorp/local"Where to download it: the local provider published by HashiCorp on the Terraform Registry.
version = "~> 2.5"Which version to accept: 2.5 or newer, but not 3.0. On the course machine, init picked v2.9.1.
provider "local" {}"Enable this provider." The braces are empty because the local provider has nothing to configure (no region, no account).
resource "local_file" "message" { … }"Manage an object of type local_file, which I call message in my code." The type comes from the provider (local_ + file); the name is yours. Together they form the address local_file.message.
filename = "${path.module}/message.txt"Where to write the file. path.module is the folder containing this main.tf; Terraform will display it as ./message.txt.
content = "Bonjour, ce fichier a été créé avec Terraform."The text to write inside (French for "Hello, this file was created with Terraform."). This is the line you will change at step T10.
To understand better: why `${path.module}` rather than just `message.txt`?

Terraform runs its commands from the current folder. If you write filename = "message.txt", the file is created where you type terraform apply, which is usually the right folder, but not always (when the code is called as a module from another folder, in Module 5). path.module always designates the folder of the .tf file, wherever Terraform is launched from. The ${…} syntax inserts the value of an expression into a string: "${path.module}/message.txt" becomes "./message.txt".

To understand better: what if I remove the `terraform { required_providers … }` block?

It still works: Terraform infers from the local_file type that it needs the hashicorp/local provider, and init displays Finding latest version of hashicorp/local... instead of Finding hashicorp/local versions matching "~> 2.5".... That is what the two fundamental workshops of this module do, to keep things as short as possible. But without a version constraint, two colleagues who run init six months apart can get two different versions of the provider. In a real project, you always write the block.

Terraform, in the terminal

Show the 13 Terraform commands (T1 to T13)

The commands in this section are typed in the travail/projet-01-local folder, once main.tf is written. They are identical on Windows, Linux and macOS: Terraform is the same program everywhere. The outputs are those of the course machine (Terraform 1.12.2, Windows 11), captured as is; only the identifiers (id=ff6b93dc…) and the fingerprints change from one machine to another. The commands that do depend on the system (reading the file, listing the folder) are in your appendix.

An image to keep for the whole section: Lesson 01 introduced the architect. Each command below is one of their gestures. init opens the construction site, plan writes the estimate, apply has it carried out, state rereads the record, destroy takes everything down.

The rule: a single new thing per command. We start with the command that takes no parameter and touches nothing.

T1. Which version of Terraform?

text
terraform version
text
Terraform v1.12.2
on windows_amd64

What the command asks: "Terraform, which version are you, and on which system?" No parameter, no file read, nothing modified. If this command fails (terraform : Le terme «terraform» n'est pas reconnu… or command not found), nothing else will work: it is the installation or the PATH, see Appendix A.1 or B.1. On the course machine, Terraform sometimes adds two lines Your version of Terraform is out of date!: that is information, not an error.

The familiar equivalent: aws --version, git --version, python --version. Same gesture, same purpose.

T2. Open the construction site

text
terraform init
text
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.1...
- Installed hashicorp/local v2.9.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

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.

What the command asks: "Read my main.tf, download the providers it requires, and prepare this folder for work."

A single new thing: the folder becomes a Terraform project. Read the output line by line:

LineWhat happened
Initializing the backend...The backend is the place where the state will be stored. Nothing is specified in main.tf, so it will be a local file, terraform.tfstate.
Finding hashicorp/local versions matching "~> 2.5"...Terraform read your version constraint and is searching the Registry.
Installing hashicorp/local v2.9.1...It picked the most recent one that satisfies ~> 2.5, and downloads it into .terraform/.
(signed by HashiCorp)The plugin is signed: you do have the real provider.
Terraform has created a lock file .terraform.lock.hclThe chosen version is recorded in a file to keep in Git: the next init, on a colleague's machine, will take the same one.
Terraform has been successfully initialized!The sentence to expect.

Two things appeared in your folder: the hidden .terraform/ directory (the downloaded provider, about 18 MB on the course machine, never to be put in Git) and the .terraform.lock.hcl file (22 lines, to put in Git). You will see them in the appendix, step A.6 or B.6.

The familiar equivalent: npm install or pip install -r requirements.txt. You read a list of dependencies, download, lock the versions (package-lock.json).

To understand better: what happens if I forget `init`?

Terraform refuses to continue and tells you what to do. terraform plan without init answers:

text
Error: Inconsistent dependency lock file

The following dependency selections recorded in the lock file are
inconsistent with the current configuration:
  - provider registry.terraform.io/hashicorp/local: required by this configuration but no version is selected

To make the initial dependency selections that will initialize the dependency
lock file, run:
  terraform init

and terraform validate without init:

text
Error: Missing required provider

This configuration requires provider registry.terraform.io/hashicorp/local,
but that provider isn't available. You may be able to install it
automatically by running:
  terraform init

In both cases, the last line is the solution. init can be rerun safely as many times as you want; it never touches your resources.

T3. Tidy up the code

text
terraform fmt
text

What the command asks: "Realign the spaces and indentation of my .tf files according to the official style."

A single new thing: the empty output. fmt displays the name of each file it modified; if you pasted the code as is, it has nothing to fix and displays nothing. That is not an error, it is the best possible answer. If you had misindented a line, it would have displayed main.tf and rewritten the file.

The familiar equivalent: prettier --write, black, gofmt. The kit checks this point with terraform fmt -check -recursive, which modifies nothing and exits with an error (code 3) if a file is not formatted.

T4. Check the syntax

text
terraform validate
text
Success! The configuration is valid.

What the command asks: "Reread my blocks. Do the argument names exist, are the braces closed, do the references point to something?"

A single new thing: Terraform consults the provider to know the valid arguments of a local_file. That is why validate requires init beforehand. It does not read the state, contacts no API, creates nothing. The sentence to expect: Success! The configuration is valid.

What it catches: contenu instead of content (An argument named "contenu" is not expected here. Did you mean "content"?), a forgotten brace (Error: Unclosed configuration block), an unclosed quote (Error: Unterminated template string), a nonexistent type (The provider hashicorp/local does not support resource type "local_fichier".). Appendix C goes through them one by one.

What it does not catch: a path that does not exist, a bucket name already taken, a bad architecture. validate says "this is correct HCL", not "this is a good idea".

T5. Write the estimate

text
terraform plan
text
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.message will be created
  + resource "local_file" "message" {
      + content              = "Bonjour, ce fichier a été créé avec Terraform."
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "./message.txt"
      + id                   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.

What the command asks: "Compare what my code describes with what you already know, and tell me what you would do, without doing it."

A single new thing: the estimate. Nothing has been created; message.txt still does not exist. Read it from top to bottom:

LineHow to read it
+ createThe legend: in this plan, there are only creations.
# local_file.message will be createdThe address of the resource (type . name) and its fate.
+ resource "local_file" "message" {The whole block is preceded by a +: everything is new.
+ content = "Bonjour, …"An attribute you wrote: Terraform knows its value.
+ content_md5 = (known after apply)An attribute the provider will compute after creation: the fingerprint of the content. Terraform cannot guess it beforehand.
+ directory_permission = "0777"An attribute you did not write: the provider has a default value.
+ filename = "./message.txt"Your ${path.module}/message.txt, resolved.
Plan: 1 to add, 0 to change, 0 to destroy.The line to read first, always. One creation, no modification, no destruction.
Note: You didn't use the -out option…Information: this estimate is not saved; apply will recompute one. Irrelevant here.

The familiar equivalent: git diff before git commit, or the "simulation" mode (--dry-run, -WhatIf) of a script. The difference: a --dry-run script shows you what it is going to do; terraform plan shows you what is missing between the code and reality, after rereading reality.

To understand better: why so many lines for a text file?

For a local_file, the local provider exposes everything it knows how to compute: six fingerprints of the content (md5, sha1, sha256, sha512, and two base64-encoded versions), the permissions, the identifier. You wrote only two of them (filename, content); the others are attributes the provider fills in. It is the same for an S3 bucket: you write three lines, the plan displays thirty (ARN, region, owner identifier…). Learn to spot yours and skim the others, except the last Plan: line, which is never skimmed.

T6. Have the estimate carried out

text
terraform apply
text
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.message will be created
  + resource "local_file" "message" {
      + content              = "Bonjour, ce fichier a été créé avec Terraform."

      + filename             = "./message.txt"
      + id                   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

local_file.message: Creating...
local_file.message: Creation complete after 0s [id=ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

What the command asks: "Recompute the estimate, show it to me, and if I answer yes, do it."

A single new thing: the question. Terraform stops at Enter a value: and waits. Type yes in full, then Enter. y, Y, oui or an empty line give Apply cancelled. and nothing is touched: it is a protection, not a bug. Then three lines: Creating..., Creation complete after 0s [id=…] (the identifier the plan announced as (known after apply) is now known), and the sentence to expect: Apply complete! Resources: 1 added, 0 changed, 0 destroyed. The three numbers are those of the plan.

Now message.txt exists in your folder, and a new file has appeared next to it: terraform.tfstate. Go check it with your appendix (A.6 or B.6) before continuing: the file does contain Bonjour, ce fichier a été créé avec Terraform.

The familiar equivalent: a script that writes the file, Set-Content or echo > message.txt. The difference: the script writes and forgets; Terraform writes and records in its ledger that it wrote, under which identifier, with which content.

T7. Reread the record: the list

text
terraform state list
text
local_file.message

What the command asks: "List everything you manage in this folder."

A single new thing: the state can be read. One line, one resource: the address local_file.message, the same as in the plan. This line proves that Terraform tied the block of your code to the real file: that is what will let it modify or destroy it later without you having to tell it which one again.

Before apply, the same command answered No state file was found!: the record did not exist yet. After destroy (T12), it will answer nothing at all.

The familiar equivalent: docker ps, kubectl get pods, aws s3 ls. "What is running, what exists?" Except that state list does not go look at reality: it reads its notebook.

T8. Reread the record: the detail

text
terraform state show local_file.message
text
# local_file.message:
resource "local_file" "message" {
    content              = "Bonjour, ce fichier a été créé avec Terraform."
    content_base64sha256 = "G+tDdGue1Lx5hU3LpqBWf3y3haa0ggckj6t/9bj5ocs="
    content_base64sha512 = "oB7gPsGHBDNnXe9Hzx4zQ3/nAF1kM9wlfDV/OdMjPybCPurPeN4P8myOoZxFg3dwvs+xLBRJxo4Q3H0qGS8l5g=="
    content_md5          = "6daf774f0eb6d3da439c871afec7cf90"
    content_sha1         = "ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45"
    content_sha256       = "1beb43746b9ed4bc79854dcba6a0567f7cb785a6b48207248fab7ff5b8f9a1cb"
    content_sha512       = "a01ee03ec1870433675def47cf1e33437fe7005d6433dc257c357f39d3233f26c23eeacf78de0ff26c8ea19c45837770becfb12c1449c68e10dc7d2a192f25e6"
    directory_permission = "0777"
    file_permission      = "0777"
    filename             = "./message.txt"
    id                   = "ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45"
}

What the command asks: "Show me everything you know about the resource local_file.message."

A single new thing: the command takes a parameter, the address read in T7. Compare with the plan of T5: all the (known after apply) values are now filled in. The id is the SHA-1 fingerprint of the content, the same value as content_sha1 and as the [id=…] displayed by apply. It is the architect's record, room by room.

If you get the address wrong (local_file.mesage), Terraform answers No instance found for the given address! and sends you back to terraform state list.

To understand better: what `terraform.tfstate` contains, and why you never edit it

The terraform.tfstate file is JSON: a serial number ("serial"), the Terraform version, and a "resources" list where each resource has exactly the attributes that state show displays. You can open it to read it. You never modify it by hand: one misplaced comma, and Terraform no longer recognizes what it built, or believes it manages something that no longer exists. To act on the state, there are commands (terraform state rm, terraform state mv, terraform import) that you will see in Module 4.

Two more rules, starting today. The state can contain secrets (here, the text of the file; elsewhere, a database password): it is not shared by message nor committed to Git. And it is the memory of Terraform: if you delete it, Terraform believes nothing exists and offers to recreate everything, while the resources are still there and, in the cloud, still billed.

T9. Ask for the outputs of a project that has none

text
terraform output
text
Warning: No outputs found

The state file either has no outputs defined, or all the defined outputs are
empty. Please define an output in your configuration with the `output`
keyword and run `terraform refresh` for it to become available. If you are
using interpolation, please verify the interpolated value is not empty. You
can use the `terraform console` command to assist.

What the command asks: "Display the values the code chose to expose with output blocks."

A single new thing: a warning (Warning), not an error. Your main.tf has no output block, so Terraform has nothing to display and tells you so. You will write your first outputs in Project 02; here, the command is used to see the difference between "it failed" (Error:) and "there is nothing to show" (Warning:).

T10. Modify the code and reread the estimate

Open main.tf, replace the content line with this one, save:

hcl
  content  = "Deuxième version du fichier créée avec Terraform."

Then:

text
terraform plan
text
local_file.message: Refreshing state... [id=ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # local_file.message must be replaced
-/+ resource "local_file" "message" {
      ~ content              = "Bonjour, ce fichier a été créé avec Terraform." -> "Deuxième version du fichier créée avec Terraform." # forces replacement
      ~ content_base64sha256 = "G+tDdGue1Lx5hU3LpqBWf3y3haa0ggckj6t/9bj5ocs=" -> (known after apply)
      ~ content_base64sha512 = "oB7gPsGHBDNnXe9Hzx4zQ3/nAF1kM9wlfDV/OdMjPybCPurPeN4P8myOoZxFg3dwvs+xLBRJxo4Q3H0qGS8l5g==" -> (known after apply)
      ~ content_md5          = "6daf774f0eb6d3da439c871afec7cf90" -> (known after apply)
      ~ content_sha1         = "ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45" -> (known after apply)
      ~ content_sha256       = "1beb43746b9ed4bc79854dcba6a0567f7cb785a6b48207248fab7ff5b8f9a1cb" -> (known after apply)
      ~ content_sha512       = "a01ee03ec1870433675def47cf1e33437fe7005d6433dc257c357f39d3233f26c23eeacf78de0ff26c8ea19c45837770becfb12c1449c68e10dc7d2a192f25e6" -> (known after apply)
      ~ id                   = "ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45" -> (known after apply)
        # (3 unchanged attributes hidden)
    }

Plan: 1 to add, 0 to change, 1 to destroy.

What the command asks: the same thing as in T5. But this time Terraform has a record to compare against.

A single new thing: the -/+ symbol, "destroy then recreate". Three clues tell you, and you must know how to spot them:

ClueWhereWhat it says
must be replacedthe title # local_file.message must be replacedThis is not will be updated in-place.
# forces replacementat the end of the line ~ content = "…" -> "…"This attribute is the one forcing the replacement. The ~ in front says the value changes; the comment says this change cannot be done in place.
Plan: 1 to add, 0 to change, 1 to destroy.the last lineOne destruction and one creation, zero modification.

Look also at the first line, which is new: Refreshing state... [id=…]. Before comparing, Terraform went to reread the real file to check that it still matches its record. That is where it would detect drift (Lesson 01), if you had modified message.txt by hand.

The essential difference between ~ and -/+. For a text file, being replaced or modified in place amounts to the same thing: at the end, the content is the new one. For a database, a disk or a user, -/+ means loss of the object and of its identifier: the disk's data, the user's password, the server's IP address. The provider decides, attribute by attribute, what forces a replacement; you read it in the plan before saying yes. The local provider replaces the file as soon as content changes: never a lone ~ for this attribute.

T11. Apply the replacement

text
terraform apply
text

Plan: 1 to add, 0 to change, 1 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

local_file.message: Destroying... [id=ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45]
local_file.message: Destruction complete after 0s
local_file.message: Creating...
local_file.message: Creation complete after 0s [id=1df9196ac94445f0943c35b75c8c2e1bdf3e0cd6]

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

What the command asks: the same thing as in T6.

Nothing new in the command; everything is in the output. Four action lines instead of two: Destroying..., Destruction complete, then Creating..., Creation complete. The order is that of the -/+ symbol: the minus first, the plus afterwards. And the identifier changed (ff6b93dc… before, 1df9196a… after): it is a different object, not the same one modified. Check the new content with your appendix (A.8 or B.8): Deuxième version du fichier créée avec Terraform.

Summary of T5 to T11: you have seen the two estimates you will reread throughout your career: + (create) and -/+ (replace). The third, ~ (modify in place), does not exist for the content of a local_file; you will meet it in the cloud projects, for example when a tag changes on an AWS resource.

T12. Take everything down

text
terraform destroy
text
local_file.message: Refreshing state... [id=1df9196ac94445f0943c35b75c8c2e1bdf3e0cd6]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # local_file.message will be destroyed
  - resource "local_file" "message" {
      - content              = "Deuxième version du fichier créée avec Terraform." -> null
      - content_base64sha256 = "36Hz+L76pjLbvY4LIzPfw+UsqNneFneSfFGjtfRLxkI=" -> null

      - filename             = "./message.txt" -> null
      - id                   = "1df9196ac94445f0943c35b75c8c2e1bdf3e0cd6" -> null
    }

Plan: 0 to add, 0 to change, 1 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

local_file.message: Destroying... [id=1df9196ac94445f0943c35b75c8c2e1bdf3e0cd6]
local_file.message: Destruction complete after 0s

Destroy complete! Resources: 1 destroyed.

What the command asks: "Destroy everything you manage in this folder, and show me the estimate first."

A single new thing: the - symbol on its own, and a more serious question: Do you really want to destroy all resources? … There is no undo. Every attribute goes to -> null: there will be nothing left. The sentence to expect: Destroy complete! Resources: 1 destroyed. The message.txt file has disappeared from your folder.

This is the command that closes every session of this course. Here it erases a text file; in Project 03 it will delete a bucket which, otherwise, would remain billed.

T13. Check that the record is empty

text
terraform state list
text

What the command asks: the same as in T7.

Nothing new, and an empty output: Terraform no longer manages anything in this folder. The terraform.tfstate file still exists, but its "resources" list is [], and a terraform.tfstate.backup has appeared next to it (the version from before the destroy, which Terraform keeps out of caution). Your appendix (A.9 and A.10, or B.9 and B.10) has you list the folder and run etat for the final proof: Ressources encore gérées : 0 (0 attendu à la fin d'une séance).

Summary of T1 to T13: init once, then fmt, validate, plan, apply, state list, state show, a modification plan and apply, destroy, state list. Thirteen commands, five sentences to recognize: successfully initialized!, The configuration is valid., Plan: N to add, N to change, N to destroy., Apply complete!, Destroy complete!.

The message to take away. A PowerShell or bash script would have written message.txt in one line, and deleted it in another. What the script does not do and what you saw Terraform do: announce before acting (plan), ask for explicit agreement (yes), record what it created (state list), reread reality before every decision (Refreshing state...), distinguish modifying from replacing (-/+), and know how to undo everything without being told what (destroy).

The AWS console, in the browser: preparing the account for the next projects

Show the AWS account preparation (group, IAM user, Access Key)

Project 01 does not touch AWS. But Project 03 creates a real S3 bucket, and you will need an account that is ready. This section is done in the browser, it is identical on Windows, Linux and macOS, and you can do it before or after the Terraform cycle of this project. The aws configure and aws sts get-caller-identity commands that go with it are in your appendix (A.2 or B.2).

If you work in AWS Academy, AWS Educate, a sandbox provided by your school or a temporary lab account: use the credentials provided by the platform and go directly to step A.2 or B.2 (the "Academy" case). Do not create an IAM user if your teacher already gives you temporary access.

Open the AWS console

  1. Open the browser and go to https://aws.amazon.com/.
  2. Click Sign in to the Console.
  3. Sign in with the lab AWS account.
  4. Check the region at the top right. For Canada, choose ca-central-1 unless the lab imposes another one.

Do not work with the root account

The root account is only used to create the account, manage billing and carry out sensitive operations. Enable MFA (two-factor authentication) on the root account, then create a separate user for the lab work, below.

Create an administrator group for the lab

  1. In the AWS search bar, type IAM and open the IAM service.
  2. Left menu: User groupsCreate group.
  3. Group name: terraform-admins.
  4. In the permissions search, type AdministratorAccess, tick the AdministratorAccess policy.
  5. Create group.

For a lab only. AdministratorAccess gives full access to every service and resource in the account. It is convenient for learning; it is not a security model for production, where you give Terraform only the permissions it needs (Module 4).

Create an IAM user for the lab

  1. In IAM: UsersCreate user.
  2. Name: terraform-admin.
  3. If you also want to sign in to the console with this user, tick Provide user access to the AWS Management Console and choose a temporary or generated password. Tick User must create a new password at next sign-in if you are handing the account to someone.
  4. NextAdd user to group → tick terraform-adminsNextCreate user.

Create an Access Key for AWS CLI

  1. In IAM: Users → click terraform-admin.
  2. Security credentials tab, Access keys section → Create access key.
  3. Choose Command Line Interface (CLI), tick the confirmation if AWS displays a recommendation, Next.
  4. Description tag value: aws-cli-terraform-labCreate access key.
  5. Download the .csv file or copy Access key and Secret access key into a password manager. The Secret access key will never be displayed again.

Never do this. Never paste an Access Key, a Secret Access Key, a session token, an .env file or a terraform.tfstate into GitHub, Word, Teams, Discord, Slack, a ticket or an email. These elements give access to the account. If a key leaks: IAM → the user → Security credentialsDeactivate then Delete, and you create another one.

Bonus challenge (optional)

Before rerunning terraform destroy a second time on a folder that has already been destroyed, write on paper what the last line of the plan will say. Then run it. Next, recreate the file with terraform apply, open message.txt in VS Code, add a word by hand, save, and type terraform plan: what does the Refreshing state... line say, which symbol appears, and what is this gap called in Lesson 01? Finish with destroy: the folder must return to Ressources encore gérées : 0.

Appendix A — Detailed walkthrough on Windows (PowerShell)

Show the Windows walkthrough (A.0 to A.10)

All the commands in this appendix are typed in PowerShell (Windows Terminal or PowerShell 7), with .\labo.ps1 …. The reproduced outputs are those of the course machine, on Windows 11 and Terraform 1.12.2.

A.0 — Before starting

  • Have read Lessons 01 and 02 of the module.
  • Git installed (git --version answers), VS Code installed with the code command available in PowerShell (otherwise: VS Code → Ctrl+Shift+P → "Shell Command: Install 'code' command in PATH", or reinstall ticking "Add to PATH").
  • If PowerShell refuses to run .\labo.ps1 ("running scripts is disabled on this system"): Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, answer Y (O on a French system), run again. Once per machine.
  • At the end, .\labo.ps1 etat must display Ressources encore gérées : 0 (0 attendu à la fin d'une séance).

A.1 — Install Terraform

The simplest way, with the Windows package manager:

powershell
winget install Hashicorp.Terraform

Close PowerShell, reopen it, then:

powershell
terraform version

Checkpoint:

text
Terraform v1.12.2
on windows_amd64

Your version may be more recent; anything 1.6 or later is fine. If Terraform adds Your version of Terraform is out of date!, that is information, not an error.

Without winget, with the official binary: go to https://developer.hashicorp.com/terraform/install, choose Windows, download the AMD64 version, unzip the .zip, create the folder C:\terraform, move terraform.exe into it, then add C:\terraform to the PATH: Start menu → "environment variables" → Edit the system environment variablesEnvironment Variables → under System variables, select PathEditNewC:\terraformOK. Close all PowerShell windows, reopen, retype terraform version.

If you see something else: terraform : Le terme «terraform» n'est pas reconnu comme nom d'applet de commande… (the term is not recognized as a cmdlet name) → Terraform is not in the PATH, or PowerShell was not restarted after the installation. Appendix C.

A.2 — Install and configure AWS CLI version 2 (for the next projects)

Project 01 does not use AWS; this step prepares Project 03. You can do it now or postpone it.

  1. Go to https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html, download the Windows 64-bit MSI of AWS CLI version 2.
  2. Run the .msi: Next, accept the license, keep the default folder, Install.
  3. Close then reopen PowerShell.
powershell
aws --version

Checkpoint: a version starting with aws-cli/2 (on the course machine: aws-cli/2.22.18). If you see aws-cli/1, that is the old version: uninstall it.

Then configure the keys created in the section The AWS console, in the browser:

powershell
aws configure
text
AWS Access Key ID [None]: TON_ACCESS_KEY_ID
AWS Secret Access Key [None]: TA_SECRET_ACCESS_KEY
Default region name [None]: ca-central-1
Default output format [None]: json

Check that the CLI does talk to AWS:

powershell
aws sts get-caller-identity

Example answer (the values are those of your account):

json
{
    "UserId": "AIDA...",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:user/terraform-admin"
}

AWS Academy or temporary sandbox case: open the lab, click AWS Details or Credentials, copy Access Key, Secret Key and Session Token (mandatory with temporary access), then:

powershell
aws configure set aws_access_key_id TON_ACCESS_KEY_ID
aws configure set aws_secret_access_key TA_SECRET_ACCESS_KEY
aws configure set aws_session_token TON_SESSION_TOKEN
aws configure set region ca-central-1
aws configure set output json
aws sts get-caller-identity

A.3 — Clone the kit and check the prerequisites

Go to the folder where you keep your projects (for example C:\Users\<you>\Documents), then:

powershell
git clone https://github.com/hrhouma2/aiopsatlas-terraform-labo-fr.git lab-terraform
cd lab-terraform
dir

Checkpoint: dir lists projets, labo.ps1, labo.sh, README.md (and .gitattributes, .gitignore with dir -Force). Then:

powershell
.\labo.ps1 prerequis
text
OK      terraform 1.12.2
OK      aws 2.22.18
OK      git 2.49.0.windows.1
OK      code 1.135.0
OK      gh 2.81.0 (optionnel)
ABSENT  az (optionnel, projet 08B seulement)
OK      gcloud 544.0.0 (optionnel)
Prérequis : 4 outils requis présents sur 4.

The four required tools are terraform, aws, git and code. gh, az and gcloud are optional and can be ABSENT without consequence before Module 3. If aws is ABSENT because you postponed A.2, the last line says 3 outils requis présents sur 4 (3 required tools present out of 4): you can still continue this project.

A.4 — Create the working folder and open it in VS Code

powershell
.\labo.ps1 nouveau projet-01-local
text
Dossier travail\projet-01-local créé (ignoré par Git). Tapez :
  cd travail\projet-01-local

Do what the script tells you (the folder was created, ignored by Git; type cd travail\projet-01-local), then open the folder in VS Code:

powershell
cd travail\projet-01-local
code .

The travail\ folder is ignored by Git: you can write in it freely without dirtying the kit. The solution stays in projets\01-terraform-local\.

If code . does nothing: open VS Code by hand, FileOpen Folder, and choose lab-terraform\travail\projet-01-local.

A.5 — Create main.tf

In VS Code: New File icon in the left explorer, name the file main.tf (careful: not main.tf.txt, VS Code does not add an extension but Windows Notepad would), paste the code from the section The code: file by file, save with Ctrl+S.

Check from PowerShell:

powershell
Get-Content .\main.tf

Checkpoint: the fifteen lines of the file, from terraform { to }. Then:

powershell
Get-ChildItem

A single entry: main.tf. If you see main.tf.txt, rename it: Rename-Item main.tf.txt main.tf.

A.6 — Initialize, format, validate, plan, apply

Run T2 to T6 from the section Terraform, in the terminal:

powershell
terraform init
terraform fmt
terraform validate
terraform plan
terraform apply

Answer yes to the apply question. Checkpoints, in order: Terraform has been successfully initialized!, nothing for fmt, Success! The configuration is valid., Plan: 1 to add, 0 to change, 0 to destroy., Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Check what appeared in the folder:

powershell
Get-ChildItem -Force
text
.terraform
.terraform.lock.hcl
main.tf
message.txt
terraform.tfstate

(-Force also displays hidden entries, those whose name starts with a dot; without it, you would only see main.tf, message.txt and terraform.tfstate.) Five entries:

EntryWho created itWhat it is
.terraforminitHidden folder, the downloaded provider. Never in Git.
.terraform.lock.hclinitThe chosen provider versions. In Git.
main.tfyouThe code.
message.txtapplyThe resource.
terraform.tfstateapplyThe state, the record. Never in Git, never shared.

Read the created file:

powershell
Get-Content .\message.txt
text
Bonjour, ce fichier a été créé avec Terraform.

You can also check it in Windows Explorer: open the folder lab-terraform\travail\projet-01-local, double-click message.txt.

A.7 — Read the state

Run T7, T8 and T9:

powershell
terraform state list
terraform state show local_file.message
terraform output

Checkpoints: local_file.message; the full block with filename = "./message.txt" and a forty-character id; Warning: No outputs found.

If you are curious, look at the state itself, without modifying it:

powershell
Get-Content .\terraform.tfstate

JSON with "serial", "terraform_version": "1.12.2" and a "resources" list containing local_file.message. Close it. It is never edited by hand.

A.8 — Modify the content and observe the replacement

In VS Code, replace the content line of main.tf with:

hcl
  content  = "Deuxième version du fichier créée avec Terraform."

Save, then run T10 and T11:

powershell
terraform plan
terraform apply

Answer yes. Checkpoints: # local_file.message must be replaced, # forces replacement at the end of the content line, Plan: 1 to add, 0 to change, 1 to destroy., then Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

powershell
Get-Content .\message.txt
text
Deuxième version du fichier créée avec Terraform.

A.9 — Destroy and check

Run T12 and T13:

powershell
terraform destroy
terraform state list

Answer yes. Checkpoints: Destroy complete! Resources: 1 destroyed., then an empty output for state list.

powershell
Get-ChildItem -Force
text
.terraform
.terraform.lock.hcl
main.tf
terraform.tfstate
terraform.tfstate.backup

message.txt has disappeared. terraform.tfstate is still there but no longer contains any resource; terraform.tfstate.backup is the copy from before the destroy. .terraform and .terraform.lock.hcl remain: that is normal, they will be useful if you rerun apply.

A.10 — The kit's counter

Go back up to the root of the kit and run etat:

powershell
cd ..\..
.\labo.ps1 etat
text
travail projet-01-local : aucune ressource
Ressources encore gérées : 0 (0 attendu à la fin d'une séance).

(No resource in projet-01-local; resources still managed: 0, 0 expected at the end of a session.) If you had forgotten the destroy, the output would say travail projet-01-local : 1 ressource dans le state then Ressources encore gérées : 1 (0 attendu à la fin d'une séance).: this is the guardrail you will run at the end of every session, once resources are billed.

To start over from scratch, rather than cleaning the folder: .\labo.ps1 nouveau projet-01-reprise, and you start again from an empty folder.

Appendix B — Detailed walkthrough on Linux, macOS, WSL 2 and Git Bash

Show the Linux, macOS, WSL 2 and Git Bash walkthrough (B.0 to B.10)

All the commands in this appendix are typed in a bash terminal (or zsh on macOS), with ./labo.sh …. The Terraform outputs are those of the course machine (Terraform 1.12.2); the ls -la outputs were captured under Git Bash, the permission, owner and date columns will differ on your machine.

B.0 — Before starting

  • Have read Lessons 01 and 02 of the module.
  • Git installed (git --version answers), VS Code installed with the code command available in the terminal (on macOS: VS Code → Cmd+Shift+P → "Shell Command: Install 'code' command in PATH").
  • If ./labo.sh answers Permission denied: chmod +x labo.sh, once only.
  • At the end, ./labo.sh etat must display Ressources encore gérées : 0 (0 attendu à la fin d'une séance).

B.1 — Install Terraform

Follow the official page https://developer.hashicorp.com/terraform/install for your system. In summary:

  • Ubuntu, Debian, WSL 2 Ubuntu: add the HashiCorp repository (apt with the GPG key, commands copied from the official page), then sudo apt-get install terraform.
  • macOS with Homebrew: brew tap hashicorp/tap then brew install hashicorp/tap/terraform.
  • Any distribution, binary: download the Linux AMD64 (or ARM64) .zip, unzip, then sudo mv terraform /usr/local/bin/.
  • Git Bash on Windows: install Terraform as in Appendix A.1 (winget), Git Bash sees it in the PATH.

Close the terminal, reopen it, then:

bash
terraform version

Checkpoint: Terraform v1.12.2 (or newer) followed by on linux_amd64, on darwin_arm64 or on windows_amd64 depending on your machine. Anything 1.6 or later is fine.

If you see something else: bash: terraform: command not found → the binary is not in a PATH folder (echo $PATH), or the terminal was not reopened. Appendix C.

B.2 — Install and configure AWS CLI version 2 (for the next projects)

Project 01 does not use AWS; this step prepares Project 03. You can do it now or postpone it.

Follow https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html for your system (.zip package with ./aws/install on Linux, .pkg on macOS). Then:

bash
aws --version

Checkpoint: a version starting with aws-cli/2. If you see aws-cli/1, that is the old version: uninstall it.

Configure the keys created in the section The AWS console, in the browser:

bash
aws configure
text
AWS Access Key ID [None]: TON_ACCESS_KEY_ID
AWS Secret Access Key [None]: TA_SECRET_ACCESS_KEY
Default region name [None]: ca-central-1
Default output format [None]: json

Check that the CLI does talk to AWS:

bash
aws sts get-caller-identity

Example answer (the values are those of your account):

json
{
    "UserId": "AIDA...",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:user/terraform-admin"
}

AWS Academy or temporary sandbox case: open the lab, click AWS Details or Credentials, copy Access Key, Secret Key and Session Token (mandatory with temporary access), then:

bash
aws configure set aws_access_key_id TON_ACCESS_KEY_ID
aws configure set aws_secret_access_key TA_SECRET_ACCESS_KEY
aws configure set aws_session_token TON_SESSION_TOKEN
aws configure set region ca-central-1
aws configure set output json
aws sts get-caller-identity

B.3 — Clone the kit and check the prerequisites

Go to the folder where you keep your projects (for example ~/projets), then:

bash
git clone https://github.com/hrhouma2/aiopsatlas-terraform-labo-fr.git lab-terraform
cd lab-terraform
ls

Checkpoint: ls lists labo.ps1 labo.sh projets README.md (and .gitattributes, .gitignore with ls -a). Then:

bash
./labo.sh prerequis
text
OK  terraform 1.12.2
OK  aws 2.22.18
OK  git 2.49.0.windows.1
OK  code 1.135.0
OK  gh 2.81.0 (optionnel)
ABSENT  az (optionnel, projet 08B seulement)
OK  gcloud 544.0.0 (optionnel)
Prérequis : 4 outils requis présents sur 4.

(Output captured under Git Bash, hence the git 2.49.0.windows.1; on Linux you will read your own Git version.) The four required tools are terraform, aws, git and code. gh, az and gcloud are optional and can be ABSENT without consequence before Module 3. If aws is ABSENT because you postponed B.2, the last line says 3 outils requis présents sur 4 (3 required tools present out of 4): you can still continue this project.

B.4 — Create the working folder and open it in VS Code

bash
./labo.sh nouveau projet-01-local
text
Dossier travail/projet-01-local créé (ignoré par Git). Tapez :
  cd travail/projet-01-local

Do what the script tells you (the folder was created, ignored by Git; type cd travail/projet-01-local), then open the folder in VS Code:

bash
cd travail/projet-01-local
code .

The travail/ folder is ignored by Git: you can write in it freely without dirtying the kit. The solution stays in projets/01-terraform-local/.

If code . does nothing: open VS Code by hand, FileOpen Folder, and choose lab-terraform/travail/projet-01-local.

B.5 — Create main.tf

In VS Code: New File icon in the left explorer, name the file main.tf, paste the code from the section The code: file by file, save with Ctrl+S (Cmd+S on macOS).

Third path, without an editor, if you prefer the terminal:

bash
cat > main.tf <<'EOF'
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

provider "local" {}

resource "local_file" "message" {
  filename = "${path.module}/message.txt"
  content  = "Bonjour, ce fichier a été créé avec Terraform."
}
EOF

(The quotes around 'EOF' prevent bash from interpreting ${path.module}: without them, the filename line would be emptied.) Check:

bash
cat main.tf
ls -la
text
total 1
drwxr-xr-x 1 rehou 197609   0 sept. 15 14:47 .
drwxr-xr-x 1 rehou 197609   0 sept. 15 14:47 ..
-rw-r--r-- 1 rehou 197609 286 sept. 15 14:47 main.tf

Checkpoint: the fifteen lines of the file, and a single entry in the folder, main.tf, of about 286 bytes.

B.6 — Initialize, format, validate, plan, apply

Run T2 to T6 from the section Terraform, in the terminal:

bash
terraform init
terraform fmt
terraform validate
terraform plan
terraform apply

Answer yes to the apply question. Checkpoints, in order: Terraform has been successfully initialized!, nothing for fmt, Success! The configuration is valid., Plan: 1 to add, 0 to change, 0 to destroy., Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Check what appeared in the folder:

bash
ls -la
text
total 14
drwxr-xr-x 1 rehou 197609    0 sept. 15 14:47 .
drwxr-xr-x 1 rehou 197609    0 sept. 15 14:47 ..
drwxr-xr-x 1 rehou 197609    0 sept. 15 14:47 .terraform
-rw-r--r-- 1 rehou 197609 1257 sept. 15 14:47 .terraform.lock.hcl
-rw-r--r-- 1 rehou 197609  286 sept. 15 14:47 main.tf
-rw-r--r-- 1 rehou 197609   50 sept. 15 14:47 message.txt
-rw-r--r-- 1 rehou 197609 1667 sept. 15 14:47 terraform.tfstate

(-a also displays hidden entries, those whose name starts with a dot; -l gives the detail.) Five entries in addition to . and ..:

EntryWho created itWhat it is
.terraforminitHidden folder, the downloaded provider. Never in Git.
.terraform.lock.hclinitThe chosen provider versions. In Git.
main.tfyouThe code.
message.txtapplyThe resource. 50 bytes: the text, without a trailing newline.
terraform.tfstateapplyThe state, the record. Never in Git, never shared.

Read the created file:

bash
cat message.txt
text
Bonjour, ce fichier a été créé avec Terraform.

(The file has no trailing newline: your prompt may appear glued to the end of the sentence. That is normal.)

B.7 — Read the state

Run T7, T8 and T9:

bash
terraform state list
terraform state show local_file.message
terraform output

Checkpoints: local_file.message; the full block with filename = "./message.txt" and a forty-character id; Warning: No outputs found.

If you are curious, look at the state itself, without modifying it:

bash
cat terraform.tfstate

JSON with "serial", "terraform_version": "1.12.2" and a "resources" list containing local_file.message. Close it. It is never edited by hand.

B.8 — Modify the content and observe the replacement

In VS Code, replace the content line of main.tf with:

hcl
  content  = "Deuxième version du fichier créée avec Terraform."

Save, then run T10 and T11:

bash
terraform plan
terraform apply

Answer yes. Checkpoints: # local_file.message must be replaced, # forces replacement at the end of the content line, Plan: 1 to add, 0 to change, 1 to destroy., then Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

bash
cat message.txt
text
Deuxième version du fichier créée avec Terraform.

B.9 — Destroy and check

Run T12 and T13:

bash
terraform destroy
terraform state list

Answer yes. Checkpoints: Destroy complete! Resources: 1 destroyed., then an empty output for state list.

bash
ls -la
text
total 17
drwxr-xr-x 1 rehou 197609    0 sept. 15 14:47 .
drwxr-xr-x 1 rehou 197609    0 sept. 15 14:47 ..
drwxr-xr-x 1 rehou 197609    0 sept. 15 14:47 .terraform
-rw-r--r-- 1 rehou 197609 1257 sept. 15 14:47 .terraform.lock.hcl
-rw-r--r-- 1 rehou 197609  286 sept. 15 14:47 main.tf
-rw-r--r-- 1 rehou 197609  181 sept. 15 14:47 terraform.tfstate
-rw-r--r-- 1 rehou 197609 1667 sept. 15 14:47 terraform.tfstate.backup

message.txt has disappeared. terraform.tfstate went from 1667 to 181 bytes: it no longer contains any resource; terraform.tfstate.backup (1667 bytes) is the copy from before the destroy. .terraform and .terraform.lock.hcl remain: that is normal, they will be useful if you rerun apply.

bash
cat terraform.tfstate
json
{
  "version": 4,
  "terraform_version": "1.12.2",
  "serial": 3,
  "lineage": "9ddb3372-803f-0964-7665-8413b8f65ee1",
  "outputs": {},
  "resources": [],
  "check_results": null
}

"resources": []: the record is empty. Your lineage (the unique identifier of this state) will be different.

B.10 — The kit's counter

Go back up to the root of the kit and run etat:

bash
cd ../..
./labo.sh etat
text
travail projet-01-local : aucune ressource
Ressources encore gérées : 0 (0 attendu à la fin d'une séance).

(No resource in projet-01-local; resources still managed: 0, 0 expected at the end of a session.) If you had forgotten the destroy, the output would say travail projet-01-local : 1 ressource dans le state then Ressources encore gérées : 1 (0 attendu à la fin d'une séance).: this is the guardrail you will run at the end of every session, once resources are billed.

To start over from scratch, rather than cleaning the folder: ./labo.sh nouveau projet-01-reprise, and you start again from an empty folder.

Appendix C — When things go wrong (all systems)

Show the troublesome cases

Each case: the exact message → the cause → the fix.

  • terraform plan answers Error: Inconsistent dependency lock filerequired by this configuration but no version is selected → You did not run terraform init in this folder (or you added a provider since). The message ends with the solution: terraform init, then run again.

  • terraform validate answers Error: Missing required providerYou may be able to install it automatically by running: terraform init → Same cause, same fix: terraform init.

  • Error: Unsupported argumentAn argument named "contenu" is not expected here. Did you mean "content"? → Typo in an argument name. Terraform often suggests the right word. Fix, save again, rerun validate.

  • Error: Unclosed configuration blockThere is no closing brace for this block before the end of the file. → A } brace is missing. Count them: each { has its }. In main.tf, there are three nested pairs for the terraform block, one for provider, one for resource.

  • Error: Invalid multi-line string then Error: Unterminated template stringNo closing marker was found for the string. → A " quote is missing at the end of a value. The faulty line is quoted (3: content = "Bonjour Terraform).

  • Error: Invalid resource typeThe provider hashicorp/local does not support resource type "local_fichier". → The resource type does not exist in this provider. The local provider's types are called local_file and local_sensitive_file; the provider documentation on the Registry lists them.

  • Error: Invalid Attribute CombinationNo attribute specified when one (and only one) of [content,sensitive_content,content_base64] is required (repeated four times) → You removed the content line: a local_file must say what it contains. Put it back.

  • terraform validate says Success! but terraform plan answers Error: No configuration filescreate a Terraform configuration file (.tf file) and try again. → There is no .tf file in the folder: either you are not in travail/projet-01-local (check with pwd), or the file is called main.tf.txt. Rename it to main.tf. An empty folder is a valid (empty) configuration, hence the misleading Success!.

  • terraform apply answers Apply cancelled. and nothing is created → You typed something other than yes (for example y, Y, oui, or Enter alone). Run again and type yes in full.

  • terraform state list answers No state file was found! → No apply has been done yet in this folder, or you are not in the right folder. It is not a failure: there is nothing to list yet.

  • terraform state show answers No instance found for the given address! → The address is mistyped, or the resource was destroyed. terraform state list gives the exact addresses.

  • terraform fmt -check exits with an error (code 3) displaying main.tf → The file is not in the official format (indentation, alignment of =). It is not a syntax error: terraform fmt without -check fixes it.

  • After destroy, terraform.tfstate is still there and you think the destroy failed → No: open it, "resources": []. The file remains, empty, with a terraform.tfstate.backup next to it. Destroy complete! Resources: 1 destroyed. is authoritative.

  • .\labo.ps1 etat or ./labo.sh etat displays Ressources encore gérées : 1 → A destroy was forgotten in one of the folders listed above (travail projet-01-local : 1 ressource dans le state). Go into that folder, terraform destroy, yes, rerun etat.

  • Windows only — terraform : Le terme «terraform» n'est pas reconnu comme nom d'applet de commande, fonction, fichier de script ou programme exécutable. (the term is not recognized as a cmdlet, function, script file or executable program) → Terraform is not in the PATH, or PowerShell was not restarted after the installation. Close all PowerShell windows, reopen, retype terraform version. If it persists, Appendix A.1 (adding C:\terraform to the PATH).

  • Windows only — aws : Le terme «aws» n'est pas reconnu… → AWS CLI is not installed or PowerShell was not restarted. Reinstall AWS CLI version 2 (A.2), close and reopen PowerShell, retype aws --version.

  • Windows only — .\labo.ps1 is refused: "running scripts is disabled on this system"Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, answer Y (O on a French system), run again. Once per machine.

  • Windows only — the file is called main.tf.txt → Notepad adds .txt. In PowerShell: Rename-Item main.tf.txt main.tf. Use VS Code to create the files.

  • Native Linux and macOS — bash: terraform: command not found → The binary is not in a PATH folder. sudo mv terraform /usr/local/bin/, or check the package installation (B.1). Reopen the terminal.

  • macOS and bash — ./labo.sh answers Permission deniedchmod +x labo.sh, once only. If bash: ./labo.sh: /bin/bash^M: bad interpreter, the file has Windows line endings: git config core.autocrlf input then re-clone, or sed -i 's/\r$//' labo.sh (sed -i '' … on macOS).

  • aws sts get-caller-identity answers AccessDenied or InvalidClientTokenId → The configured key is not the right one, or the Academy session token has expired. aws configure with the terraform-admin key (check that the user is in terraform-admins with AdministratorAccess), or redo aws configure set … with new Academy credentials. No effect on Project 01, which does not call AWS.

What you hand in

A compressed folder or a Git repository containing:

  • main.tf (the final file, with the second version of the content);
  • sorties.txt: the full output of terraform plan (first version), of terraform state list after apply, of the modification terraform plan (the one with must be replaced), and of terraform state list after destroy (empty);
  • README.md following the template below.

Hand in neither the .terraform/ folder, nor terraform.tfstate, nor any Access Key.

markdown
# Project 01 — Terraform local

- Terraform: (output of `terraform version`)
- System: Windows / Linux / macOS

## What I did
init, fmt, validate, plan, apply, state list, state show, content modification, plan, apply, destroy, state list.

## The terraform.tfstate file, in three lines of my own
(What it is for. Why Terraform needs it for destroy. Why you do not edit it and do not share it.)

## What blocked me, and how I got unblocked
(One line, or "nothing".)

Official sources

Sentence to remember: Terraform does not click in the console for you. It reads your code, compares it to its state and to reality, announces what is going to change, then creates, replaces or destroys to reach the requested state.