Skip to content

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

world.wit
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>;
    }
}