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

Experiment: Ephemeral input variables, and the terraform.applying symbol #35273

Merged
merged 9 commits into from
Jun 5, 2024

Conversation

apparentlymart
Copy link
Contributor

@apparentlymart apparentlymart commented May 31, 2024

This PR includes a subset of the work from #35078, extended with what I consider to be a pragmatic amount of tests for a language experiment so that we can see that it's fundamentally working while avoiding making it too hard to remove later if this experiment is not stabilized.

My intention with this is to allow us to get some early experience and more concrete feedback about ephemeral values using alpha releases in the v1.10 series, now that v1.9 has entered its prerelease period and so the main branch represents v1.10.

This focuses on only the subset of the ephemeral values ideas that we can implement relatively-non-disruptively only in this codebase:

  • Ephemeral input variables, which is intended to allow such things as using a time-limited JSON web token in the plan phase and then using a new, separately-issued JSON web token in the apply phase.
  • The terraform.applying symbol, which is an ephemeral boolean flag that is true only during the apply phase.
  • Ephemeral output values, although this is not quite a sufficient implementation of them for general use yet because we've not designed how the CLI layer ought to deal with them.

Here's an example of a mostly-realistic configuration that could be written with this subset of the functionality:

terraform {
  # This currently requires an opt-in, and so will only be available
  # in alpha releases.
  experiments = [ephemeral_values]

  required_providers {
    aws = {
      source = "hashicorp/aws"
    }
  }
}

variable "aws_jwt" {
  type      = string
  ephemeral = true
  sensitive = true
}

locals {
  aws_read_role_arn  = "arn:aws:iam::XXXXX:role/terraform-read"
  aws_write_role_arn = "arn:aws:iam::XXXXX:role/terraform-full"
}

provider "aws" {
  region = "us-west-2"

  assume_role_with_web_identity {
    role_arn           = terraform.applying ? local.aws_write_role_arn : local.aws_read_role_arn
    web_identity_token = var.aws_jwt
  }
}

The remaining ideas for ephemeral values (e.g. ephemeral resources and write-only attributes for managed resources) are not really ready for even experimental implementation yet, because they require provider plugin protocol design, plugin framework design, and devising a strategy for how to avoid the ephemeral. reference prefix being a breaking change for a hypothetical existing provider named "ephemeral".

Therefore they are intentionally excluded here, but you can find a partial implementation of ephemeral resources in the original prototype PR #35078.

We might later decide to stabilize this subset of the functionality separately from the rest, since it's useful in its own right for some real-world use-cases, but that remains to be seen as we continue with technical design and project planning for the overall project. For now my goal is only to make it easier to play with the concepts, get practical experience, and encourage some early feedback to help make sure we're on the right track before we invest too deeply.

To keep this more tightly scoped to start I didn't yet teach the stacks runtime about ephemeral values. However, I'm hoping to do that in a followup PR because patterns like the one above are particularly useful in stacks where JSON web tokens are the way to handle provider authentication. (Stacks currently uses a workaround of passing the ephemeral JSON web token indirectly via a file on disk, whereas supporting ephemeral input values would allow us to pass the tokens only in RAM.)

This isn't actually used anywhere yet. We'll introduce uses of it in
subsequent commits.
This doesn't actually do anything yet, but in future commits it will
enable some new language features for marking input variables and output
values as "ephemeral", meaning that they don't get saved as part of
state snapshots or saved plan files.
When the ephemeral_values experiment is active, a module author can
designate individual input variables and output values as being
"ephemeral", which doesn't currently do anything but in future commits
will represent that the values can be used only in locations that don't
require Terraform to persist the value as part of state snapshots or
saved plan files.
This is a checkpoint commit on the path to supporting ephemeral values as
a cross-cutting concern in the Terraform language. An ephemeral value is
one that lives only in RAM during a single phase and so cannot persist
from the plan phase to the apply phase, or between consecutive plan/apply
rounds.

Terraform tracks whether each value is ephemeral using the cty "marks"
concept, thus achieving the same dynamic analysis as we already employ for
the concept of "sensitive values" that prevents displaying a value in the
user interface.

This commit is just a starting point which gets some of the basics into
place:
 - input variables and output values can both be statically declared as
   being ephemeral. Only ephemeral inputs/outputs can have ephemeral values
   assigned to them, and the recipient of the value sees it as ephemeral
   even if the assigned value was not already ephemeral.

   This creates a dynamic analysis cutoff point at module boundaries so
   that it's possible to determine in isolation whether a single module is
   using ephemeral values correctly, without having to test it in every
   possible calling context.
 - Managed and data resources cannot have ephemeral values assigned into
   their configurations because Terraform and providers both expect the
   resource attributes to persist between phases.
 - Ephemeral values _can_ be used in provider and provisioner
   configurations, because both of those effectively meet the definition
   of the new "ephemeral" concept despite it not previously having a name.
 - Ephemeral markings propagate through all of the built-in language
   features for dynamic analysis purposes, largely relying on cty's efforts
   to do that in similar vein as for sensitive marks. In particular,
   it's possible to define a local value whose expression produces
   an ephemeral result, and passing ephemeral values to functions should
   propagate the ephemeral mark to the results when appropriate. (I've not
   yet thoroughly reviewed all of the built-in functions for correct
   marks handling though, so there may be some gaps to address in later
   commits.)

The next step for this work will be to change the modules runtime to have
support for a workflow involving ephemeral _root_ input variables, where
their values must be re-supplied during the apply phase. That will then
achieve (in experimental capacity) the first goal of ephemeral values: to
be able to provide non-persistent settings such as time-limited API tokens
to use in provider configuration blocks.
We'll use this to track what subset of the ephemeral input variables were
set in the planning options, and which ones must therefore be re-supplied
in the apply phase options.

This new plan field is not yet populated anywhere. The uses of this will
follow in subsequent commits.
The new concept of "ephemeral input variables" creates the possibility of
needing to provide an input variable value during the apply phase, rather
than just retaining it in the plan.

Now we'll remember in the plan the names of the variables that need to be
re-supplied during apply -- that is, any ephemeral values whose plan-time
values were non-null -- and then check at the start of the apply phase
whether those variables (and _only_ those variables) were provided in the
planning options.

This doesn't yet update Terraform CLI to actually populate this stuff, so
as of this commit any plan with apply-time variables is effectively
unapplyable. We'll deal with that in future commits.
When the ephemeral variables experiment is active we can potentially have
input variables whose values need to be provided separately in both the
plan and apply phases, as a compromise to avoid writing those values as
part of a saved plan file and to allow the given value to vary between
the two phases if necessary.

The CLI layer must therefore re-process the given input variable values
during the apply phase whenever this experiment is active for the root
module and the plan recorded at least one apply-time variable name.

To reduce the risk of this new logic accidentally impacting
non-experimental usage, the whole call is guarded by whether the root
module is participating in the experiment. Checking just the root module
is sufficient here because only the root input variables are directly
handled by the CLI layer; input variables for descendent modules are
handled entirely within the modules runtime.
To support ephemeral values we need a more complicated set of rules about
what input variables can and must be set when applying a saved plan. The
command itself does not have enough information to implement those rules
itself, so we'll let them pass through and check this in the local
backend's apply phase instead.

The local backend's apply phase already had basic support for dealing with
apply-time variable values, but it'll now also be responsible for
rejecting attempts to set variables when the experiment isn't enabled,
to keep all of this logic in roughly the same place.
This new symbol returns an ephemeral boolean flag that is true only during
the apply phase and false otherwise. Since this involves an ephemeral
value and those are still experimental, this symbol is also available only
when opted in to the ephemeral_values experiment.

The intended use for this is to configure a provider with a different (and
probably more privileged) role or username during the apply phase when
Terraform is actually trying to change infrastructure, so that planning
can be done with a more limited level of access that might, for example,
only allow _reading_ from the remote system.

    role_arn = (
      terraform.applying ? local.write_role_arn : local.read_role_arn
    )
Copy link
Member

@liamcervante liamcervante left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks fine to me, seems like everything is well protected behind the flag.

I'm just curious why terraform.applying should be ephemeral. Is it just because it's value can change between plan and apply so we want to make sure it doesn't get written anywhere?

@apparentlymart
Copy link
Contributor Author

Indeed, in fact it must change between plan and apply (by definition) and so it wouldn't be valid to use it in any context that must remain constant between plan and apply. For example, if it were used to populate something in a resource block then the final plan created during the apply phase could differ from the initial plan, which would invalidate the plan and cause apply to fail. It being ephemeral means we can catch that mistake during the plan phase instead.

The primary place to use it would be in a provider block, where ephemeral values are allowed.

@apparentlymart apparentlymart merged commit 7c928fc into main Jun 5, 2024
10 checks passed
@apparentlymart apparentlymart deleted the f-ephemeral-variables branch June 5, 2024 16:09
Copy link

github-actions bot commented Jun 5, 2024

Reminder for the merging maintainer: if this is a user-visible change, please update the changelog on the appropriate release branch.

Copy link

github-actions bot commented Jul 6, 2024

I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 6, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants