Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variables values get overridden from environment variables when applying a plan #36410

Closed
J0F3 opened this issue Feb 3, 2025 · 5 comments · Fixed by #36435
Closed

Variables values get overridden from environment variables when applying a plan #36410

J0F3 opened this issue Feb 3, 2025 · 5 comments · Fixed by #36435
Assignees
Labels

Comments

@J0F3
Copy link

J0F3 commented Feb 3, 2025

Terraform Version

Terraform v1.10.5
on linux_amd64

Terraform Configuration Files

main.tf:

variable "set_by_tfvars" {
  type = string
}

variable "set_by_env_var" {
  type = string
}

variable "set_by_env_var_and_tfvars" {
  type = string
}


output "set_by_tfvars" {
  value = var.set_by_tfvars
}

output "set_by_env_var" {
  value = var.set_by_env_var
}

output "set_by_env_var_and_tfvars" {
  value = var.set_by_env_var_and_tfvars
}

test.tfvars.

set_by_tfvars             = "set by test.tfvars"
set_by_env_var_and_tfvars = "set by test.tfvars"

Debug Output

2025-02-03T14:50:33.879+0100 [INFO]  Terraform version: 1.10.5
2025-02-03T14:50:33.879+0100 [DEBUG] using github.com/hashicorp/go-tfe v1.70.0
2025-02-03T14:50:33.879+0100 [DEBUG] using github.com/hashicorp/hcl/v2 v2.23.0
2025-02-03T14:50:33.879+0100 [DEBUG] using github.com/hashicorp/terraform-svchost v0.1.1
2025-02-03T14:50:33.879+0100 [DEBUG] using github.com/zclconf/go-cty v1.16.2
2025-02-03T14:50:33.879+0100 [INFO]  Go runtime version: go1.23.3
2025-02-03T14:50:33.879+0100 [INFO]  CLI args: []string{"terraform", "apply", "plan"}
2025-02-03T14:50:33.879+0100 [TRACE] Stdout is a terminal of width 183
2025-02-03T14:50:33.879+0100 [TRACE] Stderr is a terminal of width 183
2025-02-03T14:50:33.879+0100 [TRACE] Stdin is a terminal
2025-02-03T14:50:33.879+0100 [DEBUG] Attempting to open CLI config file: /home/jfe/.terraformrc
2025-02-03T14:50:33.879+0100 [DEBUG] File doesn't exist, but doesn't need to. Ignoring.
2025-02-03T14:50:33.879+0100 [DEBUG] ignoring non-existing provider search directory terraform.d/plugins
2025-02-03T14:50:33.879+0100 [DEBUG] ignoring non-existing provider search directory /home/jfe/.terraform.d/plugins
2025-02-03T14:50:33.879+0100 [DEBUG] ignoring non-existing provider search directory /home/jfe/.local/share/terraform/plugins
2025-02-03T14:50:33.879+0100 [DEBUG] ignoring non-existing provider search directory /usr/local/share/terraform/plugins
2025-02-03T14:50:33.879+0100 [DEBUG] ignoring non-existing provider search directory /usr/share/terraform/plugins
2025-02-03T14:50:33.879+0100 [DEBUG] ignoring non-existing provider search directory /var/lib/snapd/desktop/terraform/plugins
2025-02-03T14:50:33.880+0100 [INFO]  CLI command args: []string{"apply", "plan"}
2025-02-03T14:50:33.882+0100 [TRACE] Meta.BackendForLocalPlan: instantiated backend of type *local.Local
2025-02-03T14:50:33.883+0100 [DEBUG] checking for provisioner in "."
2025-02-03T14:50:33.885+0100 [DEBUG] checking for provisioner in "/usr/bin"
2025-02-03T14:50:33.886+0100 [TRACE] Meta.BackendForPlan: backend *local.Local supports operations
2025-02-03T14:50:33.888+0100 [INFO]  backend/local: starting Apply operation
2025-02-03T14:50:33.889+0100 [TRACE] backend/local: requesting state manager for workspace "default"
2025-02-03T14:50:33.889+0100 [TRACE] backend/local: state manager for workspace "default" will:
 - read initial snapshot from terraform.tfstate
 - write new snapshots to terraform.tfstate
 - create any backup at terraform.tfstate.backup
2025-02-03T14:50:33.889+0100 [TRACE] backend/local: requesting state lock for workspace "default"
2025-02-03T14:50:33.890+0100 [TRACE] statemgr.Filesystem: preparing to manage state snapshots at terraform.tfstate
2025-02-03T14:50:33.890+0100 [TRACE] statemgr.Filesystem: existing snapshot has lineage "0f2e4d3c-547f-b30b-39bd-42b8fcbe001a" serial 1
2025-02-03T14:50:33.890+0100 [TRACE] statemgr.Filesystem: locking terraform.tfstate using fcntl flock
2025-02-03T14:50:33.890+0100 [TRACE] statemgr.Filesystem: writing lock metadata to .terraform.tfstate.lock.info
2025-02-03T14:50:33.890+0100 [TRACE] backend/local: reading remote state for workspace "default"
2025-02-03T14:50:33.890+0100 [TRACE] statemgr.Filesystem: reading latest snapshot from terraform.tfstate
2025-02-03T14:50:33.890+0100 [TRACE] statemgr.Filesystem: read snapshot with lineage "0f2e4d3c-547f-b30b-39bd-42b8fcbe001a" serial 1
2025-02-03T14:50:33.891+0100 [TRACE] backend/local: populating backendrun.LocalRun from plan file
2025-02-03T14:50:33.892+0100 [TRACE] terraform.NewContext: starting
2025-02-03T14:50:33.892+0100 [TRACE] terraform.NewContext: complete
╷
│ Error: Can't change variable when applying a saved plan
│ 
│ The variable set_by_env_var_and_tfvars cannot be set using the -var and -var-file options when applying a saved plan file, because a saved plan includes the variable values that
│ were set when it was created. The saved plan specifies "set by env var" as the value whereas during apply the value "set by test.tfvars" was set by an environment variable. To
│ declare an ephemeral variable which is not saved in the plan file, use ephemeral = true.
╵
2025-02-03T14:50:33.893+0100 [TRACE] statemgr.Filesystem: removing lock metadata file .terraform.tfstate.lock.info
2025-02-03T14:50:33.893+0100 [TRACE] statemgr.Filesystem: unlocking terraform.tfstate using fcntl flock

Expected Behavior

Terraform apply should apply the plan as expected using only the values already in the planned file and not tries to override them.

Actual Behavior

It tries to override with entries from the environment variable and then fails with:

│ Error: Can't change variable when applying a saved plan
│ 
│ The variable set_by_env_var_and_tfvars cannot be set using the -var and -var-file options when applying a saved plan file, because a saved plan includes the variable values that
│ were set when it was created. The saved plan specifies "set by env var" as the value whereas during apply the value "set by test.tfvars" was set by an environment variable. To
│ declare an ephemeral variable which is not saved in the plan file, use ephemeral = true.

In additional the values seem to be mixed up in the error message. According to this: override with entries from terraform.tfvars variables defined by -var-file would override the value from the environment variable. Consequently the "The saved plan specifies" should be "set by test.tfvars" and "value whereas during apply the value" should be "set by env var" (the value which is actually set by the environment variable).
How the message is currently it says the exact opposite which is wrong. ("set by test.tfvars" is not set by environment variabel but by test.tfvars).

Steps to Reproduce

  1. export TF_VAR_set_by_env_var="set by env var
  2. export TF_VAR_set_by_env_var_and_tfvars="set by env var"
  3. terraform init
  4. terraform plan -var-file=test.tfvars -out plan
  5. terraform apply "plan"

Additional Context

No response

References

Similar to #36177 but instead for auto-loaded files it happens with environment variables (too).

Generative AI / LLM assisted development?

N/A

@J0F3 J0F3 added bug new new issue not yet triaged labels Feb 3, 2025
@radeksimko radeksimko self-assigned this Feb 4, 2025
@radeksimko radeksimko removed the new new issue not yet triaged label Feb 4, 2025
@radeksimko
Copy link
Member

Reproduced, thanks for the repro case - I'll take a look into a fix.

@J0F3
Copy link
Author

J0F3 commented Feb 6, 2025

@radeksimko Perfect, Thank you for the quick fix! Do I understand it correctly that there is currently no released version which includes the fix and it will most likely included in version 1.11?

@radeksimko
Copy link
Member

It should be part of a 1.11 release next week, as we generally cut releases weekly. If you are constrained down to final releases then you may have to wait for a little longer.

There are currently no plans to cut another 1.10 patch release but it should be relatively smooth upgrade between the two major versions as we generally follow the compatibility promises made for v1.

On the note of the fix - just to set expectations right - attempting to override variables like you described is something we always considered an anti-pattern but it was silently ignored in the past before v1.10. The fix does two things - it corrects the diagnostic wording to not imply mixed source ordering anymore and "downgrades" the new diagnostic specific to TF_VAR_ from error to warning. So you will be allowed to apply but you will be warned that the supplied variables are ignored. I hope that makes sense.

All that is to say that the best way of working around this and the best long-term solution in general is to avoid passing TF_VAR_ variables to the apply runs (since all non-ephemeral variable values are already included in the plan file).

@J0F3
Copy link
Author

J0F3 commented Feb 7, 2025

That's fine. We can wait for the 1.11 release.

attempting to override variables like you described is something we always considered an anti-pattern but it was silently ignored in the past before v1.10

That is interesting to know. I was not aware of that until now. Thanks for the clarification.

All that is to say that the best way of working around this and the best long-term solution in general is to avoid passing

TF_VAR_ variables to the apply runs
In our case this happens in a CI/CD pipeline where the TF_VAR_ is set globally for all pipeline steps where the value is overwritten for one specific step through a .tfvars file (specified to the plan). So, this would mean for us that we should unset all TF_VAR_ environment variables before the apply runs? Or what would you suggestion to handle such a case?

@radeksimko
Copy link
Member

So, this would mean for us that we should unset all TF_VAR_ environment variables before the apply runs? Or what would you suggestion to handle such a case?

That would be one solution.

I was more implying that plan should be a separate phase in the CI pipeline with its own environment, so you don't have to set and then unset but rather only pass the variables to the one phase that actually requires it.

Technically you should not need the configuration for the apply phase either as that's packaged into the plan file already. So strictly speaking the same could be said about auto-loaded tfvars files - i.e. ideally these should be omitted as well. However, we pragmatically decided to not warn against that because in reality many CI systems revolve around VCS and it's just difficult to achieve that.

I like to think that separating environments between jobs in the same sequence is a bit easier though. For example, in GitHub Actions I assume you would model each (plan/apply) phase as a separate job and then each job can declare its own env rather than declaring env globally for all steps.


On a related note, the recent introduction of ephemeral values also builds on this assumption/principle. It allows you to build workflows where plan and apply each intentionally receive different (ephemeral) values. This may include credentials - e.g. you may not need write access to produce the plan so you can pass read-only credentials. So that makes easier to achieve the principle of least privilege. Relatedly - if you do have valid reasons for variables to differ between plan and apply you should declare those as ephemeral and then you can pass them via TF_VAR_.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants