Initial example working
This commit is contained in:
parent
a41316d5b7
commit
e8242bca33
8
examples/multilang/README.md
Normal file
8
examples/multilang/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
TODO:
|
||||
- [ ] Serialize `opts` through to remote process
|
||||
- [ ] Support `Resource`s as outputs (both `CustomResource` and `ComponentResource`)
|
||||
- [ ] Support `Resource`s as inputs (both `CustomResource` and `ComponentResource`)
|
||||
- [ ] Real multi-lang: replace `remote.construct` with an engine invoke (or RPC) that spwans
|
||||
language host, loads the requested module, evals the requested constructor, returns back the URN.
|
||||
- [ ] Support client runtime in Python (and .NET and Go)
|
||||
- [ ] Generate proxies from schema
|
|
@ -5,6 +5,33 @@ interface RemoteMyComponentArgs {
|
|||
input1: pulumi.Input<number>;
|
||||
}
|
||||
|
||||
// This is a proxy for the `MyComponent` defined in `./mycomponent`. This wrapper could be
|
||||
// auto-generated from a schematization of that component. It only requires:
|
||||
//
|
||||
// 1. The filesystem path to the NodeJS module to load
|
||||
// 2. The (potentially qualified) global symbol to eval to get the instance of the component
|
||||
// constructor.
|
||||
// 3. The input properties to define the `Args` interface above.
|
||||
// 4. The output proeprties to define the public properties on this class.
|
||||
//
|
||||
// It works by remotely constructing the component in another NodeJS process (or in current
|
||||
// implementation, VM inside the same process), serializing input properties (resolving inputs and
|
||||
// promises) down to the standard Pulumi object model RPC, constructing the resources in the other
|
||||
// process (which is connected to the same Pulumi engine host interfaces), then serializing the
|
||||
// outputs of that component back through the Pulumi object model RPC into this process. This
|
||||
// remote invoke will only complete (asynchronously) once the remote component construction is
|
||||
// complete (i.e. the `registerOutputs` has been called and it's output proeprties have resolved to
|
||||
// concrete serializable values). The only return property we currently care about is the `urn`. We
|
||||
// can use this `urn` from within this process to go to the Pulumi engine and look up the output
|
||||
// properties of the already-registered resource to populate this proxy resource. This provides a
|
||||
// lot of consistency with our existing `CustomResource` wrappers for both registered and read
|
||||
// resources which populate proeprties based on outputs estabilshed by out-of-process RPCs.
|
||||
//
|
||||
// The end result is that constructing this component is observably "the same" as constucting the
|
||||
// real component in the other language/runtime, with the exception that:
|
||||
// 1. The properties of this resource are the registered outputs, not the host-language level
|
||||
// proeprties. This ensures that a more clearly defined serializable subset of all host
|
||||
// langauges can be exposed.
|
||||
class MyComponent extends pulumi.ComponentResource {
|
||||
public myid!: pulumi.Output<string>;
|
||||
public output1!: pulumi.Output<number>;
|
||||
|
@ -15,8 +42,9 @@ class MyComponent extends pulumi.ComponentResource {
|
|||
}
|
||||
}
|
||||
|
||||
const res2 = new MyComponent("n", {
|
||||
const res = new MyComponent("n", {
|
||||
input1: Promise.resolve(24),
|
||||
});
|
||||
|
||||
export const id2 = res2.myid;
|
||||
export const id2 = res.myid;
|
||||
export const output1 = res.output1;
|
||||
|
|
|
@ -789,22 +789,13 @@ export class ComponentResource<TData = any> extends Resource {
|
|||
* @param opts A bag of options that control this resource's behavior.
|
||||
*/
|
||||
constructor(type: string, name: string, args: Inputs = {}, opts: ComponentResourceOptions = {}) {
|
||||
// Explicitly ignore the props passed in. We allow them for back compat reasons. However,
|
||||
// we explicitly do not want to pass them along to the engine. The ComponentResource acts
|
||||
// only as a container for other resources. Another way to think about this is that a normal
|
||||
// 'custom resource' corresponds to real piece of cloud infrastructure. So, when it changes
|
||||
// in some way, the cloud resource needs to be updated (and vice versa). That is not true
|
||||
// for a component resource. The component is just used for organizational purposes and does
|
||||
// not correspond to a real piece of cloud infrastructure. As such, changes to it *itself*
|
||||
// do not have any effect on the cloud side of things at all.
|
||||
super(type, name, /*custom:*/ false, /*props:*/ {}, opts);
|
||||
super(type, name, /*custom:*/ false, args, opts);
|
||||
this.__data = this.initializeAndRegisterOutputs(args);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
private async initializeAndRegisterOutputs(args: Inputs) {
|
||||
const data = await this.initialize(args);
|
||||
this.registerOutputs();
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
|
@ -186,7 +186,6 @@ export function getResource(res: Resource, t: string, name: string, custom: bool
|
|||
log.debug(`Getting resource: id=${Output.isInstance(urn) ? "Output<T>" : urn}, t=${t}, name=${name}`);
|
||||
|
||||
const monitor = getMonitor();
|
||||
console.log(`getResource: props=${JSON.stringify(props)}`);
|
||||
const resopAsync = prepareResource(label, res, custom, props, opts);
|
||||
|
||||
const preallocError = new Error();
|
||||
|
@ -195,20 +194,16 @@ export function getResource(res: Resource, t: string, name: string, custom: bool
|
|||
log.debug(`GetResource RPC prepared: id=${resolvedURN}, t=${t}, name=${name}` +
|
||||
(excessiveDebugOutput ? `, obj=${JSON.stringify(resop.serializedProps)}` : ``));
|
||||
|
||||
console.log(`Resolved URN: ${resolvedURN}`);
|
||||
const result = await invoke("pulumi:pulumi:readStackResource", {
|
||||
urn: resolvedURN,
|
||||
});
|
||||
console.log(`Got stack resource outputs: ${JSON.stringify(result)}`);
|
||||
}, { async: true });
|
||||
|
||||
// Now resolve everything: the URN, the ID (supplied as input), and the output properties.
|
||||
resop.resolveURN(resolvedURN);
|
||||
if (custom) {
|
||||
resop.resolveID!(result.outputs.id, result.outputs.id !== undefined);
|
||||
}
|
||||
console.log("resolving outputs...");
|
||||
console.log(resop.resolvers);
|
||||
resolveProperties(res, resop.resolvers, t, name, props);
|
||||
resolveProperties(res, resop.resolvers, t, name, result.outputs);
|
||||
}), label);
|
||||
}
|
||||
|
||||
|
@ -358,8 +353,6 @@ async function prepareResource(label: string, res: Resource, custom: boolean,
|
|||
};
|
||||
}
|
||||
|
||||
console.log(`prepareResource: props=${JSON.stringify(props)}`);
|
||||
|
||||
// Now "transfer" all input properties into unresolved Promises on res. This way,
|
||||
// this resource will look like it has all its output properties to anyone it is
|
||||
// passed to. However, those promises won't actually resolve until the registerResource
|
||||
|
@ -419,8 +412,6 @@ async function prepareResource(label: string, res: Resource, custom: boolean,
|
|||
}
|
||||
}
|
||||
|
||||
console.log(`prepareResource: serializedProps=${JSON.stringify(serializedProps)}`);
|
||||
|
||||
return {
|
||||
resolveURN: resolveURN!,
|
||||
resolveID: resolveID,
|
||||
|
|
|
@ -38,7 +38,6 @@ export type OutputResolvers = Record<string, (value: any, isStable: boolean, isS
|
|||
export function transferProperties(onto: Resource, label: string, props: Inputs): OutputResolvers {
|
||||
const resolvers: OutputResolvers = {};
|
||||
for (const k of Object.keys(props)) {
|
||||
console.log(`transferProperties: ${k}`);
|
||||
// Skip "id" and "urn", since we handle those specially.
|
||||
if (k === "id" || k === "urn") {
|
||||
continue;
|
||||
|
@ -149,7 +148,6 @@ export function resolveProperties(
|
|||
|
||||
// Now go ahead and resolve all properties present in the inputs and outputs set.
|
||||
for (const k of Object.keys(allProps)) {
|
||||
console.log(`resolveroperties: ${k}`);
|
||||
// Skip "id" and "urn", since we handle those specially.
|
||||
if (k === "id" || k === "urn") {
|
||||
continue;
|
||||
|
@ -159,7 +157,6 @@ export function resolveProperties(
|
|||
const resolve = resolvers[k];
|
||||
|
||||
if (resolve === undefined) {
|
||||
console.log(`resolveroperties: ${k} not in initial property-map`);
|
||||
// engine returned a property that was not in our initial property-map. This can happen
|
||||
// for outputs that were registered through direct calls to 'registerOutputs'. We do
|
||||
// *not* want to do anything with these returned properties. First, the component
|
||||
|
@ -182,8 +179,6 @@ export function resolveProperties(
|
|||
const isSecret = isRpcSecret(value);
|
||||
value = unwrapRpcSecret(value);
|
||||
|
||||
|
||||
console.log(`resolveroperties: ${k} => ${value}`);
|
||||
try {
|
||||
// If the value the engine handed back is or contains an unknown value, the resolver will mark its value as
|
||||
// unknown automatically, so we just pass true for isKnown here. Note that unknown values will only be
|
||||
|
@ -200,7 +195,6 @@ export function resolveProperties(
|
|||
// We will resolve all of these values as `undefined`, and will mark the value as known if we are not running a
|
||||
// preview.
|
||||
for (const k of Object.keys(resolvers)) {
|
||||
console.log(`resolveroperties: resolved for property that wasn't returned ${k}`);
|
||||
if (!allProps.hasOwnProperty(k)) {
|
||||
const resolve = resolvers[k];
|
||||
resolve(undefined, !isDryRun(), false);
|
||||
|
|
Loading…
Reference in a new issue