diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..f40756d05 --- /dev/null +++ b/docs/README.md @@ -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. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 000000000..4ba19a554 --- /dev/null +++ b/docs/architecture.md @@ -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. + diff --git a/docs/images/architecture-overview.png b/docs/images/architecture-overview.png new file mode 100644 index 000000000..40f2a04fa Binary files /dev/null and b/docs/images/architecture-overview.png differ diff --git a/docs/images/resource-instance-change-lifecycle.png b/docs/images/resource-instance-change-lifecycle.png new file mode 100644 index 000000000..3872be78c Binary files /dev/null and b/docs/images/resource-instance-change-lifecycle.png differ diff --git a/docs/maintainer-etiquette.md b/docs/maintainer-etiquette.md index 30fd69a72..b6539c8cb 100644 --- a/docs/maintainer-etiquette.md +++ b/docs/maintainer-etiquette.md @@ -3,6 +3,10 @@ Are you a core maintainer of Terraform? Great! Here's a few notes 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 We value the time you spend on the project and as such your maintainer status diff --git a/docs/resource-instance-change-lifecycle.md b/docs/resource-instance-change-lifecycle.md new file mode 100644 index 000000000..dab4a516e --- /dev/null +++ b/docs/resource-instance-change-lifecycle.md @@ -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.