How to read this page. Ten steps, one command at a time. For each: the command to type, the exact output from the course machine (Terraform 1.12.2), and what to look at in it. Type each command yourself (no copy-paste): it is by writing
init,plan,apply,statethat the words sink in. The "To understand better" blocks are optional; open them if a step leaves you in doubt. Theterraform …commands are identical on Windows, Linux and macOS; when a command depends on the system (creating, reading, listing a file), both versions are given, one below the other. If Terraform is not installed or the kit is not cloned, go back to Project 01, section In short (kit:https://github.com/hrhouma2/aiopsatlas-terraform-labo-fr).
Project 01 had you run thirteen commands on a fifteen-line main.tf, with a terraform block, a provider block and a resource block. You saw the sentences scroll by, but fifteen lines is already a lot when you want to know what is essential. Here, you start from zero with three useful lines: a single resource block, with nothing around it. You will see Terraform refuse to work without init, then have it write its estimate, carry it out, and read what it noted in its record. At the end, you know what a block, a plan and a state are because you built them yourself, one after the other.
Take the architect from Lesson 01 again. A resource block is one line of the architect's blueprint: "here, a file, with this name and this content". The plan (the command) is the estimate: what Terraform would do so that reality looks like the drawing. The state is the record: what Terraform actually built, under which identifier, with which values. Three objects, three commands to see them: the block is read in main.tf, the estimate with terraform plan, the record with terraform state show.
| Terraform | What it is | In this workshop |
|---|---|---|
block resource "type" "name" | a thing to manage, described in a .tf file | resource "local_file" "bonjour" |
| resource type | what it is, supplied by a provider | local_file (a file on disk) |
| local name | what you call it in your code | bonjour |
| address | type . name, to refer to it elsewhere | local_file.bonjour |
| argument | a value you write in the block | filename, content |
| attribute | a value the provider computes | id, content_md5 |
| plan | the estimate: what is missing between the code and reality | Plan: 1 to add, 0 to change, 0 to destroy. |
| state | the record: what Terraform built | terraform.tfstate, read by state list and state show |
A terminal (PowerShell on Windows; bash or zsh on Linux, macOS, WSL 2 or Git Bash), opened at the root of the lab-terraform kit, the one containing labo.ps1 and labo.sh. And VS Code to write the file. The terraform … commands are written exactly the same way everywhere; only the commands to create, read or list a file change, and they are given in both versions each time.
Windows (PowerShell):
.\labo.ps1 nouveau atelier-1
cd travail\atelier-1Linux, macOS, WSL 2, Git Bash:
./labo.sh nouveau atelier-1
cd travail/atelier-1Dossier travail/atelier-1 créé (ignoré par Git). Tapez :
cd travail/atelier-1What the command asks: create an empty working folder for me, travail/atelier-1, which Git will ignore (on Windows the script writes travail\atelier-1, with the backslash).
What to look at: the folder is empty. Nothing is a Terraform project yet: main.tf, then init, will make it one.
In VS Code (code . from the folder), create a main.tf file and type exactly:
resource "local_file" "bonjour" {
filename = "${path.module}/bonjour.txt"
content = "Bonjour Terraform"
}What the file asks: "Terraform, manage a file on disk for me (local_file), which I call bonjour; it is named bonjour.txt, in this folder, and it contains Bonjour Terraform."
What to look at: there is neither a terraform block nor a provider block. Nothing but the resource. Terraform will infer everything else from the word local_file. Check that the file is there and correctly named:
Windows (PowerShell):
Get-ChildItem
Get-Content .\main.tfLinux, macOS, WSL 2, Git Bash:
ls -la
cat main.tfA single entry, main.tf, and the four lines of the file. If you see main.tf.txt, rename it.
"local_file" then "bonjour": the type, then the name. The type is imposed by the provider (local_file exists; local_fichier does not, you would get Error: Invalid resource type). The name is yours: bonjour, message, mon_fichier. Together, they form the address local_file.bonjour that you will find again in the plan and in the state.filename and content are arguments: values you supply. For local_file, content is mandatory (without it: Error: Invalid Attribute Combination), filename too.${path.module} means "the folder where this .tf file is"; Terraform will display it as ./bonjour.txt. Without it, filename = "bonjour.txt" would also work here; Project 01 explains why you get into the habit of writing it.= are aligned (filename =, content =): that is the terraform fmt style. It changes nothing to the meaning.terraform planError: 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 initWhat the command asks: "Tell me what you would do so that reality looks like my code."
What to look at: Terraform refuses, and explains. It read local_file, inferred that it needs the registry.terraform.io/hashicorp/local provider, and notices it does not have it (no version is selected). The last line is the solution: terraform init. This is the error every beginner meets once; now you know how to read it.
terraform initInitializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/local...
- 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: "Download the providers my code requires and prepare this folder."
What to look at: Finding latest version of hashicorp/local...: without a required_providers block, Terraform takes the most recent version (v2.9.1 on the course machine; on yours, maybe newer). In Project 01, the same line said Finding hashicorp/local versions matching "~> 2.5"... because the code set a constraint. Then the sentence to expect: Terraform has been successfully initialized!
List the folder, including hidden files:
Windows (PowerShell):
Get-ChildItem -ForceLinux, macOS, WSL 2, Git Bash:
ls -laThree entries: .terraform (a folder, the downloaded provider), .terraform.lock.hcl (the chosen version, recorded) and your main.tf. No bonjour.txt yet, no terraform.tfstate yet: init built nothing.
init never touches your resources. You can rerun it as many times as you want. It only prepares the folder..terraform/ is heavy and gets re-downloaded (about 18 MB here): it never goes into Git. .terraform.lock.hcl is light and precious: it goes into Git, so that your colleague gets the same provider version.v2.9.1; in six months, v3.0.0 with a different behavior. The required_providers block of Project 01 (version = "~> 2.5") avoids the surprise. For a twenty-minute workshop, we do without it.terraform planTerraform 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.bonjour will be created
+ resource "local_file" "bonjour" {
+ content = "Bonjour 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 = "./bonjour.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: the same as in step 3. This time Terraform has its provider, and it answers.
What to look at, line by line, because this is the output you will read most often in your whole life with Terraform:
| Line | What it says |
|---|---|
+ create | The legend of the estimate: a single symbol used, +, create. |
# local_file.bonjour will be created | The address of your resource and its fate. It is not done yet: will be. |
+ resource "local_file" "bonjour" { | Your block, copied with a + in front: everything is new. |
+ content = "Bonjour Terraform" | An argument you wrote. Terraform knows its value. |
+ content_md5 = (known after apply) | An attribute you did not write and that the provider will compute after creating the file: its fingerprint. Terraform cannot know it beforehand. |
+ directory_permission = "0777" | An attribute you did not write and for which the provider has a default value. |
+ filename = "./bonjour.txt" | Your ${path.module}/bonjour.txt, resolved: . is the current folder. |
+ id = (known after apply) | The identifier of the resource, known only after creation. |
Plan: 1 to add, 0 to change, 0 to destroy. | The line to read first. One thing to add, nothing to change, nothing to destroy. |
Nothing has been created. Check: bonjour.txt still does not exist in the folder.
local_file, not only what you gave it. Learn to spot your arguments and to skim the attributes, except the Plan: line.(known after apply) is the most important phrase to understand for what follows: when a resource B uses the id of a resource A not yet created, B displays (known after apply) for that value, and Terraform knows it must create A first. That is the dependency graph from Lesson 02, seen from the plan.Note: You didn't use the -out option: this estimate is not saved to a file; apply will recompute an identical one. -out is useful when a pipeline must apply exactly the plan that was reviewed, later in the course.terraform applyTerraform 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.bonjour will be created
+ resource "local_file" "bonjour" {
+ content = "Bonjour Terraform"
…
+ filename = "./bonjour.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.bonjour: Creating...
local_file.bonjour: Creation complete after 0s [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.What the command asks: "Recompute the estimate, show it to me, wait for my agreement, then do it."
What to look at: the same plan as in step 5, then the question. Type yes, in full, then Enter. Then Creating..., Creation complete after 0s [id=fd9aee55…]: the id that was (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.
If you type y instead of yes, Terraform answers Apply cancelled. and does nothing. Try it once, to have seen it; then rerun with yes.
Windows (PowerShell):
Get-Content .\bonjour.txt
Get-ChildItem -ForceLinux, macOS, WSL 2, Git Bash:
cat bonjour.txt
ls -laBonjour Terraformtotal 14
drwxr-xr-x 1 rehou 197609 0 sept. 15 14:55 .
drwxr-xr-x 1 rehou 197609 0 sept. 15 14:55 ..
drwxr-xr-x 1 rehou 197609 0 sept. 15 14:55 .terraform
-rw-r--r-- 1 rehou 197609 1228 sept. 15 14:55 .terraform.lock.hcl
-rw-r--r-- 1 rehou 197609 17 sept. 15 14:55 bonjour.txt
-rw-r--r-- 1 rehou 197609 111 sept. 15 14:55 main.tf
-rw-r--r-- 1 rehou 197609 1634 sept. 15 14:55 terraform.tfstate(ls -la listing from the course machine; Get-ChildItem -Force displays the same five names, with other columns.)
What the commands ask: "Show me the content of bonjour.txt, then everything in the folder, hidden files included."
What to look at: Bonjour Terraform, 17 bytes, exactly your content, without a trailing newline (under bash, your prompt may appear glued to the text: that is normal). And two new things in the folder since step 4: bonjour.txt, the resource, and terraform.tfstate, the state. It is apply that created both, at the same time: the file on disk, and the note "I created it" in the record.
terraform state listlocal_file.bonjourWhat the command asks: "List everything you manage in this folder."
What to look at: one line, one resource, designated by its address local_file.bonjour, the same as in the plan. It is the proof that Terraform tied your block to the real file. Before step 6, the same command would have answered No state file was found!: there was no record yet.
terraform state show local_file.bonjour# local_file.bonjour:
resource "local_file" "bonjour" {
content = "Bonjour Terraform"
content_base64sha256 = "SvXkP5Iqo1rJKHgTVJnjanxfsNVGhb+r52mzUqZJYyg="
content_base64sha512 = "r3+PevYNJRFsHMz4H4U1DK+lg89nhO9SLJZMAqqtzPKiIv3ilrmbx5sYh2JCW9o5EF0NXanYmuxMvI2QN4Z1Ag=="
content_md5 = "ff3a967c227a58691a3d34a931d3eeb5"
content_sha1 = "fd9aee5556589b4d797e6d49f8ec894e29e57713"
content_sha256 = "4af5e43f922aa35ac92878135499e36a7c5fb0d54685bfabe769b352a6496328"
content_sha512 = "af7f8f7af60d25116c1cccf81f85350cafa583cf6784ef522c964c02aaadccf2a222fde296b99bc79b188762425bda39105d0d5da9d89aec4cbc8d9037867502"
directory_permission = "0777"
file_permission = "0777"
filename = "./bonjour.txt"
id = "fd9aee5556589b4d797e6d49f8ec894e29e57713"
}What the command asks: "Show me everything you know about local_file.bonjour."
What to look at: the same shape as the plan of step 5, but without any + and without any (known after apply): everything is known, everything is recorded. The id is the SHA-1 fingerprint of the content (fd9aee55…), identical to content_sha1 and to the [id=…] displayed by apply. Your two arguments (filename, content) are there, along with the nine attributes computed by the provider. This is your deliverable answer: keep it.
terraform.tfstate, in your folder. Open it in VS Code to read it: a "resources" list with one element, and inside exactly the values that state show displays. Never modify it by hand: one misplaced comma and Terraform no longer recognizes what it built.terraform.tfstate, bonjour.txt stays on disk, but Terraform no longer knows it is its own: at the next plan, it will offer to create it again. The state is Terraform's only memory.terraform planlocal_file.bonjour: Refreshing state... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.What the command asks: the same as in steps 3 and 5.
What to look at: two new things compared to step 5. Refreshing state... [id=…]: before comparing, Terraform went to reread the real file to check that it still matches the record. Then No changes. Your infrastructure matches the configuration.: the code, the record and reality say the same thing, the estimate is empty. That is the idempotence from Lesson 01, in the terminal: rerunning does nothing more.
Do not destroy anything. Fundamental Workshop 2 starts exactly from this folder, with this file and this state. If you do not chain it right away, leave the folder as is: nothing is billed, nothing moves. (If you really want to erase everything now: terraform destroy, yes, and you will redo Workshop 1 before Workshop 2.)
Go back up to the root of the kit and run the counter:
Windows (PowerShell):
cd ..\..
.\labo.ps1 etatLinux, macOS, WSL 2, Git Bash:
cd ../..
./labo.sh etattravail atelier-1 : 1 ressource dans le state
Ressources encore gérées : 1 (0 attendu à la fin d'une séance).Expected answer: 1 ressource dans le state (1 resource in the state). The (0 attendu à la fin d'une séance) (0 expected at the end of a session) is the kit's reminder; here it is normal to be at 1, since Workshop 2 is going to destroy this resource.
main.tf, without terraform {} or provider {}, and it was enough.Error: Inconsistent dependency lock file and you know the solution is in the last line.init created .terraform/ and .terraform.lock.hcl, and nothing else.+ create, will be created, (known after apply), Plan: 1 to add, 0 to change, 0 to destroy.apply asked for yes, created bonjour.txt and terraform.tfstate.state list returns local_file.bonjour; state show returns the full block with a forty-character id.plan returns No changes.terraform state show local_file.bonjour (step 9) as the deliverable.terraform : Le terme «terraform» n'est pas reconnu… or bash: terraform: command not found → Terraform is not installed or not in the PATH. Project 01, Appendix A.1 or B.1..\labo.ps1 or ./labo.sh: file not found → You are not at the root of the kit. cd to the lab-terraform folder (the one containing labo.ps1, labo.sh, projets).Le dossier travail\atelier-1 existe déjà. Tapez : (the folder already exists) → You already ran nouveau atelier-1. Do the cd indicated; if the folder contains an old attempt, delete it and rerun nouveau.Error: Invalid resource type … does not support resource type "local_fichier" → The type is local_file, in English, with an underscore.Error: Unsupported argument … Did you mean "content"? → Typo in an argument name; Terraform suggests the right one.Error: Unclosed configuration block → The closing brace } of the last line is missing.Error: Unterminated template string → A " quote is missing at the end of a value.terraform plan answers Error: No configuration files → The file is not named main.tf (often main.tf.txt), or you are not in travail/atelier-1. Check with Get-ChildItem or ls -la.Apply cancelled. → You typed something other than yes. Run again, type yes in full.terraform state list answers No state file was found! → No apply has succeeded in this folder. Go back to step 6.terraform state show answers No instance found for the given address! → The address is mistyped; terraform state list gives you the right one (local_file.bonjour).