Wasm
Please Read First
Before proceeding, make sure to read Pulumi Gestalt integrations Overview page to get a better understanding of the documentation.
In Pulumi Gestalt, Wasm support is based on the Component Model.
Artifacts
Artifacts related to Wasm support can be found on the releases page:
- Runner (
pulumi_gestalt_wasm_runner) - WIT files (
world.wit)
Versioning
Runner and WIT files are versioned after the whole project without any forward or backward compatibility. This means that if you want to use a specific version of the Runner, you need to use WIT files from the same version.
Entrypoint
The entry point is managed by the component:pulumi-gestal/pulumi-main#pulumi-main interface. This function is invoked by
pulumi-gestalt-runner.
Callback Emulation
In Pulumi, one of the key features is the ability to invoke arbitrary code for Output mapping. Typically, an Output
would be implemented as a Future<T> abstraction with a map method. However, this introduces a circular dependency:
user code needs Future<T>.map, but Future<T>.map needs user code. While this is not a problem in most languages, it
is an issue in the WebAssembly (Wasm) Component Model due to its lack of support for circular dependencies.
To address this, Pulumi Gestalt uses a callback emulation mechanism where user code must manually retrieve a list of
required callbacks, invoke them, and return the results. This is done using the finish function, which has the
following signature:
interface types {
record function-invocation-request {
id: output,
function-name: string,
value: string,
}
record function-invocation-result {
id: borrow<output>,
value: string,
}
}
interface output-interface {
resource output {
map: func(function-name: string) -> output;
}
}
interface context {
resource context {
finish: func(functions: list<function-invocation-result>) -> list<function-invocation-request>;
}
}
The algorithm is as follows:
sequenceDiagram
User -> User: Assign name to function
User -> User: Add name and function to global map
User -> Pulumi_Gestalt_Component: Map given output with function name
User -> User: Run rest of the program
User -> User: Assign empty list to `results`
loop forever
User -> Pulumi_Gestalt_Component: Invoke `finish` with `results`
Note right of Pulumi_Gestalt_Component: Returns list of `function-invocation-request`
User -> User: If list is empty, exit loop
loop Compute results
User -> User: For each request in list
User -> User: Look up function by `function-id`
User -> User: Invoke function with `value`
User -> User: Create `function-invocation-result`
User -> User: Add result to `results`
end
end
You can check implementation of this mechanism inside Pulumi Gestalt itself:
- First implementation
- Second implementation (run_loop function)
Runner Quick Start
To execute a Pulumi Gestalt Wasm program using the Runner, use:
pulumi_wasm_runner run <WASM_FILE>
WIT Files
package component:pulumi-gestalt@0.0.3;
world pulumi-gestalt {
import output-interface;
import types;
import context;
export pulumi-main; // Used by macro
}
interface pulumi-main {
main: func();
}
interface output-interface {
resource output {
map: func(function-name: string) -> output;
clone: func() -> output;
combine: func(outputs: list<borrow<output>>) -> output;
add-to-export: func(name: string);
}
resource composite-output {
get-field: func(field-name: string) -> output;
}
}
interface types {
use output-interface.{output};
record function-invocation-request {
id: output,
function-name: string,
value: string,
}
record function-invocation-result {
id: borrow<output>,
value: string,
}
record object-field {
name: string,
value: borrow<output>
}
record register-resource-request {
%type: string,
name: string,
version: string,
object: list<object-field>,
}
record resource-invoke-request {
token: string,
version: string,
object: list<object-field>,
}
variant config-value {
plaintext(string),
secret(output),
}
}
interface context {
use output-interface.{output, composite-output};
use types.{register-resource-request, resource-invoke-request, function-invocation-result, function-invocation-request, config-value};
resource context {
constructor();
create-output: func(value: string, secret:bool) -> output;
register-resource: func(request: register-resource-request) -> composite-output;
invoke-resource: func(request: resource-invoke-request) -> composite-output;
finish: func(functions: list<function-invocation-result>) -> list<function-invocation-request>;
get-config: func(name: option<string>, key: string) -> option<config-value>;
}
}