github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/docs/resource-instance-change-lifecycle.md (about)

     1  # Durgaform Resource Instance Change Lifecycle
     2  
     3  This document describes the relationships between the different operations
     4  called on a Durgaform Provider to handle a change to a resource instance.
     5  
     6  ![](https://user-images.githubusercontent.com/20180/172506401-777597dc-3e6e-411d-9580-b192fd34adba.png)
     7  
     8  The resource instance operations all both consume and produce objects that
     9  conform to the schema of the selected resource type.
    10  
    11  The overall goal of this process is to take a **Configuration** and a
    12  **Previous Run State**, merge them together using resource-type-specific
    13  planning logic to produce a **Planned State**, and then change the remote
    14  system to match that planned state before finally producing the **New State**
    15  that will be saved in order to become the **Previous Run State** for the next
    16  operation.
    17  
    18  The various object values used in different parts of this process are:
    19  
    20  * **Configuration**: Represents the values the user wrote in the configuration,
    21    after any automatic type conversions to match the resource type schema.
    22  
    23      Any attributes not defined by the user appear as null in the configuration
    24      object. If an argument value is derived from an unknown result of another
    25      resource instance, its value in the configuration object could also be
    26      unknown.
    27  
    28  * **Prior State**: The provider's representation of the current state of the
    29    remote object at the time of the most recent read.
    30  
    31  * **Proposed New State**: Durgaform Core uses some built-in logic to perform
    32    an initial basic merger of the **Configuration** and the **Prior State**
    33    which a provider may use as a starting point for its planning operation.
    34  
    35      The built-in logic primarily deals with the expected behavior for attributes
    36      marked in the schema as both "optional" _and_ "computed", which means that
    37      the user may either set it or may leave it unset to allow the provider
    38      to choose a value instead.
    39  
    40      Durgaform Core therefore constructs the proposed new state by taking the
    41      attribute value from Configuration if it is non-null, and then using the
    42      Prior State as a fallback otherwise, thereby helping a provider to
    43      preserve its previously-chosen value for the attribute where appropriate.
    44  
    45  * **Initial Planned State** and **Final Planned State** are both descriptions
    46    of what the associated remote object ought to look like after completing
    47    the planned action.
    48  
    49      There will often be parts of the object that the provider isn't yet able to
    50      predict, either because they will be decided by the remote system during
    51      the apply step or because they are derived from configuration values from
    52      other resource instances that are themselves not yet known. The provider
    53      must mark these by including unknown values in the state objects.
    54  
    55      The distinction between the _Initial_ and _Final_ planned states is that
    56      the initial one is created during Durgaform Core's planning phase based
    57      on a possibly-incomplete configuration, whereas the final one is created
    58      during the apply step once all of the dependencies have already been
    59      updated and so the configuration should then be wholly known.
    60  
    61  * **New State** is a representation of the result of whatever modifications
    62    were made to the remote system by the provider during the apply step.
    63  
    64      The new state must always be wholly known, because it represents the
    65      actual state of the system, rather than a hypothetical future state.
    66  
    67  * **Previous Run State** is the same object as the **New State** from
    68    the previous run of Durgaform. This is exactly what the provider most
    69    recently returned, and so it will not take into account any changes that
    70    may have been made outside of Durgaform in the meantime, and it may conform
    71    to an earlier version of the resource type schema and therefore be
    72    incompatible with the _current_ schema.
    73  
    74  * **Upgraded State** is derived from **Previous Run State** by using some
    75    provider-specified logic to upgrade the existing data to the latest schema.
    76    However, it still represents the remote system as it was at the end of the
    77    last run, and so still doesn't take into account any changes that may have
    78    been made outside of Durgaform.
    79  
    80  * The **Import ID** and **Import Stub State** are both details of the special
    81    process of importing pre-existing objects into a Durgaform state, and so
    82    we'll wait to discuss those in a later section on importing.
    83  
    84  
    85  ## Provider Protocol API Functions
    86  
    87  The following sections describe the three provider API functions that are
    88  called to plan and apply a change, including the expectations Durgaform Core
    89  enforces for each.
    90  
    91  For historical reasons, the original Durgaform SDK is exempt from error
    92  messages produced when certain assumptions are violated, but violating them
    93  will often cause downstream errors nonetheless, because Durgaform's workflow
    94  depends on these contracts being met.
    95  
    96  The following section uses the word "attribute" to refer to the named
    97  attributes described in the resource type schema. A schema may also include
    98  nested blocks, which contain their _own_ set of attributes; the constraints
    99  apply recursively to these nested attributes too.
   100  
   101  The following are the function names used in provider protocol version 6.
   102  Protocol version 5 has the same set of operations but uses some
   103  marginally-different names for them, because we used protocol version 6 as an
   104  opportunity to tidy up some names that had been awkward before.
   105  
   106  ### ValidateResourceConfig
   107  
   108  `ValidateResourceConfig` takes the **Configuration** object alone, and
   109  may return error or warning diagnostics in response to its attribute values.
   110  
   111  `ValidateResourceConfig` is the provider's opportunity to apply custom
   112  validation rules to the schema, allowing for constraints that could not be
   113  expressed via schema alone.
   114  
   115  In principle a provider can make any rule it wants here, although in practice
   116  providers should typically avoid reporting errors for values that are unknown.
   117  Durgaform Core will call this function multiple times at different phases
   118  of evaluation, and guarantees to _eventually_ call with a wholly-known
   119  configuration so that the provider will have an opportunity to belatedly catch
   120  problems related to values that are initially unknown during planning.
   121  
   122  If a provider intends to choose a default value for a particular
   123  optional+computed attribute when left as null in the configuration, the
   124  provider _must_ tolerate that attribute being unknown in the configuration in
   125  order to get an opportunity to choose the default value during the later
   126  plan or apply phase.
   127  
   128  The validation step does not produce a new object itself and so it cannot
   129  modify the user's supplied configuration.
   130  
   131  ### PlanResourceChange
   132  
   133  The purpose of `PlanResourceChange` is to predict the approximate effect of
   134  a subsequent apply operation, allowing Durgaform to render the plan for the
   135  user and to propagate the predictable subset of results downstream through
   136  expressions in the configuration.
   137  
   138  This operation can base its decision on any combination of **Configuration**,
   139  **Prior State**, and **Proposed New State**, as long as its result fits the
   140  following constraints:
   141  
   142  * Any attribute that was non-null in the configuration must either preserve
   143    the exact configuration value or return the corresponding attribute value
   144    from the prior state. (Do the latter if you determine that the change is not
   145    functionally significant, such as if the value is a JSON string that has
   146    changed only in the positioning of whitespace.)
   147  
   148  * Any attribute that is marked as computed in the schema _and_ is null in the
   149    configuration may be set by the provider to any arbitrary value of the
   150    expected type.
   151  
   152  * If a computed attribute has any _known_ value in the planned new state, the
   153    provider will be required to ensure that it is unchanged in the new state
   154    returned by `ApplyResourceChange`, or return an error explaining why it
   155    changed. Set an attribute to an unknown value to indicate that its final
   156    result will be determined during `ApplyResourceChange`.
   157  
   158  `PlanResourceChange` is actually called twice per run for each resource type.
   159  
   160  The first call is during the planning phase, before Durgaform prints out a
   161  diff to the user for confirmation. Because no changes at all have been applied
   162  at that point, the given **Configuration** may contain unknown values as
   163  placeholders for the results of expressions that derive from unknown values
   164  of other resource instances. The result of this initial call is the
   165  **Initial Planned State**.
   166  
   167  If the user accepts the plan, Durgaform will call `PlanResourceChange` a
   168  second time during the apply step, and that call is guaranteed to have a
   169  wholly-known **Configuration** with any values from upstream dependencies
   170  taken into account already. The result of this second call is the
   171  **Final Planned State**.
   172  
   173  Durgaform Core compares the final with the initial planned state, enforcing
   174  the following additional constraints along with those listed above:
   175  
   176  * Any attribute that had a known value in the **Initial Planned State** must
   177    have an identical value in the **Final Planned State**.
   178  
   179  * Any attribute that had an unknown value in the **Initial Planned State** may
   180    either remain unknown in the second _or_ take on any known value that
   181    conforms to the unknown value's type constraint.
   182  
   183  The **Final Planned State** is what passes to `ApplyResourceChange`, as
   184  described in the following section.
   185  
   186  ### ApplyResourceChange
   187  
   188  The `ApplyResourceChange` function is responsible for making calls into the
   189  remote system to make remote objects match the **Final Planned State**. During
   190  that operation, the provider should decide on final values for any attributes
   191  that were left unknown in the **Final Planned State**, and thus produce the
   192  **New State** object.
   193  
   194  `ApplyResourceChange` also receives the **Prior State** so that it can use it
   195  to potentially implement more "surgical" changes to particular parts of
   196  the remote objects by detecting portions that are unchanged, in cases where the
   197  remote API supports partial-update operations.
   198  
   199  The **New State** object returned from the provider must meet the following
   200  constraints:
   201  
   202  * Any attribute that had a known value in the **Final Planned State** must have
   203    an identical value in the new state. In particular, if the remote API
   204    returned a different serialization of the same value then the provider must
   205    preserve the form the user wrote in the configuration, and _must not_ return
   206    the normalized form produced by the provider.
   207  
   208  * Any attribute that had an unknown value in the **Final Planned State** must
   209    take on a known value whose type conforms to the type constraint of the
   210    unknown value. No unknown values are permitted in the **New State**.
   211  
   212  After calling `ApplyResourceChange` for each resource instance in the plan,
   213  and dealing with any other bookkeeping to return the results to the user,
   214  a single Durgaform run is complete. Terraform Core saves the **New State**
   215  in a state snapshot for the entire configuration, so it'll be preserved for
   216  use on the next run.
   217  
   218  When the user subsequently runs Durgaform again, the **New State** becomes
   219  the **Previous Run State** verbatim, and passes into `UpgradeResourceState`.
   220  
   221  ### UpgradeResourceState
   222  
   223  Because the state values for a particular resource instance persist in a
   224  saved state snapshot from one run to the next, Durgaform Core must deal with
   225  the possibility that the user has upgraded to a newer version of the provider
   226  since the last run, and that the new provider version has an incompatible
   227  schema for the relevant resource type.
   228  
   229  Durgaform Core therefore begins by calling `UpgradeResourceState` and passing
   230  the **Previous Run State** in a _raw_ form, which in current protocol versions
   231  is the raw JSON data structure as was stored in the state snapshot. Durgaform
   232  Core doesn't have access to the previous schema versions for a provider's
   233  resource types, so the provider itself must handle the data decoding in this
   234  upgrade function.
   235  
   236  The provider can then use whatever logic is appropriate to update the shape
   237  of the data to conform to the current schema for the resource type. Although
   238  Durgaform Core has no way to enforce it, a provider should only change the
   239  shape of the data structure and should _not_ change the meaning of the data.
   240  In particular, it should not try to update the state data to capture any
   241  changes made to the corresponding remote object outside of Durgaform.
   242  
   243  This function then returns the **Upgraded State**, which captures the same
   244  information as the **Previous Run State** but does so in a way that conforms
   245  to the current version of the resource type schema, which therefore allows
   246  Durgaform Core to interact with the data fully for subsequent steps.
   247  
   248  ### ReadResource
   249  
   250  Although Durgaform typically expects to have exclusive control over any remote
   251  object that is bound to a resource instance, in practice users may make changes
   252  to those objects outside of Durgaform, causing Terraform's records of the
   253  object to become stale.
   254  
   255  The `ReadResource` function asks the provider to make a best effort to detect
   256  any such external changes and describe them so that Durgaform Core can use
   257  an up-to-date **Prior State** as the input to the next `PlanResourceChange`
   258  call.
   259  
   260  This is always a best effort operation because there are various reasons why
   261  a provider might not be able to detect certain changes. For example:
   262  * Some remote objects have write-only attributes, which means that there is
   263    no way to determine what value is currently stored in the remote system.
   264  * There may be new features of the underlying API which the current provider
   265    version doesn't know how to ask about.
   266  
   267  Durgaform Core expects a provider to carefully distinguish between the
   268  following two situations for each attribute:
   269  * **Normalization**: the remote API has returned some data in a different form
   270    than was recorded in the **Previous Run State**, but the meaning is unchanged.
   271  
   272      In this case, the provider should return the exact value from the
   273      **Previous Run State**, thereby preserving the value as it was written by
   274      the user in the configuration and thus avoiding unwanted cascading changes to
   275      elsewhere in the configuration.
   276  * **Drift**: the remote API returned data that is materially different from
   277    what was recorded in the **Previous Run State**, meaning that the remote
   278    system's behavior no longer matches what the configuration previously
   279    requested.
   280  
   281      In this case, the provider should return the value from the remote system,
   282      thereby discarding the value from the **Previous Run State**. When a
   283      provider does this, Durgaform _may_ report it to the user as a change
   284      made outside of Durgaform, if Terraform Core determined that the detected
   285      change was a possible cause of another planned action for a downstream
   286      resource instance.
   287  
   288  This operation returns the **Prior State** to use for the next call to
   289  `PlanResourceChange`, thus completing the circle and beginning this process
   290  over again.
   291  
   292  ## Handling of Nested Blocks in Configuration
   293  
   294  Nested blocks are a configuration-only construct and so the number of blocks
   295  cannot be changed on the fly during planning or during apply: each block
   296  represented in the configuration must have a corresponding nested object in
   297  the planned new state and new state, or Durgaform Core will raise an error.
   298  
   299  If a provider wishes to report about new instances of the sub-object type
   300  represented by nested blocks that are created implicitly during the apply
   301  operation -- for example, if a compute instance gets a default network
   302  interface created when none are explicitly specified -- this must be done via
   303  separate "computed" attributes alongside the nested blocks. This could be list
   304  or map of objects that includes a mixture of the objects described by the
   305  nested blocks in the configuration and any additional objects created implicitly
   306  by the remote system.
   307  
   308  Provider protocol version 6 introduced the new idea of structural-typed
   309  attributes, which are a hybrid of attribute-style syntax but nested-block-style
   310  interpretation. For providers that use structural-typed attributes, they must
   311  follow the same rules as for a nested block type of the same nesting mode.
   312  
   313  ## Import Behavior
   314  
   315  The main resource instance change lifecycle is concerned with objects whose
   316  entire lifecycle is driven through Durgaform, including the initial creation
   317  of the object.
   318  
   319  As an aid to those who are adopting Durgaform as a replacement for existing
   320  processes or software, Durgaform also supports adopting pre-existing objects
   321  to bring them under Durgaform's management without needing to recreate them
   322  first.
   323  
   324  When using this facility, the user provides the address of the resource
   325  instance they wish to bind the existing object to, and a string representation
   326  of the identifier of the existing object to be imported in a syntax defined
   327  by the provider on a per-resource-type basis, which we'll call the
   328  **Import ID**.
   329  
   330  The import process trades the user's **Import ID** for a special
   331  **Import Stub State**, which behaves as a placeholder for the
   332  **Previous Run State** pretending as if a previous Durgaform run is what had
   333  created the object.
   334  
   335  ### ImportResourceState
   336  
   337  The `ImportResourceState` operation takes the user's given **Import ID** and
   338  uses it to verify that the given object exists and, if so, to retrieve enough
   339  data about it to produce the **Import Stub State**.
   340  
   341  Durgaform Core will always pass the returned **Import Stub State** to the
   342  normal `ReadResource` operation after `ImportResourceState` returns it, so
   343  in practice the provider may populate only the minimal subset of attributes
   344  that `ReadResource` will need to do its work, letting the normal function
   345  deal with populating the rest of the data to match what is currently set in
   346  the remote system.
   347  
   348  For the same reasons that `ReadResource` is only a _best effort_ at detecting
   349  changes outside of Durgaform, a provider may not be able to fully support
   350  importing for all resource types. In that case, the provider developer must
   351  choose between the following options:
   352  
   353  * Perform only a partial import: the provider may choose to leave certain
   354    attributes set to `null` in the **Prior State** after both
   355    `ImportResourceState` and the subsequent `ReadResource` have completed.
   356  
   357      In this case, the user can provide the missing value in the configuration
   358      and thus cause the next `PlanResourceChange` to plan to update that value
   359      to match the configuration. The provider's `PlanResourceChange` function
   360      must be ready to deal with the attribute being `null` in the
   361      **Prior State** and handle that appropriately.
   362  * Return an error explaining why importing isn't possible.
   363  
   364      This is a last resort because of course it will then leave the user unable
   365      to bring the existing object under Durgaform's management. However, if a
   366      particular object's design doesn't suit importing then it can be a better
   367      user experience to be clear and honest that the user must replace the object
   368      as part of adopting Durgaform, rather than to perform an import that will
   369      leave the object in a situation where Durgaform cannot meaningfully manage
   370      it.