Initial example working

This commit is contained in:
Luke Hoban 2019-12-22 11:25:17 -08:00 committed by Pat Gavlin
parent a41316d5b7
commit e8242bca33
5 changed files with 41 additions and 29 deletions

View 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

View file

@ -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;

View file

@ -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;
}

View file

@ -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,

View file

@ -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);