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.