docs: Import some existing docs to bootstrap the codebase docs section
For a while now we've been gathering some codebase-level docs that felt out-of-place on the main Terraform website (since they are about the implementation, not usage) but we had no existing suitable place to put them. In order to make this information more available (and, hopefully, more likely to stay up-to-date as we change things), here we'll establish the "docs" directory as a place to keep documentation aimed at those who are working on code changes to the Terraform Core codebase. User-oriented docs should never appear in this directory. The Terraform website is always the better place for those. The set of docs here is rudimentary to start and we'll see if it makes sense to expand and reorganize it over time based on the experience with having these initial docs available.
This commit is contained in:
parent
657dddceab
commit
18c88f5a41
|
@ -0,0 +1,31 @@
|
||||||
|
# Terraform Core Codebase Documentation
|
||||||
|
|
||||||
|
This directory contains some documentation about the Terraform Core codebase,
|
||||||
|
aimed at readers who are interested in making code contributions.
|
||||||
|
|
||||||
|
If you're looking for information on _using_ Terraform, please instead refer
|
||||||
|
to [the main Terraform CLI documentation](https://www.terraform.io/docs/cli-index.html).
|
||||||
|
|
||||||
|
## Terraform Core Architecture Documents
|
||||||
|
|
||||||
|
* [Terraform Core Architecture Summary](./architecture.md): an overview of the
|
||||||
|
main components of Terraform Core and how they interact. This is the best
|
||||||
|
starting point if you are diving in to this codebase for the first time.
|
||||||
|
|
||||||
|
* [Resource Instance Change Lifecycle](./resource-instance-change-lifecycle.md):
|
||||||
|
a description of the steps in validating, planning, and applying a change
|
||||||
|
to a resource instance, from the perspective of the provider plugin RPC
|
||||||
|
operations. This may be useful for understanding the various expectations
|
||||||
|
Terraform enforces about provider behavior, either if you intend to make
|
||||||
|
changes to those behaviors or if you are implementing a new Terraform plugin
|
||||||
|
SDK and so wish to conform to them.
|
||||||
|
|
||||||
|
(If you are planning to write a new provider using the _official_ SDK then
|
||||||
|
please refer to [the Extend documentation](https://www.terraform.io/docs/extend/index.html)
|
||||||
|
instead; it presents similar information from the perspective of the SDK
|
||||||
|
API, rather than the plugin wire protocol.)
|
||||||
|
|
||||||
|
## Contribution Guides
|
||||||
|
|
||||||
|
* [Maintainer Etiquette](./maintainer-etiquette.md): guidelines and expectations
|
||||||
|
for those who serve as Pull Request reviewers, issue triagers, etc.
|
|
@ -0,0 +1,390 @@
|
||||||
|
# Terraform Core Architecture Summary
|
||||||
|
|
||||||
|
This document is a summary of the main components of Terraform Core and how
|
||||||
|
data and requests flow between these components. It's intended as a primer
|
||||||
|
to help navigate the codebase to dig into more details.
|
||||||
|
|
||||||
|
We assume some familiarity with user-facing Terraform concepts like
|
||||||
|
configuration, state, CLI workflow, etc. The Terraform website has
|
||||||
|
documentation on these ideas.
|
||||||
|
|
||||||
|
## Terraform Request Flow
|
||||||
|
|
||||||
|
The following diagram shows an approximation of how a user command is
|
||||||
|
executed in Terraform:
|
||||||
|
|
||||||
|
![Terraform Architecture Diagram, described in text below](./images/architecture-overview.png)
|
||||||
|
|
||||||
|
Each of the different subsystems (solid boxes) in this diagram is described
|
||||||
|
in more detail in a corresponding section below.
|
||||||
|
|
||||||
|
## CLI (`command` package)
|
||||||
|
|
||||||
|
Each time a user runs the `terraform` program, aside from some initial
|
||||||
|
bootstrapping in the root package (not shown in the diagram) execution
|
||||||
|
transfers immediately into one of the "command" implementations in
|
||||||
|
[the `command` package](https://godoc.org/github.com/hashicorp/terraform/command).
|
||||||
|
The mapping between the user-facing command names and
|
||||||
|
their corresponding `command` package types can be found in the `commands.go`
|
||||||
|
file in the root of the repository.
|
||||||
|
|
||||||
|
The full flow illustrated above does not actually apply to _all_ commands,
|
||||||
|
but it applies to the main Terraform workflow commands `terraform plan` and
|
||||||
|
`terraform apply`, along with a few others.
|
||||||
|
|
||||||
|
For these commands, the role of the command implemtation is to read and parse
|
||||||
|
any command line arguments, command line options, and environment variables
|
||||||
|
that are needed for the given command and use them to produce a
|
||||||
|
[`backend.Operation`](https://godoc.org/github.com/hashicorp/terraform/backend#Operation)
|
||||||
|
object that describes an action to be taken.
|
||||||
|
|
||||||
|
An _operation_ consists of:
|
||||||
|
|
||||||
|
* The action to be taken (e.g. "plan", "apply").
|
||||||
|
* The name of the [workspace](https://www.terraform.io/docs/state/workspaces.html)
|
||||||
|
where the action will be taken.
|
||||||
|
* Root module input variables to use for the action.
|
||||||
|
* For the "plan" operation, a path to the directory containing the configuration's root module.
|
||||||
|
* For the "apply" operation, the plan to apply.
|
||||||
|
* Various other less-common options/settings such as `-target` addresses, the
|
||||||
|
"force" flag, etc.
|
||||||
|
|
||||||
|
The operation is then passed to the currently-selected
|
||||||
|
[backend](https://www.terraform.io/docs/backends/index.html). Each backend name
|
||||||
|
corresponds to an implementation of
|
||||||
|
[`backend.Backend`](https://godoc.org/github.com/hashicorp/terraform/backend#Backend), using a
|
||||||
|
mapping table in
|
||||||
|
[the `backend/init` package](https://godoc.org/github.com/hashicorp/terraform/backend/init).
|
||||||
|
|
||||||
|
Backends that are able to execute operations additionally implement
|
||||||
|
[`backend.Enhanced`](https://godoc.org/github.com/hashicorp/terraform/backend#Enhanced);
|
||||||
|
the command-handling code calls `Operation` with the operation it has
|
||||||
|
constructed, and then the backend is responsible for executing that action.
|
||||||
|
|
||||||
|
Most backends do _not_ implement this interface, and so the `command` package
|
||||||
|
wraps these backends in an instance of
|
||||||
|
[`local.Local`](https://godoc.org/github.com/hashicorp/terraform/backend/local#Local),
|
||||||
|
causing the operation to be executed locally within the `terraform` process
|
||||||
|
itself, which (at the time of writing) is currently the only way an operation
|
||||||
|
can be executed.
|
||||||
|
|
||||||
|
## Backends
|
||||||
|
|
||||||
|
A _backend_ has a number of responsibilities in Terraform:
|
||||||
|
|
||||||
|
* Execute operations (e.g. plan, apply)
|
||||||
|
* Store state
|
||||||
|
* Store workspace-defined variables (in the future; not yet implemented)
|
||||||
|
|
||||||
|
As described above, the `local.Local` implementation -- named `local` from the
|
||||||
|
user's standpoint -- is the only backend which implements _all_ functionality.
|
||||||
|
Backends that cannot execute operations (at the time of writing, all except
|
||||||
|
`local`) can be wrapped inside `local.Local` to perform operations locally
|
||||||
|
while storing the [state](https://www.terraform.io/docs/state/index.html)
|
||||||
|
elsewhere.
|
||||||
|
|
||||||
|
To execute an operation locally, the `local` backend uses a _state manager_
|
||||||
|
(either
|
||||||
|
[`state.LocalState`](https://godoc.org/github.com/hashicorp/terraform/state#LocalState) if the
|
||||||
|
local backend is being used directly, or an implementation provided by whatever
|
||||||
|
backend is being wrapped) to retrieve the current state for the workspace
|
||||||
|
specified in the operation, then uses the _config loader_ to load and do
|
||||||
|
initial processing/validation of the configuration specified in the
|
||||||
|
operation. It then uses these, along with the other settings given in the
|
||||||
|
operation, to construct a
|
||||||
|
[`terraform.Context`](https://godoc.org/github.com/hashicorp/terraform/terraform#Context),
|
||||||
|
which is the main object that actually performs Terraform operations.
|
||||||
|
|
||||||
|
The `local` backend finally calls an appropriate method on that context to
|
||||||
|
begin execution of the relevant command, such as
|
||||||
|
[`Plan`](https://godoc.org/github.com/hashicorp/terraform/terraform#Context.Plan)
|
||||||
|
or
|
||||||
|
[`Apply`](), which in turn constructs a graph using a _graph builder_,
|
||||||
|
described in a later section.
|
||||||
|
|
||||||
|
## Configuration Loader
|
||||||
|
|
||||||
|
The top-level configuration structure is represented by model types in
|
||||||
|
[package `configs`](https://godoc.org/github.com/hashicorp/terraform/configs).
|
||||||
|
A whole configuration (the root module plus all of its descendent modules)
|
||||||
|
is represented by
|
||||||
|
[`configs.Config`](https://godoc.org/github.com/hashicorp/terraform/configs#Config).
|
||||||
|
|
||||||
|
The `configs` package contains some low-level functionality for constructing
|
||||||
|
configuration objects, but the main entry point is in the sub-package
|
||||||
|
[`configload`](https://godoc.org/github.com/hashicorp/terraform/configs/configload]),
|
||||||
|
via
|
||||||
|
[`configload.Loader`](https://godoc.org/github.com/hashicorp/terraform/configs/configload#Loader).
|
||||||
|
A loader deals with all of the details of installing child modules
|
||||||
|
(during `terraform init`) and then locating those modules again when a
|
||||||
|
configuration is loaded by a backend. It takes the path to a root module
|
||||||
|
and recursively loads all of the child modules to produce a single
|
||||||
|
[`configs.Config`](https://godoc.org/github.com/hashicorp/terraform/configs#Config)
|
||||||
|
representing the entire configuration.
|
||||||
|
|
||||||
|
Terraform expects configuration files written in the Terraform language, which
|
||||||
|
is a DSL built on top of
|
||||||
|
[HCL](https://github.com/hashicorp/hcl2). Some parts of the configuration
|
||||||
|
cannot be interpreted until we build and walk the graph, since they depend
|
||||||
|
on the outcome of other parts of the configuration, and so these parts of
|
||||||
|
the configuration remain represented as the low-level HCL types
|
||||||
|
[hcl.Body](https://godoc.org/github.com/hashicorp/hcl2/hcl#Body)
|
||||||
|
and
|
||||||
|
[hcl.Expression](https://godoc.org/github.com/hashicorp/hcl2/hcl#Expression),
|
||||||
|
allowing Terraform to interpret them at a more appropriate time.
|
||||||
|
|
||||||
|
## State Manager
|
||||||
|
|
||||||
|
A _state manager_ is responsible for storing and retrieving the
|
||||||
|
[Terraform state](https://www.terraform.io/docs/state/index.html)
|
||||||
|
for a particular workspace. Each manager is an implementation of
|
||||||
|
[`state.State`](https://godoc.org/github.com/hashicorp/terraform/state#State)
|
||||||
|
provided by a _backend_.
|
||||||
|
|
||||||
|
The implementation
|
||||||
|
[`state.LocalState`](https://godoc.org/github.com/hashicorp/terraform/state#LocalState) is used
|
||||||
|
by default (by the `local` backend) and is responsible for the familiar
|
||||||
|
`terraform.tfstate` local file that most Terraform users start with, before
|
||||||
|
they switch to [remote state](https://www.terraform.io/docs/state/remote.html).
|
||||||
|
Other implementations of `state.State` are used to implement remote state.
|
||||||
|
Each of these saves and retrieves state via a remote network service
|
||||||
|
appropriate to the backend that creates it.
|
||||||
|
|
||||||
|
A state manager accepts and returns state as a
|
||||||
|
[`terraform.State`](https://godoc.org/github.com/hashicorp/terraform/terraform#State)
|
||||||
|
object. The state manager is responsible for exactly how that object is
|
||||||
|
serialized and stored, but all state managers at the time of writing use
|
||||||
|
the same JSON serialization format, storing the resulting JSON bytes in some
|
||||||
|
kind of arbitrary blob store.
|
||||||
|
|
||||||
|
## Graph Builder
|
||||||
|
|
||||||
|
A _graph builder_ is called by a
|
||||||
|
[`terraform.Context`](https://godoc.org/github.com/hashicorp/terraform/terraform#Context)
|
||||||
|
method (e.g. `Plan` or `Apply`) to produce the graph that will be used
|
||||||
|
to represent the necessary steps for that operation and the dependency
|
||||||
|
relationships between them.
|
||||||
|
|
||||||
|
In most cases, the
|
||||||
|
[vertices](https://en.wikipedia.org/wiki/Vertex_(graph_theory)) of Terraform's
|
||||||
|
graphs each represent a specific object in the configuration, or something
|
||||||
|
derived from those configuration objects. For example, each `resource` block
|
||||||
|
in the configuration has one corresponding
|
||||||
|
[`GraphNodeResource`](https://godoc.org/github.com/hashicorp/terraform/terraform#GraphNodeResource)
|
||||||
|
vertex representing it in the "plan" graph. (Terraform Core uses terminology
|
||||||
|
inconsistently, describing graph vertices also as graph nodes in various
|
||||||
|
places. These both describe the same concept.)
|
||||||
|
|
||||||
|
The [edges](https://en.wikipedia.org/wiki/Glossary_of_graph_theory_terms#edge)
|
||||||
|
in the graph represent "must happen after" relationships. These define the
|
||||||
|
order in which the vertices are evaluated, ensuring that e.g. one resource is
|
||||||
|
created before another resource that depends on it.
|
||||||
|
|
||||||
|
Each operation has its own graph builder, because the graph building process
|
||||||
|
is different for each. For example, a "plan" operation needs a graph built
|
||||||
|
directly from the configuration, but an "apply" operation instead builds its
|
||||||
|
graph from the set of changes described in the plan that is being applied.
|
||||||
|
|
||||||
|
The graph builders all work in terms of a sequence of _transforms_, which
|
||||||
|
are implementations of
|
||||||
|
[`terraform.GraphTransformer`](https://godoc.org/github.com/hashicorp/terraform/terraform#GraphTransformer).
|
||||||
|
Implementations of this interface just take a graph and mutate it in any
|
||||||
|
way needed, and so the set of available transforms is quite varied. Some
|
||||||
|
import examples include:
|
||||||
|
|
||||||
|
* [`ConfigTransformer`](https://godoc.org/github.com/hashicorp/terraform/terraform#ConfigTransformer),
|
||||||
|
which creates a graph vertex for each `resource` block in the configuration.
|
||||||
|
|
||||||
|
* [`StateTransformer`](https://godoc.org/github.com/hashicorp/terraform/terraform#StateTransformer),
|
||||||
|
which creates a graph vertex for each resource instance currently tracked
|
||||||
|
in the state.
|
||||||
|
|
||||||
|
* [`ReferenceTransformer`](https://godoc.org/github.com/hashicorp/terraform/terraform#ReferenceTransformer),
|
||||||
|
which analyses the configuration to find dependencies between resources and
|
||||||
|
other objects and creates any necessary "happens after" edges for these.
|
||||||
|
|
||||||
|
* [`ProviderTransformer`](https://godoc.org/github.com/hashicorp/terraform/terraform#ProviderTransformer),
|
||||||
|
which associates each resource or resource instance with exactly one
|
||||||
|
provider configuration (implementing
|
||||||
|
[the inheritance rules](https://www.terraform.io/docs/modules/usage.html#providers-within-modules))
|
||||||
|
and then creates "happens after" edges to ensure that the providers are
|
||||||
|
initialized before taking any actions with the resources that belong to
|
||||||
|
them.
|
||||||
|
|
||||||
|
There are many more different graph transforms, which can be discovered
|
||||||
|
by reading the source code for the different graph transformers. Each graph
|
||||||
|
builder uses a different subset of these depending on the needs of the
|
||||||
|
operation that is being performed.
|
||||||
|
|
||||||
|
The result of graph building is a
|
||||||
|
[`terraform.Graph`](https://godoc.org/github.com/hashicorp/terraform/terraform#Graph), which
|
||||||
|
can then be processed using a _graph walker_.
|
||||||
|
|
||||||
|
## Graph Walk
|
||||||
|
|
||||||
|
The process of walking the graph visits each vertex of that graph in a way
|
||||||
|
which respects the "happens after" edges in the graph. The walk algorithm
|
||||||
|
itself is implemented in
|
||||||
|
[the low-level `dag` package](https://godoc.org/github.com/hashicorp/terraform/dag#AcyclicGraph.Walk)
|
||||||
|
(where "DAG" is short for [_Directed Acyclic Graph_](https://en.wikipedia.org/wiki/Directed_acyclic_graph)), in
|
||||||
|
[`AcyclicGraph.Walk`](https://godoc.org/github.com/hashicorp/terraform/dag#AcyclicGraph.Walk).
|
||||||
|
However, the "interesting" Terraform walk functionality is implemented
|
||||||
|
in
|
||||||
|
[`terraform.ContextGraphWalker`](https://godoc.org/github.com/hashicorp/terraform/terraform#ContextGraphWalker),
|
||||||
|
which implements a small set of higher-level operations that are performed
|
||||||
|
during the graph walk:
|
||||||
|
|
||||||
|
* `EnterPath` is called once for each module in the configuration, taking a
|
||||||
|
module address and returning a
|
||||||
|
[`terraform.EvalContext`](https://godoc.org/github.com/hashicorp/terraform/terraform#EvalContext)
|
||||||
|
that tracks objects within that module. `terraform.Context` is the _global_
|
||||||
|
context for the entire operation, while `terraform.EvalContext` is a
|
||||||
|
context for processing within a single module, and is the primary means
|
||||||
|
by which the namespaces in each module are kept separate.
|
||||||
|
|
||||||
|
* `EnterEvalTree` and `ExitEvalTree` are each called once for each vertex
|
||||||
|
in the graph during _vertex evaluation_, which is described in the following
|
||||||
|
section.
|
||||||
|
|
||||||
|
Each vertex in the graph is evaluated, in an order that guarantees that the
|
||||||
|
"happens after" edges will be respected. If possible, the graph walk algorithm
|
||||||
|
will evaluate multiple vertices concurrently. Vertex evaluation code must
|
||||||
|
therefore make careful use of concurrency primitives such as mutexes in order
|
||||||
|
to coordinate access to shared objects such as the `terraform.State` object.
|
||||||
|
|
||||||
|
## Vertex Evaluation
|
||||||
|
|
||||||
|
The action taken for each vertex during the _graph walk_ is called
|
||||||
|
_evaluation_. In practice, evaluation includes any arbitrary action that make
|
||||||
|
sense for a particular vertex type.
|
||||||
|
|
||||||
|
For example, evaluation of a vertex representing a resource instance during
|
||||||
|
a plan operation would include the following high-level steps:
|
||||||
|
|
||||||
|
* Retrieve the resource's associated provider from the `EvalContext`. This
|
||||||
|
should already be initialized earlier by the provider's own graph vertex,
|
||||||
|
due to the "happens after" edge between the resource node and the provider
|
||||||
|
node.
|
||||||
|
|
||||||
|
* Retrieve from the state the portion relevant to the specific resource
|
||||||
|
instance being evaluated.
|
||||||
|
|
||||||
|
* Evaluate the attribute expressions given for the resource in configuration.
|
||||||
|
This often involves retrieving the state of _other_ resource instances so
|
||||||
|
that their values can be copied or transformed into the current instance's
|
||||||
|
attributes, which is coordinated by the `EvalContext`.
|
||||||
|
|
||||||
|
* Pass the current instance state and the resource configuration to the
|
||||||
|
provider, asking the provider to produce an _instance diff_ representing the
|
||||||
|
differences between the state and the configuration.
|
||||||
|
|
||||||
|
* Save the instance diff as part of the plan that is being constructed by
|
||||||
|
this operation.
|
||||||
|
|
||||||
|
Each evaluation step for a vertex is an implementation of
|
||||||
|
[`terraform.EvalNode`](https://godoc.org/github.com/hashicorp/terraform/terraform#EvalNode).
|
||||||
|
As with graph transforms, the behavior of these implementations varies widely:
|
||||||
|
whereas graph transforms can take any action against the graph, an `EvalNode`
|
||||||
|
implementation can take any action against the `EvalContext`.
|
||||||
|
|
||||||
|
The implementation of `terraform.EvalContext` used in real processing
|
||||||
|
(as opposed to testing) is
|
||||||
|
[`terraform.BuiltinEvalContext`](https://godoc.org/github.com/hashicorp/terraform/terraform#BuiltinEvalContext).
|
||||||
|
It provides coordinated access to plugins, the current state, and the current
|
||||||
|
plan via the `EvalContext` interface methods.
|
||||||
|
|
||||||
|
In order to be evaluated, a vertex must implement
|
||||||
|
[`terraform.GraphNodeEvalable`](https://godoc.org/github.com/hashicorp/terraform/terraform#GraphNodeEvalable),
|
||||||
|
which has a single method that returns an `EvalNode`. In practice, most
|
||||||
|
implementations return an instance of
|
||||||
|
[`terraform.EvalSequence`](https://godoc.org/github.com/hashicorp/terraform/terraform#EvalSequence),
|
||||||
|
which wraps a number of other `EvalNode` objects to be executed in sequence.
|
||||||
|
|
||||||
|
There are numerous `EvalNode` implementations with different behaviors, but
|
||||||
|
some prominent examples are:
|
||||||
|
|
||||||
|
* [`EvalReadState`](https://godoc.org/github.com/hashicorp/terraform/terraform#EvalReadState),
|
||||||
|
which extracts the data for a particular resource instance from the state.
|
||||||
|
|
||||||
|
* [`EvalWriteState`](https://godoc.org/github.com/hashicorp/terraform/terraform#EvalWriteState),
|
||||||
|
which conversely replaces the data for a particular resource instance in
|
||||||
|
the state with some updated data resulting from changes made by the
|
||||||
|
provider.
|
||||||
|
|
||||||
|
* [`EvalInitProvider`](https://godoc.org/github.com/hashicorp/terraform/terraform#EvalInitProvider),
|
||||||
|
which starts up a provider plugin and passes the user-provided configuration
|
||||||
|
to it, caching the provider inside the `EvalContext`.
|
||||||
|
|
||||||
|
* [`EvalGetProvider`](https://godoc.org/github.com/hashicorp/terraform/terraform#EvalGetProvider),
|
||||||
|
which retrieves an already-initialized provider that is cached in the
|
||||||
|
`EvalContext`.
|
||||||
|
|
||||||
|
* [`EvalValidateResource`](https://godoc.org/github.com/hashicorp/terraform/terraform#EvalValidateResource),
|
||||||
|
which checks to make sure that resource configuration conforms to the
|
||||||
|
expected schema and gives a provider plugin the opportunity to check that
|
||||||
|
given values are within the expected range, etc.
|
||||||
|
|
||||||
|
* [`EvalApply`](https://godoc.org/github.com/hashicorp/terraform/terraform#EvalApply),
|
||||||
|
which calls into a provider plugin to make apply some planned changes
|
||||||
|
to a given resource instance.
|
||||||
|
|
||||||
|
All of the evaluation steps for a vertex must complete successfully before
|
||||||
|
the graph walk will begin evaluation for other vertices that have
|
||||||
|
"happens after" edges. Evaluation can fail with one or more errors, in which
|
||||||
|
case the graph walk is halted and the errors are returned to the user.
|
||||||
|
|
||||||
|
### Expression Evaluation
|
||||||
|
|
||||||
|
An important part of vertex evaluation for most vertex types is evaluating
|
||||||
|
any expressions in the configuration block associated with the vertex. This
|
||||||
|
completes the processing of the portions of the configuration that were not
|
||||||
|
processed by the configuration loader.
|
||||||
|
|
||||||
|
At the time of writing this, the portions of Terraform that handle this
|
||||||
|
are in flux in a development branch, and so we cannot currently link to the
|
||||||
|
relevant portions of code, but the high-level process for this is:
|
||||||
|
|
||||||
|
* Analyze the configuration expressions to see which other objects they refer
|
||||||
|
to. For example, the expression `aws_instance.example[1]` refers to one of
|
||||||
|
the instances created by a `resource "aws_instance" "example"` block in
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
* Retrieve from the state the data for the objects that are referred to and
|
||||||
|
create a lookup table of the values from these objects that the
|
||||||
|
HCL evaluation code can refer to.
|
||||||
|
|
||||||
|
* Prepare the table of built-in functions so that HCL evaluation can refer to
|
||||||
|
them.
|
||||||
|
|
||||||
|
* Ask HCL to evaluate each attribute's expression (a `hcl.Expression` object)
|
||||||
|
against the data and function lookup tables.
|
||||||
|
|
||||||
|
This produces a dynamic value represented as a
|
||||||
|
[`cty.Value`](https://godoc.org/github.com/zclconf/go-cty/cty#Value).
|
||||||
|
This Go type represents values from the Terraform language and such values
|
||||||
|
are eventually passed to plugins.
|
||||||
|
|
||||||
|
### Sub-graphs
|
||||||
|
|
||||||
|
Some vertices have a special additional behavior that happens after their
|
||||||
|
evaluation steps are complete, where the vertex implementation is given
|
||||||
|
the opportunity to build another separate graph which will be walked as part
|
||||||
|
of the evaluation of the vertex.
|
||||||
|
|
||||||
|
The main example of this is when a `resource` block has the `count` argument
|
||||||
|
set. In that case, the plan graph initially contains one vertex for each
|
||||||
|
`resource` block, but that graph then _dynamically expands_ to have a sub-graph
|
||||||
|
containing one vertex for each instance requested by the count. That is, the
|
||||||
|
sub-graph of `aws_instance.example` might contain vertices for
|
||||||
|
`aws_instance.example[0]`, `aws_instance.example[1]`, etc. This is necessary
|
||||||
|
because the `count` argument may refer to other objects whose values are not
|
||||||
|
known when the main graph is constructed, but become known while evaluating
|
||||||
|
other vertices in the main graph.
|
||||||
|
|
||||||
|
This special behavior applies to vertex objects that implement
|
||||||
|
[`terraform.GraphNodeDynamicExpandable`](https://godoc.org/github.com/hashicorp/terraform/terraform#GraphNodeDynamicExpandable). Such vertexes have their own nested _graph builder_, _graph walk_,
|
||||||
|
and _vertex evaluation_ steps, with the same behaviors as described in these
|
||||||
|
sections for the main graph. The difference is in which graph transforms
|
||||||
|
are used to construct the graph and in which evaluation steps apply to the
|
||||||
|
nodes in that sub-graph.
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 281 KiB |
Binary file not shown.
After Width: | Height: | Size: 243 KiB |
|
@ -3,6 +3,10 @@
|
||||||
Are you a core maintainer of Terraform? Great! Here's a few notes
|
Are you a core maintainer of Terraform? Great! Here's a few notes
|
||||||
to help you get comfortable when working on the project.
|
to help you get comfortable when working on the project.
|
||||||
|
|
||||||
|
This documentation is somewhat outdated since it still includes provider-related
|
||||||
|
information even though providers are now developed in their own separate
|
||||||
|
codebases, but the general information is still valid.
|
||||||
|
|
||||||
## Expectations
|
## Expectations
|
||||||
|
|
||||||
We value the time you spend on the project and as such your maintainer status
|
We value the time you spend on the project and as such your maintainer status
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
# Terraform Resource Instance Change Lifecycle
|
||||||
|
|
||||||
|
This document describes the relationships between the different operations
|
||||||
|
called on a Terraform Provider to handle a change to a resource instance.
|
||||||
|
|
||||||
|
![](https://gist.githubusercontent.com/apparentlymart/c4e401cdb724fa5b866850c78569b241/raw/fefa90ce625c240d5323ea28c92943c2917e36e3/resource_instance_change_lifecycle.png)
|
||||||
|
|
||||||
|
The process includes several different artifacts that are all objects
|
||||||
|
conforming to the schema of the resource type in question, representing
|
||||||
|
different subsets of the instance for different purposes:
|
||||||
|
|
||||||
|
* **Configuration**: Contains only values from the configuration, including
|
||||||
|
unknown values in any case where the argument value is derived from an
|
||||||
|
unknown result on another resource. Any attributes not set directly in the
|
||||||
|
configuration are null.
|
||||||
|
|
||||||
|
* **Prior State**: The full object produced by a previous apply operation, or
|
||||||
|
null if the instance is being created for the first time.
|
||||||
|
|
||||||
|
* **Proposed New State**: Terraform Core merges the non-null values from
|
||||||
|
the configuration with any computed attribute results in the prior state
|
||||||
|
to produce a combined object that includes both, to avoid each provider
|
||||||
|
having to re-implement that merging logic. Will be null when planning a
|
||||||
|
delete operation.
|
||||||
|
|
||||||
|
* **Planned New State**: An approximation of the result the provider expects
|
||||||
|
to produce when applying the requested change. This is usually derived from
|
||||||
|
the proposed new state by inserting default attribute values in place of
|
||||||
|
null values and overriding any computed attribute values that are expected
|
||||||
|
to change as a result of the apply operation. May include unknown values
|
||||||
|
for attributes whose results cannot be predicted until apply. Will be null
|
||||||
|
when planning a delete operation.
|
||||||
|
|
||||||
|
* **New State**: The actual result of applying the change, with any unknown
|
||||||
|
values from the planned new state replaced with final result values. This
|
||||||
|
value will be used as the input to plan the next operation.
|
||||||
|
|
||||||
|
The remaining sections describe the three provider API functions that are
|
||||||
|
called to plan and apply a change, including the expectations Terraform Core
|
||||||
|
enforces for each.
|
||||||
|
|
||||||
|
For historical reasons, the original Terraform SDK is exempt from error
|
||||||
|
messages produced when the assumptions are violated, but violating them will
|
||||||
|
often cause downstream errors nonetheless, because Terraform's workflow
|
||||||
|
depends on these contracts being met.
|
||||||
|
|
||||||
|
The following section uses the word "attribute" to refer to the named
|
||||||
|
attributes described in the resource type schema. A schema may also include
|
||||||
|
nested blocks, which contain their _own_ set of attributes; the constraints
|
||||||
|
apply recursively to these nested attributes too.
|
||||||
|
|
||||||
|
Nested blocks are a configuration-only construct and so the number of blocks
|
||||||
|
cannot be changed on the fly during planning or during apply: each block
|
||||||
|
represented in the configuration must have a corresponding nested object in
|
||||||
|
the planned new state and new state, or an error will be returned.
|
||||||
|
|
||||||
|
If a provider wishes to report about new instances of the sub-object type
|
||||||
|
represented by nested blocks that are created implicitly during the apply
|
||||||
|
operation -- for example, if a compute instance gets a default network
|
||||||
|
interface created when none are explicitly specified -- this must be done via
|
||||||
|
separate `Computed` attributes alongside the nested blocks, which could for
|
||||||
|
example be a list or map of objects that includes a mixture of the objects
|
||||||
|
described by the nested blocks in the configuration and any additional objects
|
||||||
|
created by the remote system.
|
||||||
|
|
||||||
|
## ValidateResourceTypeConfig
|
||||||
|
|
||||||
|
`ValidateResourceTypeConfig` is the provider's opportunity to perform any
|
||||||
|
custom validation of the configuration that cannot be represented in the schema
|
||||||
|
alone.
|
||||||
|
|
||||||
|
In principle the provider can require any constraint it sees fit here, though
|
||||||
|
in practice it should avoid reporting errors when values are unknown (so that
|
||||||
|
the operation can proceed and determine those values downstream) and if
|
||||||
|
it intends to apply default values during `PlanResourceChange` then it must
|
||||||
|
tolerate those attributes being null at validation time, because validation
|
||||||
|
happens before planning.
|
||||||
|
|
||||||
|
A provider should repeat similar validation logic at the start of
|
||||||
|
`PlanResourceChange`, in order to catch any new
|
||||||
|
values that have switched from unknown to known along the way during the
|
||||||
|
overall plan/apply flow.
|
||||||
|
|
||||||
|
## PlanResourceChange
|
||||||
|
|
||||||
|
The purpose of `PlanResourceChange` is to predict the approximate effect of
|
||||||
|
a subsequent apply operation, allowing Terraform to render the plan for the
|
||||||
|
user and to propagate any predictable results downstream through expressions
|
||||||
|
in the configuration.
|
||||||
|
|
||||||
|
The _planned new state_ returned from the provider must meet the following
|
||||||
|
constraints:
|
||||||
|
|
||||||
|
* Any attribute that was non-null in the configuration must either preserve
|
||||||
|
the exact configuration value or return the corresponding attribute value
|
||||||
|
from the prior state. (Do the latter if you determine that the change is not
|
||||||
|
functionally significant, such as if the value is a JSON string that has
|
||||||
|
changed only in the positioning of whitespace.)
|
||||||
|
|
||||||
|
* Any attribute that is marked as computed in the schema _and_ is null in the
|
||||||
|
configuration may be set by the provider to any arbitrary value of the
|
||||||
|
expected type.
|
||||||
|
|
||||||
|
* If a computed attribute has any _known_ value in the planned new state, the
|
||||||
|
provider will be required to ensure that it is unchanged in the new state
|
||||||
|
returned by `ApplyResourceChange`, or return an error explaining why it
|
||||||
|
changed. Set an attribute to an unknown value to indicate that its final
|
||||||
|
result will be determined during `ApplyResourceChange`.
|
||||||
|
|
||||||
|
`PlanResourceChange` is actually called twice for each resource type.
|
||||||
|
It will be called first during the planning phase before Terraform prints out
|
||||||
|
the diff to the user for confirmation. If the user accepts the plan, then
|
||||||
|
`PlanResourceChange` will be called _again_ during the apply phase with any
|
||||||
|
unknown values from configuration filled in with their final results from
|
||||||
|
upstream resources. The second planned new state is compared with the first
|
||||||
|
and must meet the following additional constraints along with those listed
|
||||||
|
above:
|
||||||
|
|
||||||
|
* Any attribute that had a known value in the first planned new state must
|
||||||
|
have an identical value in the second.
|
||||||
|
|
||||||
|
* Any attribute that had an unknown value in the first planned new state may
|
||||||
|
either remain unknown in the second or take on any known value of the
|
||||||
|
expected type.
|
||||||
|
|
||||||
|
It is the second planned new state that is finally provided to
|
||||||
|
`ApplyResourceChange`, as described in the following section.
|
||||||
|
|
||||||
|
## ApplyResourceChange
|
||||||
|
|
||||||
|
The `ApplyResourceChange` function is responsible for making calls into the
|
||||||
|
remote system to make remote objects match the planned new state. During that
|
||||||
|
operation, it should determine final values for any attributes that were left
|
||||||
|
unknown in the planned new state, thus producing a wholly-known _new state_
|
||||||
|
object.
|
||||||
|
|
||||||
|
`ApplyResourceChange` also recieves the prior state so that it can use it
|
||||||
|
to potentially implement more "surgical" changes to particular parts of
|
||||||
|
the remote objects by detecting portions that are unchanged, in cases where the
|
||||||
|
remote API supports partial-update operations.
|
||||||
|
|
||||||
|
The new state object returned from the provider must meet the following
|
||||||
|
constraints:
|
||||||
|
|
||||||
|
* Any attribute that had a known value in the planned new state must have an
|
||||||
|
identical value in the new state.
|
||||||
|
|
||||||
|
* Any attribute that had an unknown value in the planned new state must take
|
||||||
|
on a known value of the expected type in the new state. No unknown values
|
||||||
|
are allowed in the new state.
|
Loading…
Reference in New Issue