docs/architecture/deployment-execution/resource-registration.md
(resource-registration)=
As a Pulumi program is executed by the language host, it will make calls to the engine in order to declare resources and their desired state. Each contains:
In order to determine what actions to take in order to arrive at the desired, the engine diffs the desired state of the resource against the current state as recorded in the state snapshot:
When the appropriate actions have been determined, the engine will invoke the relevant provider methods to carry them out. After the actions complete, the engine returns the new state of the resource to the program.
Although all of the above happens "in the engine", in practice these concerns are separated into a number of subsystems: the resource monitor, the step generator, and the step executor.
(resource-monitor)=
The resource monitor (largely represented by resmon in the codebase; see
gh-file:pulumi#pkg/resource/deploy/source_eval.go) implements the
interface, which is the primary communication
channel between language hosts and the engine. There is a single resource
monitor per deployment. Aside from being a marshalling and unmarshalling layer
between the engine and its gRPC boundary, the resource monitor is also
responsible for resolving default providers and component
providers, responding to
s as follows:
provider reference, the resource monitor will resolve
a default provider for the resource's package and
version.RegisterResourceEvent and await a response.RegisterResourceEvent),
the resource monitor will marshal the result back into the gRPC wire format
and return it to the language host.(step-generation)=
The step generator (gh-file:pulumi#pkg/resource/deploy/step_generator.go)
converts RegisterResourceEvents and ReadResourceEvents from the Pulumi program
into executable steps. Steps are the internal representation of
everything that needs to happen to fulfill the goals of that program. The steps
are passed to the step executor, which will do the actual work encoded in the
steps.
This is a fire and forget process. Once a step has been generated
the step generator immediately moves on to the next RegisterResourceEvent. It
is the responsibility of the step executor to communicate the
results of each step back to the resource monitor.
flowchart TD
Start[RegisterResourceEvent] --> GenURN[Generate URN]
GenURN --> LookupOld[Lookup existing state
Check aliases]
LookupOld --> Import{Import?}
Import -->|Yes| ImportStep[Generate ImportStep]
Import -->|No| Check[Call Provider.Check]
Check --> Analyzers[Run Analyzers]
Analyzers --> HasOld{Existing
state?}
HasOld -->|No| CreateStep[Generate CreateStep]
HasOld -->|Yes| Diff[Compute Diff]
Diff --> DiffResult{Changes?}
DiffResult -->|None| SameStep[Generate SameStep]
DiffResult -->|Update| UpdateStep[Generate UpdateStep]
DiffResult -->|Replace| ReplaceFlow[Replacement Flow]
ReplaceFlow --> ReplaceType{Replace
Type?}
ReplaceType -->|Create-Before| CBR[CreateStep +
ReplaceStep +
DeleteStep]
ReplaceType -->|Delete-Before| DBR[Calculate Dependents
DeleteStep +
CreateStep]
CreateStep --> Submit[Submit to Executor]
SameStep --> Submit
UpdateStep --> Submit
ImportStep --> Submit
CBR --> Submit
DBR --> Submit
When a RegisterResourceEvent is received:
ImportStep and returnCheck to validate and normalize inputsCreateStepSameStepUpdateStep:::{note} Presently, step generation is a serial process (that is, steps are processed one at a time, in turn). This means that step generation is on the critical path for a deployment, so any significant blocking operations could slow down deployments considerably. In the case of an update, step generator latency is generally insignificant compared to the time spend performing provider operations (e.g. cloud updates), but in the case of a large preview operation, or an update where most resources are unchanged, the step generator could become a bottleneck.
There are some exceptions to this, e.g. with diffs (see below), but largely operations that happen in the step generator should be fast, to not slow the whole deployment down. :::
While RegisterResourceEvents represent resources managed by Pulumi,
ReadResourceEvents fetch existing resources as external references. These
resources are marked External = true, meaning Pulumi tracks them but doesn't
own their lifecycle.
flowchart TD
Start[ReadResourceEvent] --> GenURN[Generate URN]
GenURN --> CreateState[Create new state
marked External]
CreateState --> HasOld{Existing
resource?}
HasOld -->|No| ReadStep[Generate ReadStep]
HasOld -->|Yes| CheckExternal{Old is
External?}
CheckExternal -->|Yes| ReadStep
CheckExternal -->|No| CheckID{IDs
match?}
CheckID -->|Yes| Relinquish[Relinquish:
Release from management]
CheckID -->|No| ReadReplace[Read Replacement:
Delete old, read new]
Relinquish --> ReadStep
ReadReplace --> ReadReplaceStep[Generate ReadReplacementStep
+ ReplaceStep]
ReadStep --> Submit[Submit to Executor]
ReadReplaceStep --> Submit
External = true with provided IDReadStep to fetch stateReadStep (updating reference)ReadStepReadReplacementStep to delete old managed resource and ReplaceStep
marker(step-generation-diff)=
While in most cases diffing boils down to calling a provider's method, there are a number of cases where this might not happen. The full algorithm that the engine currently implements is as follows:
--target-replace command-line option), the resource must be
replaced.To avoid blocking step generation, diffs can be computed in parallel using the
step executor's worker pool (note that this is disabled by default as of the time
of this writing, and can be enabled with the PULUMI_PARALLEL_DIFF=true env var):
sequenceDiagram
participant SG as Step Generator
participant SE as Step Executor
participant P as Provider
SG->>SE: Submit DiffStep
Note over SG: Continues to next resource
SE->>P: Call Diff
P-->>SE: DiffResult
SE->>SG: Fire ContinueResourceDiffEvent
SG->>SG: Generate steps from diff
The DiffStep leverages parallel workers while the step generator continues
processing other resources. When complete, a ContinueResourceDiffEvent re-enters
step generation to produce the final steps.
(step-generation-deletions)=
After the program exits, resources not registered in the new state are deleted:
flowchart LR
A[Old State Resources] --> B[Subtract Registered
Resources]
B --> C[Resources to Delete]
C --> D[Topological Sort
Reverse Dependencies]
D --> E[Decompose into
Parallel Groups]
E --> F[Submit Delete Steps]
style C fill:#f99
To determine which deletions can safely run in parallel, the step generator treats the resource dependency graph as a partially ordered set and decomposes it into antichains — subsets of resources that do not depend on one another and can therefore be deleted concurrently. The algorithm is:
Because dependent resources must be deleted before the resources they depend on, the resulting list of batches is reversed before being submitted to the step executor. Each batch is executed to completion before the next begins.
Pulumi supports two different type of replaces, either creating the new resource before deleting the old one (default), or deleting the old resource first, and then replacing it.
Delete-before-replace (DBR) is triggered when:
deleteBeforeReplace in deleteBeforeReplace resource optionflowchart TD
Replace[Resource Requires
Replacement] --> CheckType{Delete
Before?}
CheckType -->|No| CBR[Create-Before-Replace]
CheckType -->|Yes| DBR[Delete-Before-Replace]
CBR --> CBRCheck[Check inputs
without old defaults]
CBRCheck --> CBRCreate[Generate CreateStep
pendingDelete=true]
CBRCreate --> CBRReplace[Generate ReplaceStep]
CBRReplace --> CBRDelete[Generate DeleteStep]
DBR --> DBRCalc[Calculate dependent
replacements]
DBRCalc --> DBRDelete[Generate DeleteStep]
DBRDelete --> DBRCheck[Check inputs
without old defaults]
DBRCheck --> DBRCreate[Generate CreateStep]
CBRDelete --> End[Return Steps]
DBRCreate --> End
For DBR replacements, a second Check call is made without old inputs to ensure
new auto-generated properties (like names) don't reuse old values.1
In such cases, it may be necessary to first delete resources that depend on that being replaced, since there will be a moment between the delete and create steps where no version of the resource exists (and thus dependent resources will have broken dependencies). The step generator does this as follows:
To better illustrate this, consider the following example (written in pseudo-TypeScript):
const a = new Resource("a", {})
const b = new Resource("b", {}, { dependsOn: a })
const c = new Resource("c", { input: a.output })
const d = new Resource("d", { input: b.output })
The dependency graph for this program is as follows:
flowchart TD
b --> a
c --> a
d --> b
We see that the transitive set of resources that depend on a is {b, c, d}.
In the event that a is subject to a delete-before-replace, then each of b,
c, and d must also be considered. Since b's relationship is only due to
dependsOn, its
inputs will not be affected by the deletion of a, so it does not need to be
replaced. c's inputs are affected by the deletion of a, so we must call
Diff to see whether it needs to be replaced or not. d's dependency on a is
through b, which we have established does not need to be replaced, so d does
not need to be replaced either.
flowchart TD
Start[Resource A needs DBR] --> Find[Find all transitive
dependents]
Find --> Filter[Filter: Keep only
property dependents]
Filter --> TestDiff[For each dependent:
Substitute unknowns
for A's outputs]
TestDiff --> CallDiff[Call Provider.Diff]
CallDiff --> NeedsReplace{Needs
replace?}
NeedsReplace -->|Yes| AddToList[Add to replacement list]
NeedsReplace -->|No| Skip[Skip this dependent]
AddToList --> Next{More
dependents?}
Skip --> Next
Next -->|Yes| TestDiff
Next -->|No| Sort[Sort by reverse
topological order]
Sort --> Execute[Execute replacements]
Process:
dependsOn)Diff to determine if replacement needed(step-execution)=
The step executor is responsible for executing steps yielded by the step generator. Steps are processed in sequences called chains. While the steps within a chain must be processed serially, chains may be processed in parallel. The step executor uses a pool of workers to execute steps. Once a step completes, the executor communicates its results to the resource monitor. If a step fails, the executor notes its failure and cancels the deployment. Once the Pulumi program has exited and the step generator has issued all required deletions, the step executor waits for all outstanding steps to complete and then returns.
(resource-registration-examples)=
The following subsections give some example sequence diagrams for the processes described in this document. Use your mouse to zoom in/out and move around as necessary.
:zoom:
sequenceDiagram
participant LH as Language host
box Engine
participant RM as Resource monitor
participant SG as Step generator
participant SE as Step executor
end
participant P as Provider
LH->>+RM: RegisterResourceRequest(type, name, inputs, options)
RM->>+SG: RegisterResourceEvent(type, name, inputs, options)
SG->>+P: CheckRequest(type, inputs)
P->>-SG: CheckResponse(inputs', failures)
SG->>+SE: CreateStep(inputs', options)
SE->>+P: CreateRequest(type, inputs')
P->>-SE: CreateResponse(new state)
SE->>-RM: Done(new state)
RM->>-LH: RegisterResourceResponse(URN, ID, new state)
:zoom:
sequenceDiagram
participant LH as Language host
box Engine
participant RM as Resource monitor
participant SG as Step generator
participant SE as Step executor
end
participant P as Provider
LH->>+RM: RegisterResourceRequest(type, name, inputs, options)
RM->>+SG: RegisterResourceEvent(type, name, inputs, options)
SG->>+P: CheckRequest(type, inputs, old inputs)
P->>-SG: CheckResponse(inputs', failures)
SG->>+P: DiffRequest(type, inputs', old state, options)
P->>-SG: DiffResponse(diff)
SG->>+SE: UpdateStep(inputs', old state, options)
SE->>+P: UpdateRequest(type, inputs', old state)
P->>-SE: UpdateResponse(new state)
SE->>-RM: Done(new state)
RM->>-LH: RegisterResourceResponse(URN, ID, new state)
:zoom:
sequenceDiagram
participant LH as Language host
box Engine
participant RM as Resource monitor
participant SG as Step generator
participant SE as Step executor
end
participant P as Provider
LH->>+RM: RegisterResourceRequest(type, name, inputs, options)
RM->>+SG: RegisterResourceEvent(type, name, inputs, options)
SG->>+P: CheckRequest(type, inputs, old inputs)
P->>-SG: CheckResponse(inputs', failures)
SG->>+P: DiffRequest(type, inputs', old state, options)
P->>-SG: DiffResponse(diff)
SG->>+SE: CreateStep(inputs', old state, options)
SE->>+P: CreateRequest(type, inputs', old state)
P->>-SE: CreateResponse(new state)
SE->>-RM: Done(new state)
RM->>-LH: RegisterResourceResponse(URN, ID, new state)
Note over SG: Pulumi program exits
SG->>SG: Generate delete steps
SG->>+SE: DeleteStep(old state)
SE->>+P: DeleteRequest(type, old state)
P->>-SE: DeleteResponse()
:zoom:
sequenceDiagram
participant LH as Language host
box Engine
participant RM as Resource monitor
participant SG as Step generator
participant SE as Step executor
end
participant P as Provider
LH->>+RM: RegisterResourceRequest(type, name, inputs, options)
RM->>+SG: RegisterResourceEvent(type, name, inputs, options)
SG->>+P: CheckRequest(type, inputs, old inputs)
P->>-SG: CheckResponse(inputs', failures)
SG->>+P: DiffRequest(type, inputs', old state, options)
P->>-SG: DiffResponse(diff)
SG->>+SE: DeleteStep(old state), CreateStep(inputs', options)
SE->>+P: DeleteRequest(type, old state)
P->>-SE: DeleteResponse()
SE->>+P: CreateRequest(type, inputs', old state)
P->>-SE: CreateResponse(new state)
SE->>-RM: Done(new state)
RM->>-LH: RegisterResourceResponse(URN, ID, new state)
:zoom:
sequenceDiagram
participant LH as Language host
box Engine
participant RM as Resource monitor
participant SG as Step generator
participant SE as Step executor
end
participant P as Provider
LH->>+RM: RegisterResourceRequest(type, name, inputs, options)
RM->>+SG: RegisterResourceEvent(type, name, inputs, options)
SG->>+SE: ImportStep(inputs, options)
SE->>+P: ReadRequest(type, id)
P->>-SE: ReadResponse(current inputs, current state)
SE->>+P: CheckRequest(type, inputs, current inputs)
P->>-SE: CheckResponse(inputs', failures)
SE->>+P: DiffRequest(type, inputs', current state, options)
P->>-SE: DiffResponse(diff)
SE->>-RM: Done(current state)
RM->>-LH: RegisterResourceResponse(URN, ID, current state)
:zoom:
sequenceDiagram
participant LH as Language host
box Engine
participant RM as Resource monitor
participant SG as Step generator
participant SE as Step executor
end
participant P as Provider
LH->>+RM: RegisterResourceRequest(type, name, inputs, options)
RM->>+SG: RegisterResourceEvent(type, name, inputs, options)
SG->>+P: CheckRequest(type, inputs, old inputs)
P->>-SG: CheckResponse(inputs', failures)
SG->>+P: DiffRequest(type, inputs', old state, options)
P->>-SG: DiffResponse(diff)
SG->>+SE: SameStep(inputs', old state, options)
SE->>-RM: Done(old state)
RM->>-LH: RegisterResourceResponse(URN, ID, old state)
:zoom:
sequenceDiagram
participant LH as Language host
box Engine
participant RM as Resource monitor
participant SG as Step generator
participant SE as Step executor
end
participant P as Provider
LH->>+RM: ReadResourceRequest(type, name, id, parent)
RM->>+SG: ReadResourceEvent(type, name, id, parent)
SG->>SG: Generate URN
SG->>SG: Create state marked External=true
SG->>+SE: ReadStep(id, provider)
SE->>+P: ReadRequest(type, id)
P->>-SE: ReadResponse(current state)
SE->>-RM: Done(external state)
RM->>-LH: ReadResourceResponse(URN, ID, state)
Existing inputs may be used to repopulate default values for input properties that are automatically generated when the resource is created but that are not changed on subsequent updates (e.g. automatically generated names). For replacements, we don't want to reuse these values. ↩