How to read this page. Ten steps, one command at a time. For each: the command, the exact output from the course machine (Terraform 1.12.2), and what to look at. Type each command yourself. The "To understand better" blocks are optional. The
terraform …commands are identical everywhere; when a command depends on the system, both versions are given, one below the other. If the Workshop 1 folder no longer exists or ifstate listdoes not answerlocal_file.bonjour, redo Fundamental Workshop 1 first (kit:https://github.com/hrhouma2/aiopsatlas-terraform-labo-fr).
Workshop 1 had you create a resource and read the state. But an infrastructure is not created once and for all: it gets modified, someone breaks it by mistake, and one day it gets taken down. Those are the three gestures of this workshop. You will first delete bonjour.txt by hand, like a colleague who "cleans up" a folder, and see Terraform notice it then recreate it: that is the drift from Lesson 01. You will then change one line of main.tf and discover that, for the local provider, changing the content of a file does not modify it: it replaces it, with a symbol you must know how to recognize before saying yes. Finally you will destroy everything, and check that the record is empty and that the kit counts zero.
Still the architect. A drift is a worker who moved a wall without saying so: the building no longer matches the record; the architect notices when going to see on site (Refreshing state...). Modifying in place (~) is repainting a wall: the wall stays the same. Replacing (-/+) is demolishing the wall and rebuilding one in the same spot: it has a new number in the record. Destroying (-) is demolishing without rebuilding. The estimate always says which of the three it foresees; you read it before signing.
| Symbol in the plan | Sentence in the plan | What Terraform does | In this workshop |
|---|---|---|---|
+ | will be created | creates a new object | step 3: the file deleted by hand |
~ | will be updated in-place | changes an attribute, the object remains | never for the content of a local_file |
-/+ | must be replaced | destroys the object, then creates another one | step 5: content changes |
- | will be destroyed | removes the object | step 8: destroy |
The same terminal as for Workshop 1, in the travail/atelier-1 folder of the lab-terraform kit. And VS Code to modify main.tf. The terraform … commands are identical everywhere; the commands to delete, read or list a file are given in both versions.
Windows (PowerShell), from the root of the kit:
cd travail\atelier-1Linux, macOS, WSL 2, Git Bash:
cd travail/atelier-1Then:
terraform state listlocal_file.bonjourWhat the command asks: "What do you manage in this folder?"
What to look at: one line, local_file.bonjour. That is the state Workshop 1 left you in. If you read No state file was found! or nothing at all, the folder is not the right one or the resource was destroyed: redo Workshop 1 before continuing.
Windows (PowerShell):
Remove-Item .\bonjour.txt
Get-ChildItemLinux, macOS, WSL 2, Git Bash:
rm bonjour.txt
lsWhat the commands ask: "Delete bonjour.txt, then show me the folder."
What to look at: bonjour.txt is no longer there; main.tf and terraform.tfstate still are. You just did what a colleague does in the AWS console when deleting a bucket "that was of no use": reality changed, but neither the code nor the record knows it yet.
terraform planlocal_file.bonjour: Refreshing state... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]
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.bonjour will be created
+ resource "local_file" "bonjour" {
+ content = "Bonjour Terraform"
+ content_base64sha256 = (known after apply)
…
+ filename = "./bonjour.txt"
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.What the command asks: "Compare the code, the record and reality, and tell me what is missing."
What to look at: Refreshing state... [id=fd9aee55…]: Terraform went to look at the file its record mentions, and did not find it. Result: it removes it from its record and the estimate says will be created, Plan: 1 to add. The same plan as in Workshop 1, step 5, even though you did not touch the code. That is what a drift is: reality moved without going through Terraform, and Terraform detected it at plan time, not before.
Refreshing state... of the plan (and of the apply) that rereads reality. A file deleted three days ago is only discovered at the next plan.+ and not -/+? The object no longer exists at all: there is nothing to destroy, only to create. -/+ (step 5) is for an object that still exists but must be redone.plan offers to put it back as the code says. The plan shows it to you; it is up to you to decide whether the code or the console was right.terraform applylocal_file.bonjour: Refreshing state... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]
…
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, wait for my yes, then do it."
What to look at: bonjour.txt is back, and its id is the same as before (fd9aee55…): the identifier of a local_file is the fingerprint of its content, and the content did not change. Check with Get-Content .\bonjour.txt or cat bonjour.txt: Bonjour Terraform. A script would not have known that a repair was needed; Terraform knew because it has a record and code to compare.
In VS Code, modify the content line of main.tf to get:
resource "local_file" "bonjour" {
filename = "${path.module}/bonjour.txt"
content = "Bonjour Terraform, deuxième version"
}Save, then:
terraform planlocal_file.bonjour: Refreshing state... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]
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.bonjour must be replaced
-/+ resource "local_file" "bonjour" {
~ content = "Bonjour Terraform" -> "Bonjour Terraform, deuxième version" # forces replacement
~ content_base64sha256 = "SvXkP5Iqo1rJKHgTVJnjanxfsNVGhb+r52mzUqZJYyg=" -> (known after apply)
~ content_base64sha512 = "r3+PevYNJRFsHMz4H4U1DK+lg89nhO9SLJZMAqqtzPKiIv3ilrmbx5sYh2JCW9o5EF0NXanYmuxMvI2QN4Z1Ag==" -> (known after apply)
~ content_md5 = "ff3a967c227a58691a3d34a931d3eeb5" -> (known after apply)
~ content_sha1 = "fd9aee5556589b4d797e6d49f8ec894e29e57713" -> (known after apply)
~ content_sha256 = "4af5e43f922aa35ac92878135499e36a7c5fb0d54685bfabe769b352a6496328" -> (known after apply)
~ content_sha512 = "af7f8f7af60d25116c1cccf81f85350cafa583cf6784ef522c964c02aaadccf2a222fde296b99bc79b188762425bda39105d0d5da9d89aec4cbc8d9037867502" -> (known after apply)
~ id = "fd9aee5556589b4d797e6d49f8ec894e29e57713" -> (known after apply)
# (3 unchanged attributes hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.What the command asks: the same as in step 3. But this time it is the code that changed, not reality.
What to look at, line by line. This is the most important plan of the module:
| Line | What it says |
|---|---|
-/+ destroy and then create replacement | The legend: the symbol used is -/+, "destroy then create a replacement". |
# local_file.bonjour must be replaced | Not will be updated in-place: the resource must be replaced. |
-/+ resource "local_file" "bonjour" { | The whole block carries the -/+. |
~ content = "Bonjour Terraform" -> "Bonjour Terraform, deuxième version" # forces replacement | The ~ says the value changes, from the old one (->) to the new one. The # forces replacement comment says this attribute is the one forcing the replacement. |
~ content_md5 = "ff3a967c…" -> (known after apply) | The fingerprints will change; Terraform does not know the new ones yet. |
~ id = "fd9aee55…" -> (known after apply) | The identifier is going to change. It will be another object. |
# (3 unchanged attributes hidden) | filename, directory_permission, file_permission do not move; Terraform hides them for readability. |
Plan: 1 to add, 0 to change, 1 to destroy. | One destruction and one creation, zero modification. |
This is your first deliverable answer: keep this output.
The essential difference between
~and-/+. The~in front ofcontentsays what changes. The-/+in front ofresourcesays how Terraform is going to go about it: demolish, then rebuild. For a text file, you will not see the difference. For a database,-/+means the data disappears with the old object. The provider decides, attribute by attribute; thelocalprovider decided thatcontentforces the replacement. You read the three clues (must be replaced,# forces replacement,1 to destroy) before typingyes.
local provider replace instead of modifying? Because it does not know how to "fix" an existing file: it knows how to write it or erase it. The id of a local_file is, by the way, the SHA-1 fingerprint of its content; changing the content changes the identifier, and an object whose identifier changes is, for Terraform, another object. On the course machine, file_permission also forces the replacement. Other providers do otherwise: changing a tag on an AWS resource is a ~, changing its name is often a -/+.apply in production, that is what you are going to read.terraform apply -replace=local_file.bonjour replaces the resource even without a code change. Useful when an object is corrupted. You will see it in Project 08.terraform applylocal_file.bonjour: Refreshing state... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]
…
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.bonjour: Destroying... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]
local_file.bonjour: Destruction complete after 0s
local_file.bonjour: Creating...
local_file.bonjour: Creation complete after 0s [id=bd2b5f8ccbb2cbc2b9bea872211549635a76b092]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.What the command asks: the same as in step 4.
What to look at: four action lines instead of two, in the order of the -/+ symbol: Destroying... then Destruction complete (the old one, fd9aee55…), then Creating... then Creation complete (the new one, bd2b5f8c…). And the last line: Apply complete! Resources: 1 added, 0 changed, 1 destroyed. Not 1 changed: nothing was modified, one object was destroyed and another created.
Windows (PowerShell):
Get-Content .\bonjour.txtLinux, macOS, WSL 2, Git Bash:
cat bonjour.txtBonjour Terraform, deuxième versionThen:
terraform state show local_file.bonjour# local_file.bonjour:
resource "local_file" "bonjour" {
content = "Bonjour Terraform, deuxième version"
content_base64sha256 = "hg9L75JxH+5SDk+ZOADZinGONkirydetS0TFjN3uZ3o="
content_base64sha512 = "EjT8mRVQU2JQ+Em/rnbRKYfe7E2UqIWw8yQMMtaatimH5XCKg4ZomW4PI5yO5jhujdSrU04vX17HE2kaqEVSkQ=="
content_md5 = "9a1a1355b48e28ad727200e450b851e1"
content_sha1 = "bd2b5f8ccbb2cbc2b9bea872211549635a76b092"
content_sha256 = "860f4bef92711fee520e4f993800d98a718e3648abc9d7ad4b44c58cddee677a"
content_sha512 = "1234fc991550536250f849bfae76d12987deec4d94a885b0f3240c32d69ab62987e5708a838668996e0f239c8ee6386e8dd4ab534e2f5f5ec713691aa8455291"
directory_permission = "0777"
file_permission = "0777"
filename = "./bonjour.txt"
id = "bd2b5f8ccbb2cbc2b9bea872211549635a76b092"
}What the commands ask: "Show me the real file, then what the record says about it."
What to look at: the file contains the second version. In the record, content is the new value, all the fingerprints have changed, and the id is bd2b5f8c…, the one displayed by Creation complete in step 6. Compare with the state show of Workshop 1, step 9: same address local_file.bonjour, but another object. The code, the record and reality agree again: a terraform plan now would say No changes.
terraform destroylocal_file.bonjour: Refreshing state... [id=bd2b5f8ccbb2cbc2b9bea872211549635a76b092]
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.bonjour will be destroyed
- resource "local_file" "bonjour" {
- content = "Bonjour Terraform, deuxième version" -> null
- content_base64sha256 = "hg9L75JxH+5SDk+ZOADZinGONkirydetS0TFjN3uZ3o=" -> null
…
- filename = "./bonjour.txt" -> null
- id = "bd2b5f8ccbb2cbc2b9bea872211549635a76b092" -> 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.bonjour: Destroying... [id=bd2b5f8ccbb2cbc2b9bea872211549635a76b092]
local_file.bonjour: Destruction complete after 0s
Destroy complete! Resources: 1 destroyed.What the command asks: "Destroy everything you manage here; show me the estimate and wait for my yes."
What to look at: the third symbol, - on its own, and each attribute going to -> null ("nothing anymore"). The question is more serious than at apply: Do you really want to destroy all resources? and There is no undo. Type yes. The sentence to expect: Destroy complete! Resources: 1 destroyed. The file has disappeared from the folder.
terraform state listThen, to see what Terraform answers when asked about a resource it no longer manages:
terraform state show local_file.bonjourNo instance found for the given address!
This command requires that the address references one specific instance.
To view the available instances, use "terraform state list". Please modify
the address to reference a specific instance.What the commands ask: "List what you manage", then "show me local_file.bonjour".
What to look at: state list answers nothing: not a line, no error, an empty record. That is different from the No state file was found! of Workshop 1 before apply: here the terraform.tfstate file exists, but its resource list is empty. And state show answers No instance found for the given address!: the address is correct, but nothing matches it anymore, and Terraform sends you back to state list. This is your second deliverable answer: the empty output of terraform state list.
Windows (PowerShell):
Get-ChildItem -ForceLinux, macOS, WSL 2, Git Bash:
ls -latotal 17
drwxr-xr-x 1 rehou 197609 0 Sep 15 14:57 .
drwxr-xr-x 1 rehou 197609 0 Sep 15 14:57 ..
drwxr-xr-x 1 rehou 197609 0 Sep 15 14:57 .terraform
-rw-r--r-- 1 rehou 197609 1228 Sep 15 14:57 .terraform.lock.hcl
-rw-r--r-- 1 rehou 197609 130 Sep 15 14:57 main.tf
-rw-r--r-- 1 rehou 197609 181 Sep 15 14:57 terraform.tfstate
-rw-r--r-- 1 rehou 197609 1653 Sep 15 14:57 terraform.tfstate.backup(ls -la listing from the course machine; Get-ChildItem -Force displays the same five names: .terraform, .terraform.lock.hcl, main.tf, terraform.tfstate, terraform.tfstate.backup.)
What the command asks: "Show me the whole folder, hidden files included."
What to look at: bonjour.txt is no longer there. main.tf is intact: destroying the resources never touches the code. terraform.tfstate is still there but tiny (181 bytes), and a terraform.tfstate.backup has appeared: the copy of the record from before the destroy, which Terraform keeps out of caution. .terraform/ and .terraform.lock.hcl also remain: if you rerun apply, no need to redo init.
Open the state to see it empty, without modifying it (Get-Content .\terraform.tfstate or cat terraform.tfstate):
{
"version": 4,
"terraform_version": "1.12.2",
"serial": 6,
"lineage": "69fb219e-a88c-a97f-5aed-a33e7c34ff69",
"outputs": {},
"resources": [],
"check_results": null
}"resources": []: nothing. The "serial" counts the writes to the state (six here: two apply in Workshop 1, then repair, replacement and destruction); the "lineage" is the identifier of this state, different on your machine.
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 : aucune ressource
Ressources encore gérées : 0 (0 attendu à la fin d'une séance).Expected answer: aucune ressource (no resource) and Ressources encore gérées : 0 (resources still managed: 0). This is the clean end of session that every project of the course will demand, once the resources cost money.
bonjour.txt by hand and saw plan offer will be created without the code having changed: a drift, detected at Refreshing state....apply recreated the file with the same id.content and read the three clues of the replacement: must be replaced, # forces replacement, Plan: 1 to add, 0 to change, 1 to destroy.apply displayed Destroying... then Creating..., and Resources: 1 added, 0 changed, 1 destroyed.; the id changed.destroy asked for yes with There is no undo. and answered Destroy complete! Resources: 1 destroyed.state list returns nothing; state show returns No instance found for the given address!; terraform.tfstate contains "resources": [].etat returns Ressources encore gérées : 0.state list answers No state file was found! → You are not in travail/atelier-1, or Workshop 1 was not done up to the apply. Check the folder (Get-ChildItem -Force or ls -la must show terraform.tfstate), otherwise redo Workshop 1.state list answers nothing → The resource has already been destroyed (a destroy at the end of Workshop 1). Rerun terraform apply, yes, then resume at step 2.No changes. → The file was not deleted (typo in Remove-Item or rm, or wrong folder). Check with Get-ChildItem or ls, delete it, rerun plan.No changes. → main.tf was not saved after the modification (VS Code: white dot in the tab). Ctrl+S then rerun.Error: Unterminated template string → The closing quote of the new content value got lost during editing. Put it back.# forces replacement is not content → You modified another argument at the same time (for example file_permission = "0644"). For a local_file, that one also forces the replacement (checked: ~ file_permission = "0777" -> "0644" # forces replacement). Put the file back as in step 5, only the content line must change.Apply cancelled. → Something other than yes was typed. Rerun, type yes in full.destroy says No changes. No objects need to be destroyed. then Destroy complete! Resources: 0 destroyed. → There is already nothing left in the state (Either you have not created any objects yet or the existing objects were already deleted outside of Terraform.). Move on to step 9.destroy, you think the state was not cleaned because terraform.tfstate still exists → That is normal. Open it: "resources": []. The file remains, empty, with a .backup next to it.etat answers Ressources encore gérées : 1 → The destroy was cancelled or done in another folder. Go back into travail/atelier-1, terraform destroy, yes, rerun etat.