I created a terraform gcs backend using the block :
terraform {
backend "gcs" {
bucket = "<redacted_bucket_name>"
prefix = "base/terraform.state"
impersonate_service_account = "<redacted_service_account>#<redacted_project_id>.iam.gserviceaccount.com"
}
}
By performing a terraform init, this creates the state file in the specified bucket but in a directory :
<bucket>
|--base (Directory)
|--terraform.state (Directory)
|--default.state (File)
How do I specify the name of the state file?
I know I've used the prefix wrong in this example, but there is nothing in the documentation about it.
The name is the name of the Terraform workspace that you're working in (hence default).
You use workspaces to separate your deployments when you're using the same code for multiple environments.
For example, if you were to perform a terraform init with a backend configured, it would use the default workspace and create a default.tfstate within the bucket.
If you were to do a terraform workspace new prod and performed a terraform init you would be working against a state file of prod.tfstate.
You don't have to use workspaces though as they can add complexity to the pipeline. You could just as easily have a different backend or place the state file under a different prefix; you can think of prefix in regards to GCS backends as a directory structure to place the state file.
e.g. prefix=terraform/myproject/nonprod would keep a default.tfstate under a nonprod directory so it's separate from the other environments. Whatever works for you.
prefix - (Optional) GCS prefix inside the bucket. Named states for workspaces are stored in an object called /.tfstate.
source
Related
I have a Terraform (1.0+) script that generates a local config file from a template based on some inputs, e.g:
locals {
config_tpl = templatefile("${path.module}/config.tpl", {
foo = "bar"
})
}
resource "local_file" "config" {
content = local._config_tpl
filename = "${path.module}/config.yaml"
}
This file is then used by a subsequent command run from a local-exec block, which in turn also generates local config files:
resource "null_resource" "my_command" {
provisioner "local-exec" {
when = create
command = "../scripts/my_command.sh"
working_dir = "${path.module}"
}
depends_on = [
local_file.config,
]
}
my_command.sh generates infrastructure for which there is no Terraform provider currently available.
All of the generated files should form part of the configuration state, as they are required later during upgrades and ultimately to destroy the environment.
I also would like to run these scripts from a CI/CD pipeline, so naturally you would expect the workspace to be clean on each run, which means the generated files won't be present.
Is there a pattern for managing files such as these? My initial though is to create cloud storage bucket, zip the files up, and store them there before pulling them back down whenever they're needed. However, this feels even more dirty than what is already happening, and it seems like there is the possibility to run into dependency issues.
Or, am I missing something completely different to solve issues such as this?
The problem you've encountered here is what the warning in the hashicorp/local provider's documentation is discussing:
Terraform primarily deals with remote resources which are able to outlive a single Terraform run, and so local resources can sometimes violate its assumptions. The resources here are best used with care, since depending on local state can make it hard to apply the same Terraform configuration on many different local systems where the local resources may not be universally available. See specific notes in each resource for more information.
The short and unfortunate answer is that what you are trying to do here is not a problem Terraform is designed to address: its purpose is to manage long-lived objects in remote systems, not artifacts on your local workstation where you are running Terraform.
In the case of your config.yaml file you may find it a suitable alternative to use a cloud storage object resource type instead of local_file, so that Terraform will just write the file directly to that remote storage and not affect the local system at all. Of course, that will help only if whatever you intend to have read this file is also able to read from the same cloud storage, or if you can write a separate glue script to fetch the object after terraform apply is finished.
There is no straightforward path to treating the result of a provisioner as persistent data in the state. If you use provisioners then they are always, by definition, one-shot actions taken only during creation of a resource.
# Using a single workspace:
terraform {
backend "remote" {
hostname = "app.terraform.io"
organization = "company"
workspaces {
name = "my-app-prod"
}
}
}
For Terraform remote backend, would there be a way to use variable to specify the organization / workspace name instead of the hardcoded values there?
The Terraform documentation
didn't seem to mention anything related either.
The backend configuration documentation goes into this in some detail. The main point to note is this:
Only one backend may be specified and the configuration may not contain interpolations. Terraform will validate this.
If you want to make this easily configurable then you can use partial configuration for the static parts (eg the type of backend such as S3) and then provide config at run time interactively, via environment variables or via command line flags.
I personally wrap Terraform actions in a small shell script that runs terraform init with command line flags that uses an appropriate S3 bucket (eg a different one for each project and AWS account) and makes sure the state file location matches the path to the directory I am working on.
I had the same problems and was very disappointed with the need of additional init/wrapper scripts. Some time ago I started to use Terragrunt.
It's worth taking a look at Terragrunt because it closes the gap between Terraform and the lack of using variables at some points, e.g. for the remote backend configuration:
https://terragrunt.gruntwork.io/docs/getting-started/quick-start/#keep-your-backend-configuration-dry
When I run terraform destroy to destroy everything in a folder I leaves behind the state file in S3 (which I'm using as my backend).
The state file it leaves behind looks like this:
{
"version": 4,
"terraform_version": "0.12.12",
"serial": 7,
"lineage": "9eb5ca6d-20a9-d5f5-053a-eefe274bf669",
"outputs": {},
"resources": []
}
Can Terraform delete the S3 file on destroying?
Probably not.
Terraform requires state to function, and a state file with zero resources represents an infrastructure state that's different than having no state file at all. Namely that you've created resources before, and if one were to compare backups of the state file, one could clearly see and restore the state history.
We further confirm this by looking at the terraform state and terraform destroy commands and notice none of their sub-commands or options remove the state file.
Having said that, what may work and I've never tried this, is if terraform manages the bucket containing the state file, and you destroy that bucket (using a local backup as the state file during the bucket destroy operation), we're effectively tricking terraform into erasing its state.
If you are using terraform workspaces to represent different environments then it is possible to remove the state file for a particular workspace by calling:
terraform workspace select default && terraform workspace delete workspace_for_deletion
This will only work with an empty state so should be called after you call terraform destroy.
Note that the default workspace can't be deleted. This means this isn't a perfect solution, but it should help keep minimise the clutter in your backend.
There is no straight forward option to delete a state file we can manipulate it with
terraform state rm command.
But One of the way is to use workspace for each environment but default workspace wont be deleted in that case.
Another way would be in aws cli something like aws s3 rm s3://mybucket/foo.tfstate.
https://docs.aws.amazon.com/cli/latest/reference/s3/rm.html might help.
# Using a single workspace:
terraform {
backend "remote" {
hostname = "app.terraform.io"
organization = "company"
workspaces {
name = "my-app-prod"
}
}
}
For Terraform remote backend, would there be a way to use variable to specify the organization / workspace name instead of the hardcoded values there?
The Terraform documentation
didn't seem to mention anything related either.
The backend configuration documentation goes into this in some detail. The main point to note is this:
Only one backend may be specified and the configuration may not contain interpolations. Terraform will validate this.
If you want to make this easily configurable then you can use partial configuration for the static parts (eg the type of backend such as S3) and then provide config at run time interactively, via environment variables or via command line flags.
I personally wrap Terraform actions in a small shell script that runs terraform init with command line flags that uses an appropriate S3 bucket (eg a different one for each project and AWS account) and makes sure the state file location matches the path to the directory I am working on.
I had the same problems and was very disappointed with the need of additional init/wrapper scripts. Some time ago I started to use Terragrunt.
It's worth taking a look at Terragrunt because it closes the gap between Terraform and the lack of using variables at some points, e.g. for the remote backend configuration:
https://terragrunt.gruntwork.io/docs/getting-started/quick-start/#keep-your-backend-configuration-dry
I am using TF 0.11.11 on GCP. I have my backend.tfvars file setup to store by state file in a GCP bucket:
# bucket where terraform state file will be kept
bucket = "this-is-my-tf-bucket"
# folder in the bucket where the terraform state file will be kept
prefix = "bucket.folder.sbx"
credentials = "greg-cred.json"
However, after I run my init and apply, I notice that the folder (bucket.folder.sbx) does not exist in my bucket. The init and apply run fine, with no errors. So I'm wondering...where is my state file going?
Is there a way to trace this?
As a secondary question, it seems that my local "terraform.tfstate" file is not getting placed in my .terraform directory and is getting placed into the root directory. I'm wondering why this is? How do you control where the local terraform.tfstate file is placed?
It is expected to have terraform.tfstate in a root directory of the project.
According to Terrafor architecture, in folder .terraform should be installed modules and providers, but not the state file.
If you want to store a state file in GCP bucket - you need to declare a "terraform" block, see documentation:
terraform {
backend "gcs" {
bucket = "tf-state-prod"
prefix = "terraform/state"
}
}
So, if you see terraform.tfstate locally - it's definatelly not in cloud. Terraform creates only one state file either locally, or in remote bucket (with configuration above).
Creating a backend.tfvars does not create a backend configuration. More over, it is not even automatically loaded. More on that later.
To create a GCS remote backend configuration you should follow the Terraform GCS backend documentation:
terraform {
backend "gcs" {
# bucket where terraform state file will be kept
bucket = "this-is-my-tf-bucket"
# folder in the bucket where the terraform state file will be kept
prefix = "bucket.folder.sbx"
credentials = "greg-cred.json"
}
}
If you do not provide a backend configuration, you get the default local backend and according to the default backend documentation the state file path:
defaults to "terraform.tfstate" relative to the root module by default.
As for the backend.tfvars, *.tfvars are not automatically loaded (except for terraform.tfvars or *.auto.tfvars). See the input variables documentation. If you want to load the the backend.tfvars file you should use -var-file=backend.tfvars. You must also reference these variables from your backend configuration. See this example.