pulumi/pulumi
Resources and URNs
Active contributors: Justin Van Patten, Ian Wahbe, Thomas Gummerer
Purpose
Pulumi's universe consists of resources. Every cloud object — and every logical grouping — is a resource. Each resource has a URN (a stable identity), a type token (what kind of thing it is), an input map (what was asked for), an output map (what came back), and a parent / dependency graph linking it to others.
This page explains the canonical shape of a resource, the URN algebra, and the way the engine, the SDKs, and the snapshot all agree on this model.
The URN
sdk/go/common/resource/urn/urn.go defines:
urn:pulumi:<stack>::<project>::<parent-type>$<type>::<name>Concrete example:
urn:pulumi:dev::my-app::aws:s3/bucket:Bucket::logs
urn:pulumi:dev::my-app::custom:component:App$aws:s3/bucket:Bucket::logsComponents of the URN:
<stack>— stack name (dev,prod, etc.).<project>— project name fromPulumi.yaml.<parent-type>— concatenated chain of parent resource types (separated by$). Empty for root-level resources.<type>— the type token (e.g.aws:s3/bucket:Bucket).<name>— the user-supplied name.
URNs are deterministic given those inputs. Renaming a resource — or moving its parent — produces a new URN. To preserve identity across renames, declare an Alias (proto/pulumi/alias.proto).
Type tokens
sdk/go/common/tokens/ defines:
type Type = string // e.g. "aws:s3/bucket:Bucket"
type Package = string // "aws"
type ModuleMember = string // "aws:s3/bucket"Format: <package>:<module>:<TypeName>. Module is hierarchical (aws:s3/bucket).
Type tokens are interned by providers and cataloged in their schemas. The engine uses the package portion to pick the right provider.
Resource categories
| Category | Marker | What it is |
|---|---|---|
| Custom | Custom: true |
Backed by a real provider RPC (Create/Update/Delete). 99% of resources. |
| Component | Custom: false |
A logical grouping with no provider counterpart. Implemented by extending ComponentResource. |
| Provider | type starts with pulumi:providers: |
An instance of a provider plugin with specific config. Synthesized as a default if not declared. |
| Stack | type is pulumi:pulumi:Stack |
The root resource — every other resource is parented under it. |
| External | External: true |
Read-only — declared via Resource.Read(...). |
| Remote component | a component implemented in another language | Looks like a Custom resource on the wire (uses Construct/Call). |
Resource state
sdk/go/common/resource/resource_state.go defines:
type State struct {
URN resource.URN
Custom bool
Delete bool
ID resource.ID
Type tokens.Type
Inputs PropertyMap
Outputs PropertyMap
Parent resource.URN
Protect bool
External bool
Dependencies []resource.URN
InitErrors []string
Provider string // URN of provider as string
PropertyDependencies map[PropertyKey][]URN
PendingReplacement bool
AdditionalSecretOutputs []PropertyKey
Aliases []resource.URN
CustomTimeouts CustomTimeouts
ImportID resource.ID
RetainOnDelete bool
DeletedWith resource.URN
Created *time.Time
Modified *time.Time
SourcePosition string
}Every field has a wire counterpart in proto/pulumi/resource.proto. The snapshot is a list of States plus metadata.
PropertyMap and PropertyValue
Inputs and outputs aren't typed structs — they're PropertyMap, a map[string]PropertyValue. PropertyValue is a sum type covering:
- Primitives: bool, number, string.
- Containers: array, object, set.
- Special:
Asset,Archive,Secret,Computed(engine-only "unknown"),Output(placeholder),ResourceReference.
This shape lives in sdk/go/common/resource/properties.go. Every SDK marshals between language-native values and PropertyMap for the wire.
Parent / dependency graph
A resource's parent is set by the SDK when constructing the resource (component children parent under the component). Dependencies are explicit (dependsOn) or implicit (an Output value flowed in as an input).
The step generator uses dependencies to determine execution order. The step executor schedules N steps in parallel; cross-resource dependencies serialize execution.
Aliases
When you rename a resource, declare an Alias:
new aws.s3.Bucket("logs", { ... }, { aliases: [{ name: "old-logs" }] });The engine recognizes the old URN and emits a SameStep instead of a DeleteStep followed by a CreateStep (which would destroy and recreate the bucket).
URN scope
URNs are scoped by stack — same project, different stack means different URN. pulumi state move is the way to migrate URNs across stacks.
Custom timeouts and protect
Two oft-misunderstood fields:
Protect: true—pulumi destroyrefuses to delete this resource. To remove it:pulumi state unprotect <urn>then re-rundestroy.RetainOnDelete: true— the engine emits noDeleteto the provider. The resource leaves the stack but remains in the cloud. Useful for cross-stack ownership transitions.CustomTimeouts— per-op timeouts for Create / Update / Delete. The provider chooses how to honor them.
Source position
SourcePosition is the file and line where the resource was registered in the user's program. Recent versions of the engine plumb this through so error messages can point at the offending source line. Implemented by the language SDKs (each captures its own stack frame).
Entry points for modification
- Adding a new resource state field — add to
State, toproto/pulumi/resource.proto, regenerate, plumb through every SDK. - A new resource option — same path, but the option is in
RegisterResourceRequestrather than the persistedState(or both). - A new property type —
PropertyValueinsdk/go/common/resource/properties.go. Cross-language work to teach every SDK.
See also
- Inputs and Outputs
- Resource graph & step model
docs/architecture/resources/— the in-repo deep-dive.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.