Compare commits

...

50 commits

Author SHA1 Message Date
Pat Gavlin ee7824deb6 Regen proto files. 2020-07-15 16:15:02 -07:00
Luke Hoban 401ecb15a9 Add a Node.js SDK test 2020-07-15 15:17:44 -07:00
Luke Hoban e078b718a4 Fix lint issues 2020-07-15 15:17:44 -07:00
Luke Hoban cdac8f47a5 Tests for reading Component resources and nested Resource references 2020-07-15 15:17:44 -07:00
Luke Hoban 5fb46412de TestReadStackResource 2020-07-15 15:17:44 -07:00
Luke Hoban 0c92e7bc04 Working Python test
Include a setup.py for the Python component so that we can run `yarn install` and `yarn link` inside the Python package as part of the test setup.
2020-07-15 15:17:44 -07:00
Luke Hoban 63603f0133 Typos 2020-07-15 15:17:44 -07:00
Luke Hoban 2a35a7283a PR feedback 2020-07-15 15:17:44 -07:00
Luke Hoban 3d07de0535 Use sync.Map for resourceState 2020-07-15 15:17:44 -07:00
Luke Hoban f23932c020 Fixes 2020-07-15 15:17:44 -07:00
Luke Hoban c41bccffce Reduce scope of serializataion breaking change 2020-07-15 15:17:22 -07:00
Luke Hoban 204b779cbb Remove TODO from code 2020-07-15 15:17:22 -07:00
Luke Hoban 18db3b1156 More PR feedback 2020-07-15 15:17:22 -07:00
Luke Hoban 8b50f3da6d Get mypy clean 2020-07-15 15:17:22 -07:00
Luke Hoban b8d1c7b589 Avoid removing the automatic registerOutputs for components. 2020-07-15 15:17:22 -07:00
Luke Hoban 4339f2ad84 Code review feedback 2020-07-15 15:17:22 -07:00
Luke Hoban 4a9665cb0d Negotiate gRPC port with server process 2020-07-15 15:17:22 -07:00
Luke Hoban efa1f92cb4 Reorganize examples and test NodeJS->NodeJS
Python->NodeJS test not yet enabled because we don't yet have a test framework way to set up the `yarn link` inside the nested NodeJS project.
2020-07-15 15:17:22 -07:00
Luke Hoban bc41ee33c3 Initial work on integration test 2020-07-15 15:17:22 -07:00
Luke Hoban d68eee6632 Fix test 2020-07-15 15:17:05 -07:00
Luke Hoban b8c864a559 Fix test_custom_resource test
Adapt the test to the (current) breaking change in this PR.
2020-07-15 15:17:05 -07:00
Luke Hoban 9d6edeac1c pylint clean 2020-07-15 15:17:05 -07:00
Luke Hoban 5eda91bb9e More narrowly marshal rich Resources
Only use richer Resource marshalling on the return values from Resource Monitor invokes - to avoid changing behaviour for other cases that may not yet support richer Resources.

Also ensure working directory for `node` process is based on the location of the library being loaded so that modules load correctly.
2020-07-15 15:17:05 -07:00
Luke Hoban 11efdb2dd6 Handle deserializing AWS resources 2020-07-15 15:16:44 -07:00
Luke Hoban e5d6d950a5 Move ProxyComponentResource into Python SDK 2020-07-15 15:16:44 -07:00
Luke Hoban 94f7efcf54 Move ProxyComponentResource into Node SDK 2020-07-15 15:16:44 -07:00
Luke Hoban 8f6a02920e Move some shared pieces into the Node SDK 2020-07-15 15:16:44 -07:00
Luke Hoban c3ae4a5100 Factor our ProxyComponentResource base class
For both NodeJS and Python.
2020-07-15 15:16:44 -07:00
Luke Hoban c3587ecded Minor code cleanup 2020-07-15 15:16:44 -07:00
Luke Hoban 7c323f8f60 Update README 2020-07-15 15:16:44 -07:00
Luke Hoban e2d6058d2e Support returning inner resource references 2020-07-15 15:16:44 -07:00
Luke Hoban b3a20dfe47 Remote resource construction working! 2020-07-15 15:16:44 -07:00
Luke Hoban 19c3268f7b Add 'urn' resource option to Python
Initial support for `get_resource`
2020-07-15 15:15:35 -07:00
Luke Hoban c5e4bea769 WIP Python port 2020-07-15 15:10:28 -07:00
Luke Hoban fd7296797a More reorganization 2020-07-15 15:10:28 -07:00
Luke Hoban 033924efa9 Fix support for returning resources
Remove compatibility fallback for first-class Resources.
2020-07-15 15:10:28 -07:00
Luke Hoban 35d924b589 Reorganize example files 2020-07-15 15:09:34 -07:00
Luke Hoban ccbeae1f5f README reorganization 2020-07-15 15:09:34 -07:00
Luke Hoban 0c53db0587 Switch to real gRPC interface between host and guest
Introduce a new gRPC service for `runtime`.

Support a single `construct` method on the new gRPC service.

Spawn a separate Node.js process to run the gRPC service, and remote calls to it from the host language runtime `construct` API.
2020-07-15 15:09:34 -07:00
Luke Hoban ddc222d236 Introduce a first class Resource PropertyValue
Wraps a URN, but as a placeholder for a resource that should be re-hydrated on demarshaling.
2020-07-15 15:08:11 -07:00
Luke Hoban 51adc500de Serialize opts 2020-07-15 15:04:54 -07:00
Luke Hoban b37e47f46f No longer need to skip urn in readStackResource result 2020-07-15 15:04:54 -07:00
Luke Hoban abab631605 Remove deserializeUrns from deserializeProperties
This is now supported in resolveProperties instead.
2020-07-15 15:04:54 -07:00
Luke Hoban 092ee049dc Support customresources as outputs 2020-07-15 15:04:03 -07:00
Luke Hoban 4042adef9d Refactor example
Separate three pieces:
1. User code
2. Auto-generated proxy code
3. Core language SDK code
2020-07-15 15:04:03 -07:00
Luke Hoban c2942c3bcc Support components as outputs 2020-07-15 15:04:03 -07:00
Luke Hoban 0c914ebfb4 A little more cleanup 2020-07-15 14:36:01 -07:00
Luke Hoban e8242bca33 Initial example working 2020-07-15 14:36:01 -07:00
Luke Hoban a41316d5b7 WIP 2020-07-15 14:36:01 -07:00
Luke Hoban 5c5f41d3e6 Very simple "multilang" 2020-07-15 14:36:01 -07:00
59 changed files with 2510 additions and 69 deletions

1
.gitignore vendored
View file

@ -22,4 +22,5 @@ coverage.cov
# By default, we don't check in yarn.lock files
**/yarn.lock
# Turning on MyPy in VSCode creates this workspace local folder
.mypy_cache

1
examples/multilang/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
Pulumi.*.yaml

View file

@ -0,0 +1,3 @@
name: multilang
runtime: nodejs
description: A small example that uses our multilang support.

View file

@ -0,0 +1,18 @@
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";
import * as mycomponent from "./mycomponent/nodejs";
// This should go inside `@pulumi/aws`.
pulumi.runtime.registerProxyConstructor("aws:ec2/securityGroup:SecurityGroup", aws.ec2.SecurityGroup);
////////////////////////////////
// This is code the user would write to use `mycomponent` from the guest language.
const res = new mycomponent.MyComponent("n", {
input1: Promise.resolve(42),
}, { ignoreChanges: ["input1"] /*, providers: { "aws": awsProvider } */ });
export const id2 = res.myid;
export const output1 = res.output1;
export const innerComponent = res.innerComponent.data;
export const nodeSecurityGroupId = res.nodeSecurityGroup.id;

View file

@ -0,0 +1,35 @@
const pulumi = require("@pulumi/pulumi");
const aws = require("@pulumi/aws");
// TODO: This should be done in the AWS library. Also, not clear that providers work correctly
// currently - we appear to end up with calls to `Check` a provider before it has been configured -
// I suspect that the proxy is getting confused as a separate identity even though it's the same
// URN.
pulumi.runtime.registerProxyConstructor("pulumi:providers:aws", aws.Provider)
class MyInnerComponent extends pulumi.ComponentResource {
constructor(name, args, opts) {
super("my:mod:MyInnerComponent", name, {}, opts);
this.data = "mydata";
this.registerOutputs({
data: "mydata",
});
}
}
class MyComponent extends pulumi.ComponentResource {
constructor(name, args, opts) {
super("my:mod:MyComponent", name, {}, opts);
this.output1 = pulumi.output(args.input1);
this.myid = pulumi.output("foo");
this.innerComponent = new MyInnerComponent("inner", {}, { parent: this });
this.nodeSecurityGroup = new aws.ec2.SecurityGroup("securityGroup", {}, { parent: this });
this.registerOutputs({
myid: this.myid,
output1: this.output1,
innerComponent: this.innerComponent,
nodeSecurityGroup: this.nodeSecurityGroup,
});
}
}
exports.MyComponent = MyComponent;

View file

@ -0,0 +1,54 @@
// This file would be autogenerated from schema.
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";
export interface MyComponentArgs {
input1: pulumi.Input<number>;
}
export class MyComponent extends pulumi.remote.ProxyComponentResource {
public myid!: pulumi.Output<string>;
public output1!: pulumi.Output<number>;
public innerComponent!: MyInnerComponent;
public nodeSecurityGroup!: aws.ec2.SecurityGroup;
constructor(name: string, args: MyComponentArgs, opts?: pulumi.ComponentResourceOptions) {
super(
"my:mod:MyComponent",
name,
require.resolve(".."),
"MyComponent",
args,
{
myid: undefined,
output1: undefined,
innerComponent: undefined,
nodeSecurityGroup: undefined,
},
opts,
);
}
}
pulumi.runtime.registerProxyConstructor("my:mod:MyComponent", MyComponent);
export interface MyInnerComponentArgs {
}
export class MyInnerComponent extends pulumi.remote.ProxyComponentResource {
public data!: pulumi.Output<string>;
constructor(name: string, args: MyInnerComponentArgs, opts?: pulumi.ComponentResourceOptions) {
super(
"my:mod:MyInnerComponent",
name,
require.resolve(".."),
"MyInnerComponent",
args,
{
data: undefined,
},
opts,
);
}
}
pulumi.runtime.registerProxyConstructor("my:mod:MyInnerComponent", MyInnerComponent);

View file

@ -0,0 +1,7 @@
{
"name": "@pulumi/eks",
"version": "0.0.1",
"dependencies": {
"@pulumi/pulumi": "latest"
}
}

View file

@ -0,0 +1,12 @@
{
"name": "multilang",
"devDependencies": {
"@types/node": "^8.0.0"
},
"peerDependencies": {
"@pulumi/pulumi": "latest"
},
"dependencies": {
"@pulumi/aws": "^1.17.0"
}
}

View file

@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2016",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts"
]
}

4
examples/multilang/python/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
__pycache__
Pipfile.lock
*.egg-info
package-lock.json

View file

@ -0,0 +1,14 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
mypy = "*"
[packages]
pulumi = {editable = true,path = "./../../../sdk/python/env/src"}
pulumi-aws = "*"
[requires]
python_version = "3.7"

View file

@ -0,0 +1,3 @@
name: multilang-python
runtime: python
description: A small example that uses our multilang support.

View file

@ -0,0 +1,31 @@
# Copyright 2016-2018, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pulumi
from pulumi import ResourceOptions
from pulumi_aws import ec2
# TODO - this should go inside `pulumi_aws`.
pulumi.runtime.register_proxy_constructor("aws:ec2/securityGroup:SecurityGroup", lambda name, opts: ec2.SecurityGroup(name, ResourceOptions(**opts)))
######
# This is code the user would write to use `mycomponent` from the guest language.
from pulumi_mycomponent import MyComponent
res = MyComponent("n", input1=42)
pulumi.export("id2", res.myid)
pulumi.export("output1", res.output1)
pulumi.export("innerComponent", res.innerComponent.data)
pulumi.export("nodeSecurityGroupId", res.nodeSecurityGroup.id)

View file

@ -0,0 +1 @@
# MyComponent in Python

View file

@ -0,0 +1,35 @@
const pulumi = require("@pulumi/pulumi");
const aws = require("@pulumi/aws");
// TODO: This should be done in the AWS library. Also, not clear that providers work correctly
// currently - we appear to end up with calls to `Check` a provider before it has been configured -
// I suspect that the proxy is getting confused as a separate identity even though it's the same
// URN.
pulumi.runtime.registerProxyConstructor("pulumi:providers:aws", aws.Provider)
class MyInnerComponent extends pulumi.ComponentResource {
constructor(name, args, opts) {
super("my:mod:MyInnerComponent", name, {}, opts);
this.data = "mydata";
this.registerOutputs({
data: "mydata",
});
}
}
class MyComponent extends pulumi.ComponentResource {
constructor(name, args, opts) {
super("my:mod:MyComponent", name, {}, opts);
this.output1 = pulumi.output(args.input1);
this.myid = pulumi.output("foo");
this.innerComponent = new MyInnerComponent("inner", {}, { parent: this });
this.nodeSecurityGroup = new aws.ec2.SecurityGroup("securityGroup", {}, { parent: this });
this.registerOutputs({
myid: this.myid,
output1: this.output1,
innerComponent: this.innerComponent,
nodeSecurityGroup: this.nodeSecurityGroup,
});
}
}
exports.MyComponent = MyComponent;

View file

@ -0,0 +1,8 @@
{
"name": "@pulumi/mycomponent",
"version": "0.0.1",
"dependencies": {
"@pulumi/pulumi": "latest",
"@pulumi/aws": "latest"
}
}

View file

@ -0,0 +1,60 @@
# Copyright 2016-2018, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from pulumi import Output, ResourceOptions, Input
from pulumi.remote import ProxyComponentResource
from pulumi.runtime import register_proxy_constructor
from typing import Optional
from pulumi_aws import ec2
class MyInnerComponent(ProxyComponentResource):
data: Output[str]
def __init__(__self__, resource_name: str, opts: Optional[ResourceOptions]=None) -> None:
super().__init__(
"my:mod:MyInnerComponent",
resource_name,
os.path.abspath(os.path.join(os.path.dirname(__file__),"..")),
"MyInnerComponent",
{},
{
"data": None
},
opts,
)
register_proxy_constructor("my:mod:MyInnerComponent", lambda name, opts: MyInnerComponent(name, ResourceOptions(**opts)))
class MyComponent(ProxyComponentResource):
myid: Output[str]
output1: Output[int]
innerComponent: MyInnerComponent
nodeSecurityGroup: ec2.SecurityGroup
def __init__(__self__, resource_name: str, opts: Optional[ResourceOptions]=None, input1:Optional[Input]=None) -> None:
super().__init__(
"my:mod:MyComponent",
resource_name,
os.path.abspath(os.path.join(os.path.dirname(__file__),"..")),
"MyComponent",
{
"input1": input1
},
{
"myid": None,
"output1": None,
"innerComponent": None,
"nodeSecurityGroup": None
},
opts,
)
register_proxy_constructor("my:mod:MyComponent", MyComponent)

View file

@ -0,0 +1,59 @@
# Copyright 2016-2020, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import errno
from setuptools import setup, find_packages
from setuptools.command.install import install
from setuptools.command.develop import develop
from subprocess import check_call
def npm_install():
# Using `yarn` here because this package is designed specifically to be used in our tests, and we need to be able to
# `yarn link` to test at this layer.
check_call(['yarn', 'install'])
def npm_link_pulumi():
# Using `yarn` here because this package is designed specifically to be used in our tests, and we need to be able to
# `yarn link` to test at this layer.
check_call(['yarn', 'link', '@pulumi/pulumi'])
class InstallNPMPackageCommand(install):
def run(self):
install.run(self)
npm_install()
class DevelopNPMPackageCommand(develop):
def run(self):
develop.run(self)
npm_install()
npm_link_pulumi()
def readme():
with open('README.md', encoding='utf-8') as f:
return f.read()
setup(
name='pulumi_mycomponent',
version='0.0.1',
description='MyComponent',
long_description=readme(),
long_description_content_type='text/markdown',
cmdclass={
'install': InstallNPMPackageCommand,
'develop': DevelopNPMPackageCommand,
},
url='https://github.com/pulumi/pulumi',
license='Apache 2.0',
packages=find_packages(),
zip_safe=False)

View file

@ -0,0 +1,2 @@
pulumi>=1.0.0,<2.0.0
pulumi_aws>=1.0.0,<2.0.0

View file

@ -370,6 +370,10 @@ func filterPropertyMap(propertyMap resource.PropertyMap, debug bool) resource.Pr
}
case resource.Secret:
return "[secret]"
case resource.Resource:
return resource.Resource{
Urn: filterPropertyValue(t.Urn),
}
case resource.Computed:
return resource.Computed{
Element: filterPropertyValue(t.Element),

View file

@ -5936,3 +5936,116 @@ func TestProviderInheritanceGolangLifecycle(t *testing.T) {
}
p.Run(t, nil)
}
func TestReadStackResource(t *testing.T) {
loaders := []*deploytest.ProviderLoader{
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
return &deploytest.Provider{
CreateF: func(urn resource.URN,
inputs resource.PropertyMap, timeout float64) (resource.ID, resource.PropertyMap, resource.Status, error) {
return resource.ID("id"), resource.PropertyMap{
resource.PropertyKey("inprop"): inputs[resource.PropertyKey("inprop")],
resource.PropertyKey("outprop"): resource.NewStringProperty("goodbye"),
}, resource.StatusOK, nil
},
}, nil
}),
}
// Register a resource then read it back using its URN.
program := deploytest.NewLanguageRuntime(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
inPropValue := "hello"
var outPropValue string
outputsKey := resource.PropertyKey("outputs")
outrespropKey := resource.PropertyKey("outresprop")
outpropKey := resource.PropertyKey("outprop")
idKey := resource.PropertyKey("id")
inpropKey := resource.PropertyKey("inprop")
urnKey := resource.PropertyKey("urn")
////////////////////////////////
// Part 1: Custom Resource
////////////////////////////////
aUrn, _, aProps, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
Inputs: resource.PropertyMap{
inpropKey: resource.NewStringProperty(inPropValue),
},
})
assert.NoError(t, err)
if !info.DryRun {
outPropValue = aProps[outpropKey].StringValue()
}
aReadProps, _, err := monitor.Invoke(tokens.ModuleMember("pulumi:pulumi:readStackResource"), resource.PropertyMap{
urnKey: resource.NewStringProperty(string(aUrn)),
}, "", "1.0.0")
assert.NoError(t, err)
assert.Equal(t, string(aUrn), aReadProps[urnKey].StringValue())
assert.Equal(t, inPropValue, aReadProps[outputsKey].ObjectValue()[inpropKey].StringValue())
// These two fields of the result are not available during the preview step
if !info.DryRun {
assert.Equal(t, "id", aReadProps[idKey].StringValue())
assert.Equal(t, outPropValue, aReadProps[outputsKey].ObjectValue()[outpropKey].StringValue())
}
////////////////////////////////
// Part 2: Component Resource
// with nested Resources
////////////////////////////////
bUrn, _, _, err := monitor.RegisterResource("pkgB:m:typB", "resB", false, deploytest.ResourceOptions{
Inputs: resource.PropertyMap{
inpropKey: resource.NewStringProperty(inPropValue),
},
})
assert.NoError(t, err)
// Try to read the resource before "RegisterResourceOutputs" is called
bReadProps, _, err := monitor.Invoke(tokens.ModuleMember("pulumi:pulumi:readStackResource"), resource.PropertyMap{
urnKey: resource.NewStringProperty(string(bUrn)),
}, "", "1.0.0")
assert.NoError(t, err)
assert.Equal(t, string(bUrn), bReadProps[urnKey].StringValue())
assert.Equal(t, "", bReadProps[idKey].StringValue())
if info.DryRun {
// TODO: This does not seem intentional (or good) - but currently inputs are propagated into results of this
// function during preview - only if this is called before `RegisterResourceOutputs` - but are not visible
// to this function during update.
assert.Equal(t, inPropValue, bReadProps[outputsKey].ObjectValue()[inpropKey].StringValue())
}
err = monitor.RegisterResourceOutputs(bUrn, resource.PropertyMap{
outrespropKey: resource.NewResourceProperty(resource.Resource{
Urn: resource.NewStringProperty(string(aUrn)),
}),
})
assert.NoError(t, err)
// Try to read the resource again after "RegisterResourceOutputs" is called
bReadProps, _, err = monitor.Invoke(tokens.ModuleMember("pulumi:pulumi:readStackResource"), resource.PropertyMap{
urnKey: resource.NewStringProperty(string(bUrn)),
}, "", "1.0.0")
assert.NoError(t, err)
assert.Equal(t, string(bUrn), bReadProps[urnKey].StringValue())
assert.Equal(t, "", bReadProps[idKey].StringValue())
assert.Equal(t, string(aUrn), bReadProps[outputsKey].ObjectValue()[outrespropKey].ResourceValue().Urn.StringValue())
return nil
})
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
p := &TestPlan{
Options: UpdateOptions{host: host},
}
p.Steps = []TestStep{{Op: Update}}
snap := p.Run(t, nil)
assert.Len(t, snap.Resources, 4)
assert.Equal(t, string(snap.Resources[0].URN.Name()), "default")
assert.Equal(t, string(snap.Resources[1].URN.Name()), "resA")
assert.Equal(t, string(snap.Resources[2].URN.Name()), "default_1_0_0")
assert.Equal(t, string(snap.Resources[3].URN.Name()), "resB")
}

View file

@ -12,6 +12,7 @@ import (
"github.com/pulumi/pulumi/sdk/v2/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/v2/go/common/tokens"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/logging"
"github.com/pulumi/pulumi/sdk/v2/go/common/workspace"
)
@ -19,14 +20,16 @@ type builtinProvider struct {
backendClient BackendClient
context context.Context
cancel context.CancelFunc
plan *Plan
}
func newBuiltinProvider(backendClient BackendClient) *builtinProvider {
func newBuiltinProvider(backendClient BackendClient, plan *Plan) *builtinProvider {
ctx, cancel := context.WithCancel(context.Background())
return &builtinProvider{
backendClient: backendClient,
context: ctx,
cancel: cancel,
plan: plan,
}
}
@ -148,9 +151,18 @@ func (p *builtinProvider) Read(urn resource.URN, id resource.ID,
}, resource.StatusOK, nil
}
// readStackOutputs returns the full set of stack outputs from a target stack.
const readStackOutputs = "pulumi:pulumi:readStackOutputs"
// readStackResourceOutputs returns the resource outputs for every resource in a taraget stack. If
// targeting the current stack, it returns results from the initial checkpoint, not the live
// registered resource state.
const readStackResourceOutputs = "pulumi:pulumi:readStackResourceOutputs"
// readStackResource returns the live registered resource outputs for the target resource from the
// current stack.
const readStackResource = "pulumi:pulumi:readStackResource"
func (p *builtinProvider) Invoke(tok tokens.ModuleMember,
args resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
@ -167,6 +179,12 @@ func (p *builtinProvider) Invoke(tok tokens.ModuleMember,
return nil, nil, err
}
return outs, nil, nil
case readStackResource:
outs, err := p.readStackResource(args)
if err != nil {
return nil, nil, err
}
return outs, nil, nil
default:
return nil, nil, errors.Errorf("unrecognized function name: '%v'", tok)
}
@ -241,3 +259,30 @@ func (p *builtinProvider) readStackResourceOutputs(inputs resource.PropertyMap)
"outputs": resource.NewObjectProperty(outputs),
}, nil
}
func (p *builtinProvider) readStackResource(inputs resource.PropertyMap) (resource.PropertyMap, error) {
logging.V(9).Infof("builtinProvider: %v(inputs=%v)", readStackResource, inputs)
urnProp, ok := inputs["urn"]
if !ok {
return nil, fmt.Errorf("readStackResource missing required 'urn' argument")
}
if !urnProp.IsString() {
return nil, fmt.Errorf("readStackResource 'urn' argument expected string got %v", urnProp)
}
urn := resource.URN(urnProp.StringValue())
urnState, ok := p.plan.current.Load(urn)
if !ok || urnState == nil {
return nil, fmt.Errorf("resource '%s' not yet registered in current stack state", urn)
}
state := urnState.(*resource.State)
contract.Assert(state.URN == urn)
logging.V(9).Infof("builtinProvider: %v(inputs=%v) => (outputs=%v)", readStackResource, inputs, state.Outputs)
return resource.PropertyMap{
"urn": resource.NewStringProperty(string(state.URN)),
"id": resource.NewStringProperty(string(state.ID)),
"outputs": resource.NewObjectProperty(state.Outputs),
}, nil
}

View file

@ -133,6 +133,25 @@ func (rm *ResourceMonitor) RegisterResource(t tokens.Type, name string, custom b
return resource.URN(resp.Urn), resource.ID(resp.Id), outs, nil
}
func (rm *ResourceMonitor) RegisterResourceOutputs(urn resource.URN, outputs resource.PropertyMap) error {
// marshal outputs
outs, err := plugin.MarshalProperties(outputs, plugin.MarshalOptions{KeepUnknowns: true, KeepResources: true})
if err != nil {
return err
}
// submit request
_, err = rm.resmon.RegisterResourceOutputs(context.Background(), &pulumirpc.RegisterResourceOutputsRequest{
Urn: string(urn),
Outputs: outs,
})
if err != nil {
return err
}
return nil
}
func (rm *ResourceMonitor) ReadResource(t tokens.Type, name string, id resource.ID, parent resource.URN,
inputs resource.PropertyMap, provider string, version string) (resource.URN, resource.PropertyMap, error) {
@ -191,7 +210,10 @@ func (rm *ResourceMonitor) Invoke(tok tokens.ModuleMember, inputs resource.Prope
}
// unmarshal outputs
outs, err := plugin.UnmarshalProperties(resp.Return, plugin.MarshalOptions{KeepUnknowns: true})
outs, err := plugin.UnmarshalProperties(resp.Return, plugin.MarshalOptions{
KeepUnknowns: true,
KeepResources: true,
})
if err != nil {
return nil, nil, err
}

View file

@ -17,6 +17,7 @@ package deploy
import (
"context"
"math"
"sync"
"github.com/blang/semver"
"github.com/pkg/errors"
@ -117,6 +118,7 @@ type Plan struct {
preview bool // true if this plan is to be previewed rather than applied.
depGraph *graph.DependencyGraph // the dependency graph of the old snapshot
providers *providers.Registry // the provider registry for this plan.
current *sync.Map // a map of all current resources
}
// addDefaultProviders adds any necessary default provider definitions and references to the given snapshot. Version
@ -258,8 +260,19 @@ func NewPlan(ctx *plugin.Context, target *Target, prev *Snapshot, source Source,
depGraph = graph.NewDependencyGraph(oldResources)
}
plan := &Plan{
ctx: ctx,
target: target,
prev: prev,
olds: olds,
source: source,
localPolicyPackPaths: localPolicyPackPaths,
preview: preview,
depGraph: depGraph,
}
// Create a new builtin provider. This provider implements features such as `getStack`.
builtins := newBuiltinProvider(backendClient)
builtins := newBuiltinProvider(backendClient, plan)
// Create a new provider registry. Although we really only need to pass in any providers that were present in the
// old resource list, the registry itself will filter out other sorts of resources when processing the prior state,
@ -269,17 +282,9 @@ func NewPlan(ctx *plugin.Context, target *Target, prev *Snapshot, source Source,
return nil, err
}
return &Plan{
ctx: ctx,
target: target,
prev: prev,
olds: olds,
source: source,
localPolicyPackPaths: localPolicyPackPaths,
preview: preview,
depGraph: depGraph,
providers: reg,
}, nil
plan.providers = reg
return plan, nil
}
func (p *Plan) Ctx() *plugin.Context { return p.ctx }

View file

@ -168,6 +168,7 @@ func (pe *planExecutor) Execute(callerCtx context.Context, opts Options, preview
// Set up a step generator for this plan.
pe.stepGen = newStepGenerator(pe.plan, opts, updateTargetsOpt, replaceTargetsOpt)
pe.plan.current = pe.stepGen.resourceStates
// Retire any pending deletes that are currently present in this plan.
if res := pe.retirePendingDeletes(callerCtx, opts, preview); res != nil {

View file

@ -563,8 +563,9 @@ func (rm *resmon) Invoke(ctx context.Context, req *pulumirpc.InvokeRequest) (*pu
return nil, errors.Wrapf(err, "invocation of %v returned an error", tok)
}
mret, err := plugin.MarshalProperties(ret, plugin.MarshalOptions{
Label: label,
KeepUnknowns: true,
Label: label,
KeepUnknowns: true,
KeepResources: true,
})
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal %v return", tok)

View file

@ -50,7 +50,7 @@ func NewQuerySource(cancel context.Context, plugctx *plugin.Context, client Back
provs ProviderSource) (QuerySource, error) {
// Create a new builtin provider. This provider implements features such as `getStack`.
builtins := newBuiltinProvider(client)
builtins := newBuiltinProvider(client, nil)
reg, err := providers.NewRegistry(plugctx.Host, nil, false, builtins)
if err != nil {

View file

@ -16,6 +16,7 @@ package deploy
import (
"strings"
"sync"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/v2/resource/deploy/providers"
@ -61,7 +62,9 @@ type stepGenerator struct {
pendingDeletes map[*resource.State]bool // set of resources (not URNs!) that are pending deletion
providers map[resource.URN]*resource.State // URN map of providers that we have seen so far.
resourceGoals map[resource.URN]*resource.Goal // URN map of goals for ALL resources we have seen so far.
resourceStates map[resource.URN]*resource.State // URN map of state for ALL resources we have seen so far.
// resourceStates is exposed to other goroutines so needs to be a sync.Map.
resourceStates *sync.Map // URN map of state for ALL resources we have seen so far.
// a map from URN to a list of property keys that caused the replacement of a dependent resource during a
// delete-before-replace.
@ -256,7 +259,7 @@ func (sg *stepGenerator) generateSteps(event RegisterResourceEvent) ([]Step, res
// Mark the URN/resource as having been seen. So we can run analyzers on all resources seen, as well as
// lookup providers for calculating replacement of resources that use the provider.
sg.resourceGoals[urn] = goal
sg.resourceStates[urn] = new
sg.resourceStates.Store(urn, new)
if providers.IsProviderType(goal.Type) {
sg.providers[urn] = new
}
@ -1286,8 +1289,11 @@ func (sg *stepGenerator) calculateDependentReplacements(root *resource.State) ([
func (sg *stepGenerator) AnalyzeResources() result.Result {
resourcesSeen := sg.resourceStates
resources := make([]plugin.AnalyzerStackResource, 0, len(resourcesSeen))
for urn, v := range resourcesSeen {
// resources := make([]plugin.AnalyzerStackResource, 0, resourcesSeen.len(resourcesSeen))
var resources []plugin.AnalyzerStackResource
resourcesSeen.Range(func(key, val interface{}) bool {
urn := key.(resource.URN)
v := val.(*resource.State)
goal := sg.resourceGoals[urn]
resource := plugin.AnalyzerStackResource{
AnalyzerResource: plugin.AnalyzerResource{
@ -1320,7 +1326,8 @@ func (sg *stepGenerator) AnalyzeResources() result.Result {
}
}
resources = append(resources, resource)
}
return true
})
analyzers := sg.plan.ctx.Host.ListAnalyzers()
for _, analyzer := range analyzers {
@ -1335,7 +1342,7 @@ func (sg *stepGenerator) AnalyzeResources() result.Result {
// the default root stack URN.
var urn resource.URN
if d.URN != "" {
if _, ok := resourcesSeen[d.URN]; ok {
if _, ok := resourcesSeen.Load(d.URN); ok {
urn = d.URN
}
}
@ -1369,7 +1376,7 @@ func newStepGenerator(
pendingDeletes: make(map[*resource.State]bool),
providers: make(map[resource.URN]*resource.State),
resourceGoals: make(map[resource.URN]*resource.Goal),
resourceStates: make(map[resource.URN]*resource.State),
resourceStates: &sync.Map{},
dependentReplaceKeys: make(map[resource.URN][]resource.PropertyKey),
aliased: make(map[resource.URN]resource.URN),
}

View file

@ -543,6 +543,17 @@ func DeserializePropertyValue(v interface{}, dec config.Decrypter,
cachingCrypter.insert(prop.SecretValue(), plaintext, ciphertext)
}
return prop, nil
case resource.ResourceSig:
urn, ok := objmap["urn"].(string)
if !ok {
return resource.PropertyValue{}, errors.New("malformed resource value: missing urn")
}
ev, err := DeserializePropertyValue(urn, dec)
if err != nil {
return resource.PropertyValue{}, err
}
return ev, nil
default:
return resource.PropertyValue{}, errors.Errorf("unrecognized signature '%v' in property map", sig)
}

View file

@ -36,6 +36,7 @@ type MarshalOptions struct {
ComputeAssetHashes bool // true if we are computing missing asset hashes on the fly.
KeepSecrets bool // true if we are keeping secrets (otherwise we replace them with their underlying value).
RejectAssets bool // true if we should return errors on Asset and Archive values.
KeepResources bool // true if we are keeping resoures (otherwise we return raw urn).
SkipInternalKeys bool // true to skip internal property keys (keys that start with "__") in the resulting map.
}
@ -161,6 +162,16 @@ func MarshalPropertyValue(v resource.PropertyValue, opts MarshalOptions) (*struc
"value": v.SecretValue().Element,
})
return MarshalPropertyValue(secret, opts)
} else if v.IsResource() {
if !opts.KeepResources {
logging.V(5).Infof("marshalling resource value as raw urn as opts.KeepResources is false")
return MarshalPropertyValue(v.ResourceValue().Urn, opts)
}
res := resource.NewObjectProperty(resource.PropertyMap{
resource.SigKey: resource.NewStringProperty(resource.ResourceSig),
"urn": v.ResourceValue().Urn,
})
return MarshalPropertyValue(res, opts)
}
contract.Failf("Unrecognized property value in RPC[%s]: %v (type=%v)", opts.Label, v.V, reflect.TypeOf(v.V))
@ -345,6 +356,16 @@ func UnmarshalPropertyValue(v *structpb.Value, opts MarshalOptions) (*resource.P
}
s := resource.MakeSecret(value)
return &s, nil
case resource.ResourceSig:
urn, ok := obj["urn"]
if !ok {
return nil, errors.New("malformed RPC resource: missing urn")
}
if !urn.IsString() {
return nil, errors.New("malformed RPC resource: urn not a string")
}
r := resource.NewResourceProperty(resource.Resource{Urn: urn})
return &r, nil
default:
return nil, errors.Errorf("unrecognized signature '%v' in property map", sig)
}

View file

@ -96,6 +96,10 @@ type Secret struct {
Element PropertyValue
}
type Resource struct {
Urn PropertyValue
}
type ReqError struct {
K PropertyKey
}
@ -189,6 +193,7 @@ func NewObjectProperty(v PropertyMap) PropertyValue { return PropertyValue{v}
func NewComputedProperty(v Computed) PropertyValue { return PropertyValue{v} }
func NewOutputProperty(v Output) PropertyValue { return PropertyValue{v} }
func NewSecretProperty(v *Secret) PropertyValue { return PropertyValue{v} }
func NewResourceProperty(v Resource) PropertyValue { return PropertyValue{v} }
func MakeComputed(v PropertyValue) PropertyValue {
return NewComputedProperty(Computed{Element: v})
@ -202,6 +207,10 @@ func MakeSecret(v PropertyValue) PropertyValue {
return NewSecretProperty(&Secret{Element: v})
}
func MakeResource(v PropertyValue) PropertyValue {
return NewResourceProperty(Resource{Urn: v})
}
// NewPropertyValue turns a value into a property value, provided it is of a legal "JSON-like" kind.
func NewPropertyValue(v interface{}) PropertyValue {
return NewPropertyValueRepl(v, nil, nil)
@ -255,6 +264,8 @@ func NewPropertyValueRepl(v interface{},
return NewOutputProperty(t)
case *Secret:
return NewSecretProperty(t)
case Resource:
return NewResourceProperty(t)
}
// Next, see if it's an array, slice, pointer or struct, and handle each accordingly.
@ -382,6 +393,9 @@ func (v PropertyValue) OutputValue() Output { return v.V.(Output) }
// SecretValue fetches the underlying secret value (panicking if it isn't a secret).
func (v PropertyValue) SecretValue() *Secret { return v.V.(*Secret) }
// ResourceValue fetches the underlying resource value (panicking if it isn't a resource).
func (v PropertyValue) ResourceValue() Resource { return v.V.(Resource) }
// IsNull returns true if the underlying value is a null.
func (v PropertyValue) IsNull() bool {
return v.V == nil
@ -447,6 +461,12 @@ func (v PropertyValue) IsSecret() bool {
return is
}
// IsResource returns true if the underlying value is a resource value.
func (v PropertyValue) IsResource() bool {
_, is := v.V.(Resource)
return is
}
// TypeString returns a type representation of the property value's holder type.
func (v PropertyValue) TypeString() string {
if v.IsNull() {
@ -471,6 +491,8 @@ func (v PropertyValue) TypeString() string {
return "output<" + v.OutputValue().Element.TypeString() + ">"
} else if v.IsSecret() {
return "secret<" + v.SecretValue().Element.TypeString() + ">"
} else if v.IsResource() {
return "resource"
}
contract.Failf("Unrecognized PropertyValue type")
return ""
@ -514,6 +536,8 @@ func (v PropertyValue) MapRepl(replk func(string) (string, bool),
return v.OutputValue()
} else if v.IsSecret() {
return v.SecretValue()
} else if v.IsResource() {
return v.ResourceValue()
}
contract.Assertf(v.IsObject(), "v is not Object '%v' instead", v.TypeString())
return v.ObjectValue().MapRepl(replk, replv)
@ -550,6 +574,9 @@ func HasSig(obj PropertyMap, match string) bool {
// SecretSig is the unique secret signature.
const SecretSig = "1b47061264138c4ac30d75fd1eb44270"
// ResourceSig is the unique resource signature.
const ResourceSig = "5cf8f73096256a8f31e491e813e4eb8e"
// IsInternalPropertyKey returns true if the given property key is an internal key that should not be displayed to
// users.
func IsInternalPropertyKey(key PropertyKey) bool {

View file

@ -29,9 +29,10 @@ import * as asset from "./asset";
import * as dynamic from "./dynamic";
import * as iterable from "./iterable";
import * as log from "./log";
import * as remote from "./remote";
import * as runtime from "./runtime";
import * as utils from "./utils";
export { asset, dynamic, iterable, log, runtime, utils };
export { asset, dynamic, iterable, log, remote, runtime, utils };
// @pulumi is a deployment-only module. If someone tries to capture it, and we fail for some reason
// we want to give a good message about what the problem likely is. Note that capturing a

View file

@ -0,0 +1,62 @@
// GENERATED CODE -- DO NOT EDIT!
// Original file comments:
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
'use strict';
var grpc = require('@grpc/grpc-js');
var runtime_pb = require('./runtime_pb.js');
var plugin_pb = require('./plugin_pb.js');
var google_protobuf_empty_pb = require('google-protobuf/google/protobuf/empty_pb.js');
var google_protobuf_struct_pb = require('google-protobuf/google/protobuf/struct_pb.js');
function serialize_pulumirpc_ConstructRequest(arg) {
if (!(arg instanceof runtime_pb.ConstructRequest)) {
throw new Error('Expected argument of type pulumirpc.ConstructRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_pulumirpc_ConstructRequest(buffer_arg) {
return runtime_pb.ConstructRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_pulumirpc_ConstructResponse(arg) {
if (!(arg instanceof runtime_pb.ConstructResponse)) {
throw new Error('Expected argument of type pulumirpc.ConstructResponse');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_pulumirpc_ConstructResponse(buffer_arg) {
return runtime_pb.ConstructResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
var RuntimeService = exports.RuntimeService = {
construct: {
path: '/pulumirpc.Runtime/Construct',
requestStream: false,
responseStream: false,
requestType: runtime_pb.ConstructRequest,
responseType: runtime_pb.ConstructResponse,
requestSerialize: serialize_pulumirpc_ConstructRequest,
requestDeserialize: deserialize_pulumirpc_ConstructRequest,
responseSerialize: serialize_pulumirpc_ConstructResponse,
responseDeserialize: deserialize_pulumirpc_ConstructResponse,
},
};
exports.RuntimeClient = grpc.makeGenericClientConstructor(RuntimeService);

View file

@ -0,0 +1,508 @@
// source: runtime.proto
/**
* @fileoverview
* @enhanceable
* @suppress {messageConventions} JS Compiler reports an error if a variable or
* field starts with 'MSG_' and isn't a translatable message.
* @public
*/
// GENERATED CODE -- DO NOT EDIT!
var jspb = require('google-protobuf');
var goog = jspb;
var proto = { pulumirpc: {} }, global = proto;
var plugin_pb = require('./plugin_pb.js');
goog.object.extend(proto, plugin_pb);
var google_protobuf_empty_pb = require('google-protobuf/google/protobuf/empty_pb.js');
goog.object.extend(proto, google_protobuf_empty_pb);
var google_protobuf_struct_pb = require('google-protobuf/google/protobuf/struct_pb.js');
goog.object.extend(proto, google_protobuf_struct_pb);
goog.exportSymbol('proto.pulumirpc.ConstructRequest', null, global);
goog.exportSymbol('proto.pulumirpc.ConstructResponse', null, global);
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.pulumirpc.ConstructRequest = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.pulumirpc.ConstructRequest, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.pulumirpc.ConstructRequest.displayName = 'proto.pulumirpc.ConstructRequest';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.pulumirpc.ConstructResponse = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.pulumirpc.ConstructResponse, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.pulumirpc.ConstructResponse.displayName = 'proto.pulumirpc.ConstructResponse';
}
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.pulumirpc.ConstructRequest.prototype.toObject = function(opt_includeInstance) {
return proto.pulumirpc.ConstructRequest.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.pulumirpc.ConstructRequest} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.pulumirpc.ConstructRequest.toObject = function(includeInstance, msg) {
var f, obj = {
librarypath: jspb.Message.getFieldWithDefault(msg, 1, ""),
resource: jspb.Message.getFieldWithDefault(msg, 2, ""),
name: jspb.Message.getFieldWithDefault(msg, 3, ""),
args: (f = msg.getArgs()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f),
opts: (f = msg.getOpts()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f)
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.pulumirpc.ConstructRequest}
*/
proto.pulumirpc.ConstructRequest.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.pulumirpc.ConstructRequest;
return proto.pulumirpc.ConstructRequest.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.pulumirpc.ConstructRequest} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.pulumirpc.ConstructRequest}
*/
proto.pulumirpc.ConstructRequest.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setLibrarypath(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setResource(value);
break;
case 3:
var value = /** @type {string} */ (reader.readString());
msg.setName(value);
break;
case 4:
var value = new google_protobuf_struct_pb.Struct;
reader.readMessage(value,google_protobuf_struct_pb.Struct.deserializeBinaryFromReader);
msg.setArgs(value);
break;
case 5:
var value = new google_protobuf_struct_pb.Struct;
reader.readMessage(value,google_protobuf_struct_pb.Struct.deserializeBinaryFromReader);
msg.setOpts(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.pulumirpc.ConstructRequest.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.pulumirpc.ConstructRequest.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.pulumirpc.ConstructRequest} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.pulumirpc.ConstructRequest.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getLibrarypath();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
f = message.getResource();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
f = message.getName();
if (f.length > 0) {
writer.writeString(
3,
f
);
}
f = message.getArgs();
if (f != null) {
writer.writeMessage(
4,
f,
google_protobuf_struct_pb.Struct.serializeBinaryToWriter
);
}
f = message.getOpts();
if (f != null) {
writer.writeMessage(
5,
f,
google_protobuf_struct_pb.Struct.serializeBinaryToWriter
);
}
};
/**
* optional string libraryPath = 1;
* @return {string}
*/
proto.pulumirpc.ConstructRequest.prototype.getLibrarypath = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.pulumirpc.ConstructRequest} returns this
*/
proto.pulumirpc.ConstructRequest.prototype.setLibrarypath = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
/**
* optional string resource = 2;
* @return {string}
*/
proto.pulumirpc.ConstructRequest.prototype.getResource = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};
/**
* @param {string} value
* @return {!proto.pulumirpc.ConstructRequest} returns this
*/
proto.pulumirpc.ConstructRequest.prototype.setResource = function(value) {
return jspb.Message.setProto3StringField(this, 2, value);
};
/**
* optional string name = 3;
* @return {string}
*/
proto.pulumirpc.ConstructRequest.prototype.getName = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
};
/**
* @param {string} value
* @return {!proto.pulumirpc.ConstructRequest} returns this
*/
proto.pulumirpc.ConstructRequest.prototype.setName = function(value) {
return jspb.Message.setProto3StringField(this, 3, value);
};
/**
* optional google.protobuf.Struct args = 4;
* @return {?proto.google.protobuf.Struct}
*/
proto.pulumirpc.ConstructRequest.prototype.getArgs = function() {
return /** @type{?proto.google.protobuf.Struct} */ (
jspb.Message.getWrapperField(this, google_protobuf_struct_pb.Struct, 4));
};
/**
* @param {?proto.google.protobuf.Struct|undefined} value
* @return {!proto.pulumirpc.ConstructRequest} returns this
*/
proto.pulumirpc.ConstructRequest.prototype.setArgs = function(value) {
return jspb.Message.setWrapperField(this, 4, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.pulumirpc.ConstructRequest} returns this
*/
proto.pulumirpc.ConstructRequest.prototype.clearArgs = function() {
return this.setArgs(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.pulumirpc.ConstructRequest.prototype.hasArgs = function() {
return jspb.Message.getField(this, 4) != null;
};
/**
* optional google.protobuf.Struct opts = 5;
* @return {?proto.google.protobuf.Struct}
*/
proto.pulumirpc.ConstructRequest.prototype.getOpts = function() {
return /** @type{?proto.google.protobuf.Struct} */ (
jspb.Message.getWrapperField(this, google_protobuf_struct_pb.Struct, 5));
};
/**
* @param {?proto.google.protobuf.Struct|undefined} value
* @return {!proto.pulumirpc.ConstructRequest} returns this
*/
proto.pulumirpc.ConstructRequest.prototype.setOpts = function(value) {
return jspb.Message.setWrapperField(this, 5, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.pulumirpc.ConstructRequest} returns this
*/
proto.pulumirpc.ConstructRequest.prototype.clearOpts = function() {
return this.setOpts(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.pulumirpc.ConstructRequest.prototype.hasOpts = function() {
return jspb.Message.getField(this, 5) != null;
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.pulumirpc.ConstructResponse.prototype.toObject = function(opt_includeInstance) {
return proto.pulumirpc.ConstructResponse.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.pulumirpc.ConstructResponse} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.pulumirpc.ConstructResponse.toObject = function(includeInstance, msg) {
var f, obj = {
outs: (f = msg.getOuts()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f)
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.pulumirpc.ConstructResponse}
*/
proto.pulumirpc.ConstructResponse.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.pulumirpc.ConstructResponse;
return proto.pulumirpc.ConstructResponse.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.pulumirpc.ConstructResponse} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.pulumirpc.ConstructResponse}
*/
proto.pulumirpc.ConstructResponse.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = new google_protobuf_struct_pb.Struct;
reader.readMessage(value,google_protobuf_struct_pb.Struct.deserializeBinaryFromReader);
msg.setOuts(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.pulumirpc.ConstructResponse.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.pulumirpc.ConstructResponse.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.pulumirpc.ConstructResponse} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.pulumirpc.ConstructResponse.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getOuts();
if (f != null) {
writer.writeMessage(
1,
f,
google_protobuf_struct_pb.Struct.serializeBinaryToWriter
);
}
};
/**
* optional google.protobuf.Struct outs = 1;
* @return {?proto.google.protobuf.Struct}
*/
proto.pulumirpc.ConstructResponse.prototype.getOuts = function() {
return /** @type{?proto.google.protobuf.Struct} */ (
jspb.Message.getWrapperField(this, google_protobuf_struct_pb.Struct, 1));
};
/**
* @param {?proto.google.protobuf.Struct|undefined} value
* @return {!proto.pulumirpc.ConstructResponse} returns this
*/
proto.pulumirpc.ConstructResponse.prototype.setOuts = function(value) {
return jspb.Message.setWrapperField(this, 1, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.pulumirpc.ConstructResponse} returns this
*/
proto.pulumirpc.ConstructResponse.prototype.clearOuts = function() {
return this.setOuts(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.pulumirpc.ConstructResponse.prototype.hasOuts = function() {
return jspb.Message.getField(this, 1) != null;
};
goog.object.extend(exports, proto.pulumirpc);

View file

@ -0,0 +1,15 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export * from "./proxy";

View file

@ -0,0 +1,47 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as pulumi from "../";
import { getRemoteServer } from "./remoteServer";
/**
* ProxyComponentResource is the abstract base class for proxies around component resources.
*/
export abstract class ProxyComponentResource extends pulumi.ComponentResource {
constructor(
t: string,
name: string,
libraryPath: string,
libraryName: string,
inputs: pulumi.Inputs,
outputs: Record<string, undefined>,
opts: pulumi.ComponentResourceOptions = {}) {
// There are two cases:
// 1. A URN was provided - in this case we are just going to look up the existing resource
// and populate this proxy from that URN.
// 2. A URN was not provided - in this case we are going to remotely construct the resource,
// get the URN from the newly constructed resource, then look it up and populate this
// proxy from that URN.
if (!opts.urn) {
const p = getRemoteServer().construct(libraryPath, libraryName, name, inputs, opts);
const urn = p.then(r => <string>r.urn);
opts = pulumi.mergeOptions(opts, { urn });
}
const props = {
...inputs,
...outputs,
};
super(t, name, props, opts);
}
}

View file

@ -0,0 +1,99 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as grpc from "@grpc/grpc-js";
import * as child_process from "child_process";
import * as readline from "readline";
import * as runtime from "../runtime";
import * as settings from "../runtime/settings";
//tslint:disable
const gstruct = require("google-protobuf/google/protobuf/struct_pb.js");
const runtimeServiceProto = require("../proto/runtime_grpc_pb.js");
const runtimeProto = require("../proto/runtime_pb.js");
let remoteServer: RemoteServer | undefined;
export function getRemoteServer(): RemoteServer {
if (!remoteServer) {
remoteServer = new RemoteServer();
}
return remoteServer;
}
export class RemoteServer {
private readonly client: Promise<any>;
constructor() {
// Spawn a Node.js process to run a remote server.
const subprocess = child_process.spawn(process.execPath, [require.resolve("./server")], {
// Listen to stdout, ignore stdin and stderr.
stdio: ["ignore", "pipe", "ignore"],
env: {
...process.env,
// We overwrite what we inherited from our own environment just in case any of these were
// programmatically set during execution. This is important for example if test mode was enabled
// programatically.
'PULUMI_NODEJS_PROJECT': settings.getProject(),
'PULUMI_NODEJS_STACK': settings.getStack(),
'PULUMI_NODEJS_DRY_RUN': settings.isDryRun() ? "true" : "false",
'PULUMI_NODEJS_QUERY_MODE': settings.isQueryMode() ? "true": "false",
'PULUMI_TEST_MODE': settings.isTestModeEnabled() ? "true" : "false",
}
});
// Ensure we can exit the current process without waiting on the VM server process to exit.
subprocess.unref(); // do not track subprocess on our event loop
const reader = readline.createInterface(subprocess.stdout!);
this.client = new Promise((resolve, reject) => {
reader.once('line', port => {
try {
// Tear down our piped stdout stream to that we do not hold the event loop open.
subprocess.stdout?.destroy();
// Connect to the process' gRPC server on the provided port.
const client = new runtimeServiceProto.RuntimeClient(
`0.0.0.0:${port}`,
grpc.credentials.createInsecure()
);
console.log("created client: " + JSON.stringify(client));
resolve(client);
} catch (err) {
reject(err);
}
});
});
}
public async construct(libraryPath: string, resource: string, name: string, args: any, opts?: any): Promise<any> {
const serializedArgs = await runtime.serializeProperties("construct-args", args, { keepResources: true });
const argsStruct = gstruct.Struct.fromJavaScript(serializedArgs);
const serializedOpts = await runtime.serializeProperties("construct-opts", opts, { keepResources: true });
const optsStruct = gstruct.Struct.fromJavaScript(serializedOpts);
const client = await this.client;
const constructRequest = new runtimeProto.ConstructRequest();
constructRequest.setLibrarypath(libraryPath);
constructRequest.setResource(resource);
constructRequest.setName(name);
constructRequest.setArgs(argsStruct);
constructRequest.setOpts(optsStruct);
const outsStruct = await new Promise<any>((resolve, reject) => {
client.construct(constructRequest, (err: Error, resp: any) => {
if (err) {
reject(err);
} else {
resolve(resp.getOuts());
}
});
});
const outs = await runtime.deserializeProperties(outsStruct);
return outs;
}
}

View file

@ -0,0 +1,52 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as grpc from "@grpc/grpc-js";
import * as runtime from "../../runtime";
//tslint:disable
const runtimeServiceProto = require("../../proto/runtime_grpc_pb.js");
const runtimeProto = require("../../proto/runtime_pb.js");
const gstruct = require("google-protobuf/google/protobuf/struct_pb.js");
const server = new grpc.Server();
server.addService(runtimeServiceProto.RuntimeService, {
construct: construct,
});
const port = server.bind("0.0.0.0:0", grpc.ServerCredentials.createInsecure());
server.start();
process.stdout.write(`${port}\n`);
function construct(call: any, callback: (err: any, resp?: any) => void) {
try {
const library = require(call.request.getLibrarypath())
const props = runtime.deserializeProperties(call.request.getArgs());
const opts = runtime.deserializeProperties(call.request.getOpts());
const resource = call.request.getResource();
const name = call.request.getName();
const ctor = library[resource];
const res = new ctor(name, props, opts);
runtime.serializeProperties("inner-construct", res, { keepResources: true }).then(resolved => {
const outStruct = gstruct.Struct.fromJavaScript(resolved);
const reply = new runtimeProto.ConstructResponse();
reply.setOuts(outStruct);
callback(null, reply);
}).catch(err => callback(err));
} catch (err) {
callback(err);
}
}

View file

@ -15,7 +15,7 @@
import { ResourceError } from "./errors";
import { Input, Inputs, interpolate, Output, output } from "./output";
import { getStackResource, unknownValue } from "./runtime";
import { readResource, registerResource, registerResourceOutputs } from "./runtime/resource";
import { getResource, readResource, registerResource, registerResourceOutputs } from "./runtime/resource";
import { getProject, getStack } from "./runtime/settings";
import * as utils from "./utils";
@ -174,6 +174,12 @@ export abstract class Resource {
// tslint:disable-next-line:variable-name
private readonly __name?: string;
/* @internal Whether or not this resources contruction registered a resource with the Pulumi
* engine. If false, it is not safe to `registerResourceOutputs` on this resource.
*/
// tslint:disable-next-line:variable-name
readonly __wasRegistered?: boolean;
/**
* The set of providers to use for child resources. Keyed by package name (e.g. "aws").
* @internal
@ -219,6 +225,9 @@ export abstract class Resource {
if (!name) {
throw new ResourceError("Missing resource name argument (for URN creation)", opts.parent);
}
if (opts.id && opts.urn) {
throw new ResourceError(`Resource options specified mututally exlusive 'id' and 'urn' options`, opts.parent);
}
// Before anything else - if there are transformations registered, invoke them in order to transform the properties and
// options assigned to this resource.
@ -313,19 +322,27 @@ export abstract class Resource {
}
}
if (opts.id) {
if (opts.urn) {
// Assume that the resource has already been registered by another piece of code, and
// populate this resource object with the state of that resource as retrieved from the
// engine.
getResource(this, t, name, custom, props, opts);
this.__wasRegistered = false;
} else if (opts.id) {
// If this resource already exists, read its state rather than registering it anew.
if (!custom) {
throw new ResourceError(
"Cannot read an existing resource unless it has a custom provider", opts.parent);
}
readResource(this, t, name, props, opts);
this.__wasRegistered = false;
} else {
// Kick off the resource registration. If we are actually performing a deployment, this
// resource's properties will be resolved asynchronously after the operation completes, so
// that dependent computations resolve normally. If we are just planning, on the other
// hand, values will never resolve.
registerResource(this, t, name, custom, props, opts);
this.__wasRegistered = true;
}
}
}
@ -458,9 +475,13 @@ export interface ResourceOptions {
// that mergeOptions works properly for it.
/**
* An optional existing ID to load, rather than create.
* An optional existing ID to load, rather than create. At most one of `id` and `urn` can be set.
*/
id?: Input<ID>;
/**
* An optional existing URN to load, rather than create. At most one of `urn` and `id` can be set.
*/
urn?: Input<URN>;
/**
* An optional parent resource to which this resource belongs.
*/
@ -757,7 +778,7 @@ export class ComponentResource<TData = any> extends Resource {
/** @internal */
// tslint:disable-next-line:variable-name
private __registered = false;
private __registeredOutputs = false;
/**
* Returns true if the given object is an instance of CustomResource. This is designed to work even when
@ -780,22 +801,22 @@ 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();
if (this.__wasRegistered) {
// If the resource construction was registered with the engine (that is, unless `urn` or
// `id` opts were passed), we will automatically "finalize" the resource registration by
// calling `registerOutputs`. If the user already called this in the constructor, their
// outputs will win (due to the `await` above causing a delay of a turn on the event
// loop) and this will be a no-op. If they did not, we will invoke this on the next
// turn after the (potentially overriden) `initialize` returns.
this.registerOutputs();
}
return data;
}
@ -825,11 +846,11 @@ export class ComponentResource<TData = any> extends Resource {
* called after the `initialize` method completes.
*/
protected registerOutputs(outputs?: Inputs | Promise<Inputs> | Output<Inputs>): void {
if (this.__registered) {
if (this.__registeredOutputs) {
return;
}
this.__registered = true;
this.__registeredOutputs = true;
registerResourceOutputs(this, outputs || {});
}
}

View file

@ -154,6 +154,50 @@ export function readResource(res: Resource, t: string, name: string, props: Inpu
}), label);
}
// The shape of data returned by the built-in invoke to `pulumi:pulumi:readStackResource`.
interface StackResourceResult {
urn: string;
// The output properties of the resource represented by `urn` as registered in the Pulumi
// engine.
outputs: Record<string, any>;
}
/**
* Gets an existing custom resource's state from the engine. Assumes that the resource has already
* been registered by some other code, and looks it up by URN.
*/
export function getResource(res: Resource, t: string, name: string, custom: boolean, props: Inputs, opts: ResourceOptions): void {
const urn: Input<URN> | undefined = opts.urn;
if (!urn) {
throw new Error("Cannot get resource whose options are lacking a URN value");
}
const label = `resource:${name}[${t}]#...`;
log.debug(`Getting resource: id=${Output.isInstance(urn) ? "Output<T>" : urn}, t=${t}, name=${name}`);
const monitor = getMonitor();
const resopAsync = prepareResource(label, res, custom, props, opts);
debuggablePromise(resopAsync.then(async (resop) => {
const resolvedURN = await serializeProperty(label, urn, new Set());
log.debug(`GetResource RPC prepared: id=${resolvedURN}, t=${t}, name=${name}` +
(excessiveDebugOutput ? `, obj=${JSON.stringify(resop.serializedProps)}` : ``));
const result: StackResourceResult = await invoke("pulumi:pulumi:readStackResource", {
urn: resolvedURN,
}, { async: true });
// Now resolve everything: the URN, the ID (if the resource is a `CustomResource`), and the
// output properties.
resop.resolveURN(resolvedURN);
if (custom) {
resop.resolveID!(result.outputs.id, result.outputs.id !== undefined);
}
resolveProperties(res, resop.resolvers, t, name, result.outputs);
}), label);
}
/**
* registerResource registers a new resource object with a given type t and name. It returns the auto-generated
* URN and the ID that will resolve after the deployment has completed. All properties will be initialized to property
@ -514,7 +558,7 @@ export function registerResourceOutputs(res: Resource, outputs: Inputs | Promise
// The registration could very well still be taking place, so we will need to wait for its URN.
// Additionally, the output properties might have come from other resources, so we must await those too.
const urn = await res.urn.promise();
const resolved = await serializeProperties(opLabel, { outputs });
const resolved = await serializeProperties(opLabel, { outputs }, { keepResources: true });
const outputsObj = gstruct.Struct.fromJavaScript(resolved.outputs);
log.debug(`RegisterResourceOutputs RPC prepared: urn=${urn}` +
(excessiveDebugOutput ? `, outputs=${JSON.stringify(outputsObj)}` : ``));

View file

@ -82,7 +82,11 @@ export function transferProperties(onto: Resource, label: string, props: Inputs)
* be remoted over to registerResource.
*/
async function serializeFilteredProperties(
label: string, props: Inputs, acceptKey: (k: string) => boolean): Promise<[Record<string, any>, Map<string, Set<Resource>>]> {
label: string,
props: Inputs,
acceptKey: (k: string) => boolean,
opts?: SerializeOptions,
): Promise<[Record<string, any>, Map<string, Set<Resource>>]> {
const propertyToDependentResources = new Map<string, Set<Resource>>();
@ -91,7 +95,7 @@ async function serializeFilteredProperties(
if (acceptKey(k)) {
// We treat properties with undefined values as if they do not exist.
const dependentResources = new Set<Resource>();
const v = await serializeProperty(`${label}.${k}`, props[k], dependentResources);
const v = await serializeProperty(`${label}.${k}`, props[k], dependentResources, opts);
if (v !== undefined) {
result[k] = v;
propertyToDependentResources.set(k, dependentResources);
@ -110,12 +114,24 @@ export async function serializeResourceProperties(label: string, props: Inputs)
return serializeFilteredProperties(label, props, key => key !== "id" && key !== "urn");
}
/**
* SerializeOptions represents options that can be passed to a property serialization function.
*/
export interface SerializeOptions {
/**
* keepResources indicates that resource references should be maintained as strongly typed
* `Resource` instances instead of erased down to `id` (for CustomResources) or `urn` (for
* `ComponetResources`).
*/
keepResources?: boolean;
}
/**
* serializeProperties walks the props object passed in, awaiting all interior promises, creating a reasonable
* POJO object that can be remoted over to registerResource.
*/
export async function serializeProperties(label: string, props: Inputs) {
const [result] = await serializeFilteredProperties(label, props, _ => true);
export async function serializeProperties(label: string, props: Inputs, opts?: SerializeOptions) {
const [result] = await serializeFilteredProperties(label, props, _ => true, opts);
return result;
}
@ -222,14 +238,18 @@ export const specialArchiveSig = "0def7320c3a5731c473e5ecbe6d01bc7";
* specialSecretSig is a randomly assigned hash used to identify secrets in maps. See pkg/resource/properties.go.
*/
export const specialSecretSig = "1b47061264138c4ac30d75fd1eb44270";
/**
* specialResourceSig is a randomly assigned hash used to identify resources in maps. See pkg/resource/properties.go.
*/
export const specialResourceSig = "5cf8f73096256a8f31e491e813e4eb8e";
/**
* serializeProperty serializes properties deeply. This understands how to wait on any unresolved promises, as
* appropriate, in addition to translating certain "special" values so that they are ready to go on the wire.
*/
export async function serializeProperty(ctx: string, prop: Input<any>, dependentResources: Set<Resource>): Promise<any> {
export async function serializeProperty(ctx: string, prop: Input<any>, dependentResources: Set<Resource>, opts: SerializeOptions = {}): Promise<any> {
// IMPORTANT:
// IMPORTANT: Keep this in sync with serializesPropertiesSync in invoke.ts
// IMPORTANT: Keep this in sync with serializePropertiesSync in invoke.ts
// IMPORTANT:
if (prop === undefined ||
@ -262,7 +282,7 @@ export async function serializeProperty(ctx: string, prop: Input<any>, dependent
const subctx = `Promise<${ctx}>`;
return serializeProperty(subctx,
await debuggablePromise(prop, `serializeProperty.await(${subctx})`), dependentResources);
await debuggablePromise(prop, `serializeProperty.await(${subctx})`), dependentResources, opts);
}
if (Output.isInstance(prop)) {
@ -289,7 +309,7 @@ export async function serializeProperty(ctx: string, prop: Input<any>, dependent
// which will wrap undefined, if it were to be resolved (since `Output` has no member named .isSecret).
// so we must compare to the literal true instead of just doing await prop.isSecret.
const isSecret = await prop.isSecret === true;
const value = await serializeProperty(`${ctx}.id`, prop.promise(), dependentResources);
const value = await serializeProperty(`${ctx}.id`, prop.promise(), dependentResources, opts);
if (!isKnown) {
return unknownValue;
@ -309,13 +329,21 @@ export async function serializeProperty(ctx: string, prop: Input<any>, dependent
}
if (CustomResource.isInstance(prop)) {
// Resources aren't serializable; instead, we serialize them as references to the ID property.
if (excessiveDebugOutput) {
log.debug(`Serialize property [${ctx}]: custom resource id`);
log.debug(`Serialize property [${ctx}]: custom resource urn`);
}
dependentResources.add(prop);
return serializeProperty(`${ctx}.id`, prop.id, dependentResources);
if (opts.keepResources) {
// If we are keeping resources, emit a stronly typed wrapper over the URN
const urn = await serializeProperty(`${ctx}.urn`, prop.urn, dependentResources, opts);
return {
[specialSigKey]: specialResourceSig,
urn: urn,
};
}
// Else, return the id for backward compatibility.
return serializeProperty(`${ctx}.id`, prop.id, dependentResources, opts);
}
if (ComponentResource.isInstance(prop)) {
@ -334,10 +362,19 @@ export async function serializeProperty(ctx: string, prop: Input<any>, dependent
// and tracked in a reasonable manner, while not causing us to compute or embed information
// about it that is not needed, and which can lead to deadlocks.
if (excessiveDebugOutput) {
log.debug(`Serialize property [${ctx}]: component resource urnid`);
log.debug(`Serialize property [${ctx}]: component resource urn`);
}
return serializeProperty(`${ctx}.urn`, prop.urn, dependentResources);
if (opts.keepResources) {
// If we are keeping resources, emit a stronly typed wrapper over the URN
const urn = await serializeProperty(`${ctx}.urn`, prop.urn, dependentResources, opts);
return {
[specialSigKey]: specialResourceSig,
urn: urn,
};
}
// Else, return the urn for backward compatibility.
return serializeProperty(`${ctx}.urn`, prop.urn, dependentResources, opts);
}
if (prop instanceof Array) {
@ -347,7 +384,7 @@ export async function serializeProperty(ctx: string, prop: Input<any>, dependent
log.debug(`Serialize property [${ctx}]: array[${i}] element`);
}
// When serializing arrays, we serialize any undefined values as `null`. This matches JSON semantics.
const elem = await serializeProperty(`${ctx}[${i}]`, prop[i], dependentResources);
const elem = await serializeProperty(`${ctx}[${i}]`, prop[i], dependentResources, opts);
result.push(elem === undefined ? null : elem);
}
return result;
@ -362,7 +399,7 @@ export async function serializeProperty(ctx: string, prop: Input<any>, dependent
}
// When serializing an object, we omit any keys with undefined values. This matches JSON semantics.
const v = await serializeProperty(`${ctx}.${k}`, innerProp[k], dependentResources);
const v = await serializeProperty(`${ctx}.${k}`, innerProp[k], dependentResources, opts);
if (v !== undefined) {
obj[k] = v;
}
@ -468,6 +505,18 @@ export function deserializeProperty(prop: any): any {
[specialSigKey]: specialSecretSig,
value: deserializeProperty(prop["value"]),
};
case specialResourceSig:
// Deserialize the resource into a live Resource reference
const urn = prop["urn"];
const urnParts = urn.split("::");
const qualifiedType = urnParts[2];
const type = qualifiedType.split("$").pop()!;
const proxyConstructor = proxyConstructors.get(type);
if (!proxyConstructor) {
throw new Error(`Unable to deserialize resource URN ${urn}, no proxy constructor is registered for type ${type}.`);
}
const urnName = urnParts[3];
return new proxyConstructor(urnName, {}, { urn });
default:
throw new Error(`Unrecognized signature '${sig}' when unmarshaling resource property`);
}
@ -494,3 +543,20 @@ export function deserializeProperty(prop: any): any {
return obj;
}
}
type ProxyConstructor = {
new(name: string, args: any, opts: { urn: string }): Resource;
};
const proxyConstructors = new Map<string, ProxyConstructor>();
/**
* registerProxyConstructor registers a constructor to be used as a proxy for any URNs matching the
* given type that are deserialized by the current instance of the Pulumi JavaScript SDK.
*/
export function registerProxyConstructor(type: string, constructor: ProxyConstructor) {
const existing = proxyConstructors.get(type);
if (existing) {
throw new Error(`Cannot re-register type ${type} as a proxy. Previous registration was ${existing}, new registration was ${constructor}.`);
}
proxyConstructors.set(type, constructor);
}

View file

@ -55,10 +55,13 @@ class Stack extends ComponentResource<Inputs> {
*/
public readonly outputs: Output<Inputs>;
private _init: () => Promise<Inputs>;
constructor(init: () => Promise<Inputs>) {
super(rootPulumiStackTypeName, `${getProject()}-${getStack()}`, { init });
super(rootPulumiStackTypeName, `${getProject()}-${getStack()}`);
const data = this.getData();
this.outputs = output(data);
this._init = init;
}
/**
@ -67,7 +70,7 @@ class Stack extends ComponentResource<Inputs> {
*
* @param init The callback to run in the context of this Pulumi stack
*/
async initialize(args: { init: () => Promise<Inputs> }): Promise<Inputs> {
async initialize(): Promise<Inputs> {
const parent = await getRootResource();
if (parent) {
throw new Error("Only one root Pulumi Stack may be active at once");
@ -79,7 +82,7 @@ class Stack extends ComponentResource<Inputs> {
let outputs: Inputs | undefined;
try {
const inputs = await args.init();
const inputs = await this._init();
outputs = await massage(inputs, []);
} finally {
// We want to expose stack outputs as simple pojo objects (including Resources). This

View file

@ -0,0 +1,26 @@
// Copyright 2016-2020, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ComponentResource, ComponentResourceOptions, Output, output } from "../../";
export class MyComponent extends ComponentResource {
output: Output<string>;
constructor(name: string, args: any, opts: ComponentResourceOptions) {
super("my:mod:MyComponent", name, {}, opts);
this.output = output(args.input as string);
this.registerOutputs({
output: this.output,
});
}
}

View file

@ -0,0 +1,44 @@
// Copyright 2016-2020, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// import * as assert from "assert";
// import { ComponentResourceOptions, Output } from "../../";
// import { ProxyComponentResource } from "../../remote";
import * as assert from "assert";
import { getRemoteServer, RemoteServer } from "../../remote/remoteServer";
import * as runtime from "../../runtime";
describe("remote invocation", () => {
before(() => {
runtime._setTestModeEnabled(true);
runtime._setProject("myproject");
runtime._setStack("mystack");
});
after(() => {
runtime._setTestModeEnabled(false);
runtime._setProject(undefined);
runtime._setStack(undefined);
});
it("can construct a simple component", async () => {
const server = getRemoteServer();
const path = require.resolve("./component");
console.log(path);
const res = await server.construct(require.resolve("./component"), "MyComponent", "res", { input: "hello", output: undefined }, {});
assert.strictEqual("hello", res.output);
});
});

View file

@ -35,6 +35,11 @@
"log/index.ts",
"remote/index.ts",
"remote/proxy.ts",
"remote/remoteServer.ts",
"remote/server/index.ts",
"runtime/index.ts",
"runtime/closure/codePaths.ts",
@ -76,7 +81,9 @@
"tests/runtime/closureLoader.spec.ts",
"tests/runtime/tsClosureCases.ts",
"tests/runtime/props.spec.ts",
"tests/runtime/langhost/run.spec.ts"
"tests/runtime/langhost/run.spec.ts",
"tests/remote/proxycomponentresource.spec.ts",
"tests/remote/component.ts"
]
}

244
sdk/proto/go/runtime.pb.go Normal file
View file

@ -0,0 +1,244 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: runtime.proto
package pulumirpc
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
_ "github.com/golang/protobuf/ptypes/empty"
_struct "github.com/golang/protobuf/ptypes/struct"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type ConstructRequest struct {
LibraryPath string `protobuf:"bytes,1,opt,name=libraryPath,proto3" json:"libraryPath,omitempty"`
Resource string `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"`
Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
Args *_struct.Struct `protobuf:"bytes,4,opt,name=args,proto3" json:"args,omitempty"`
Opts *_struct.Struct `protobuf:"bytes,5,opt,name=opts,proto3" json:"opts,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ConstructRequest) Reset() { *m = ConstructRequest{} }
func (m *ConstructRequest) String() string { return proto.CompactTextString(m) }
func (*ConstructRequest) ProtoMessage() {}
func (*ConstructRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_86e2dd377c869464, []int{0}
}
func (m *ConstructRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ConstructRequest.Unmarshal(m, b)
}
func (m *ConstructRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ConstructRequest.Marshal(b, m, deterministic)
}
func (m *ConstructRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ConstructRequest.Merge(m, src)
}
func (m *ConstructRequest) XXX_Size() int {
return xxx_messageInfo_ConstructRequest.Size(m)
}
func (m *ConstructRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ConstructRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ConstructRequest proto.InternalMessageInfo
func (m *ConstructRequest) GetLibraryPath() string {
if m != nil {
return m.LibraryPath
}
return ""
}
func (m *ConstructRequest) GetResource() string {
if m != nil {
return m.Resource
}
return ""
}
func (m *ConstructRequest) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *ConstructRequest) GetArgs() *_struct.Struct {
if m != nil {
return m.Args
}
return nil
}
func (m *ConstructRequest) GetOpts() *_struct.Struct {
if m != nil {
return m.Opts
}
return nil
}
type ConstructResponse struct {
Outs *_struct.Struct `protobuf:"bytes,1,opt,name=outs,proto3" json:"outs,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ConstructResponse) Reset() { *m = ConstructResponse{} }
func (m *ConstructResponse) String() string { return proto.CompactTextString(m) }
func (*ConstructResponse) ProtoMessage() {}
func (*ConstructResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_86e2dd377c869464, []int{1}
}
func (m *ConstructResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ConstructResponse.Unmarshal(m, b)
}
func (m *ConstructResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ConstructResponse.Marshal(b, m, deterministic)
}
func (m *ConstructResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ConstructResponse.Merge(m, src)
}
func (m *ConstructResponse) XXX_Size() int {
return xxx_messageInfo_ConstructResponse.Size(m)
}
func (m *ConstructResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ConstructResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ConstructResponse proto.InternalMessageInfo
func (m *ConstructResponse) GetOuts() *_struct.Struct {
if m != nil {
return m.Outs
}
return nil
}
func init() {
proto.RegisterType((*ConstructRequest)(nil), "pulumirpc.ConstructRequest")
proto.RegisterType((*ConstructResponse)(nil), "pulumirpc.ConstructResponse")
}
func init() { proto.RegisterFile("runtime.proto", fileDescriptor_86e2dd377c869464) }
var fileDescriptor_86e2dd377c869464 = []byte{
// 256 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x90, 0xcd, 0x4a, 0xc4, 0x30,
0x1c, 0xc4, 0xad, 0xd6, 0x8f, 0x66, 0x15, 0x34, 0x17, 0x43, 0x77, 0x0f, 0xa5, 0xa7, 0x05, 0x21,
0x0b, 0xeb, 0x0b, 0x08, 0x5e, 0x3c, 0x4a, 0xf7, 0x09, 0xda, 0x12, 0x6b, 0xa1, 0x4d, 0xe2, 0xff,
0xe3, 0xb0, 0x2f, 0xe7, 0xb3, 0xc9, 0x26, 0x5a, 0x96, 0x22, 0x7a, 0x4b, 0xe6, 0x37, 0xfc, 0x67,
0x18, 0x71, 0x03, 0x6c, 0xa9, 0x1f, 0x8d, 0xf6, 0xe0, 0xc8, 0xc9, 0xcc, 0xf3, 0xc0, 0x63, 0x0f,
0xbe, 0xcd, 0xaf, 0xfd, 0xc0, 0x5d, 0x6f, 0x23, 0xc8, 0x97, 0x9d, 0x73, 0xdd, 0x60, 0x36, 0xe1,
0xd7, 0xf0, 0xdb, 0xc6, 0x8c, 0x9e, 0xf6, 0xdf, 0x70, 0x35, 0x87, 0x48, 0xc0, 0x2d, 0x45, 0x5a,
0x7e, 0x26, 0xe2, 0xf6, 0xd9, 0xd9, 0xa8, 0x55, 0xe6, 0x83, 0x0d, 0x92, 0x2c, 0xc4, 0x62, 0xe8,
0x1b, 0xa8, 0x61, 0xff, 0x5a, 0xd3, 0xbb, 0x4a, 0x8a, 0x64, 0x9d, 0x55, 0xc7, 0x92, 0xcc, 0xc5,
0x15, 0x18, 0x74, 0x0c, 0xad, 0x51, 0xa7, 0x01, 0x4f, 0x7f, 0x29, 0x45, 0x6a, 0xeb, 0xd1, 0xa8,
0xb3, 0xa0, 0x87, 0xb7, 0x7c, 0x10, 0x69, 0x0d, 0x1d, 0xaa, 0xb4, 0x48, 0xd6, 0x8b, 0xed, 0xbd,
0x8e, 0x9d, 0xf4, 0x4f, 0x27, 0xbd, 0x8b, 0xf9, 0xc1, 0x74, 0x30, 0x3b, 0x4f, 0xa8, 0xce, 0xff,
0x31, 0x1f, 0x4c, 0xe5, 0x93, 0xb8, 0x3b, 0xea, 0x8f, 0xde, 0x59, 0x0c, 0x71, 0x8e, 0x09, 0x43,
0xf3, 0x3f, 0x2f, 0x30, 0xe1, 0x76, 0x27, 0x2e, 0xab, 0xb8, 0xb3, 0x7c, 0x11, 0xd9, 0x74, 0x4c,
0x2e, 0xf5, 0xb4, 0xb7, 0x9e, 0x4f, 0x94, 0xaf, 0x7e, 0x87, 0x31, 0xbf, 0x3c, 0x69, 0x2e, 0x42,
0xd6, 0xe3, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x96, 0x81, 0xd7, 0xc9, 0xc3, 0x01, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6
// RuntimeClient is the client API for Runtime service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type RuntimeClient interface {
Construct(ctx context.Context, in *ConstructRequest, opts ...grpc.CallOption) (*ConstructResponse, error)
}
type runtimeClient struct {
cc grpc.ClientConnInterface
}
func NewRuntimeClient(cc grpc.ClientConnInterface) RuntimeClient {
return &runtimeClient{cc}
}
func (c *runtimeClient) Construct(ctx context.Context, in *ConstructRequest, opts ...grpc.CallOption) (*ConstructResponse, error) {
out := new(ConstructResponse)
err := c.cc.Invoke(ctx, "/pulumirpc.Runtime/Construct", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// RuntimeServer is the server API for Runtime service.
type RuntimeServer interface {
Construct(context.Context, *ConstructRequest) (*ConstructResponse, error)
}
// UnimplementedRuntimeServer can be embedded to have forward compatible implementations.
type UnimplementedRuntimeServer struct {
}
func (*UnimplementedRuntimeServer) Construct(ctx context.Context, req *ConstructRequest) (*ConstructResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Construct not implemented")
}
func RegisterRuntimeServer(s *grpc.Server, srv RuntimeServer) {
s.RegisterService(&_Runtime_serviceDesc, srv)
}
func _Runtime_Construct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ConstructRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RuntimeServer).Construct(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/pulumirpc.Runtime/Construct",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RuntimeServer).Construct(ctx, req.(*ConstructRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Runtime_serviceDesc = grpc.ServiceDesc{
ServiceName: "pulumirpc.Runtime",
HandlerType: (*RuntimeServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Construct",
Handler: _Runtime_Construct_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "runtime.proto",
}

38
sdk/proto/runtime.proto Normal file
View file

@ -0,0 +1,38 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
import "plugin.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/struct.proto";
package pulumirpc;
service Runtime {
rpc Construct(ConstructRequest) returns (ConstructResponse) {}
// TODO: Add an Invoke to allow arbitary function execution
}
message ConstructRequest {
string libraryPath = 1;
string resource = 2;
string name = 3;
google.protobuf.Struct args = 4;
google.protobuf.Struct opts = 5;
}
message ConstructResponse {
google.protobuf.Struct outs = 1;
}

View file

@ -0,0 +1,21 @@
# Copyright 2016-2018, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
The remote proxy implementation of the Pulumi Python SDK.
"""
from .remote import (
ProxyComponentResource,
)

View file

@ -0,0 +1,105 @@
# Copyright 2016-2018, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
import os
from subprocess import Popen, PIPE
import time
from typing import Callable, Any, Dict, List, Optional
import grpc
from .. import ComponentResource, CustomResource, Output, InvokeOptions, ResourceOptions, log, Input, Inputs, Resource
from ..runtime.proto import runtime_pb2, runtime_pb2_grpc
from ..runtime.rpc import deserialize_properties, serialize_properties
from ..runtime.settings import SETTINGS
def spawnServer(library_path: str):
def setting_to_string(setting: Optional[str]) -> str:
return "" if setting is None else setting
proc = Popen(["node", "-e", "require('@pulumi/pulumi/remote/server')"], cwd=library_path, stdout=PIPE, env={
**os.environ,
'PULUMI_NODEJS_PROJECT': setting_to_string(SETTINGS.project),
'PULUMI_NODEJS_STACK': setting_to_string(SETTINGS.stack),
'PULUMI_NODEJS_DRY_RUN': "true" if SETTINGS.dry_run else "false",
'PULUMI_NODEJS_QUERY_MODE': "false",
'PULUMI_NODEJS_MONITOR': setting_to_string(SETTINGS.monitor_addr),
'PULUMI_NODEJS_ENGINE': setting_to_string(SETTINGS.engine_addr),
'PULUMI_TEST_MODE': "true" if SETTINGS.test_mode_enabled else "false",
'PULUMI_ENABLE_LEGACY_APPLY': "false",
'PULUMI_NODEJS_SYNC': "false",
'PULUMI_NODEJS_PARALLEL': "true",
})
port = proc.stdout.readline().decode()[:-1]
proc.stdout.close()
channel = grpc.insecure_channel(f'0.0.0.0:{port}')
stub = runtime_pb2_grpc.RuntimeStub(channel)
return stub
stubs: Dict[str, runtime_pb2_grpc.RuntimeStub] = dict()
def get_server(library_path: str) -> runtime_pb2_grpc.RuntimeStub:
stub = stubs.get(library_path, None)
if stub is None:
stub = spawnServer(library_path)
stubs[library_path] = stub
return stub
# def resource_options_to_dict(opts: ResourceOptions) -> Inputs:
# d = vars(opts)
# d.pop("merge", None)
# return d
async def construct(
libraryPath: str,
resource: str,
name: str,
args: Any,
_opts: ResourceOptions) -> Any:
property_dependencies_resources: Dict[str, List[Resource]] = {}
args_struct = await serialize_properties(args, property_dependencies_resources)
# TODO - support opts serialization
opts_struct = await serialize_properties({}, property_dependencies_resources)
req = runtime_pb2.ConstructRequest(
libraryPath=libraryPath,
resource=resource,
name=name,
args=args_struct,
opts=opts_struct,
)
resp = get_server(libraryPath).Construct(req)
outs = deserialize_properties(resp.outs)
return outs
class ProxyComponentResource(ComponentResource):
"""
Abstract base class for proxies around component resources.
"""
def __init__(self,
t: str,
name: str,
library_path: str,
library_name: str,
inputs: Inputs,
outputs: Dict[str, None],
opts: Optional[ResourceOptions] = None) -> None:
if opts is None or opts.urn is None:
async def do_construct():
r = await construct(library_path, library_name, name, inputs, opts)
return r["urn"]
urn = asyncio.ensure_future(do_construct())
opts = ResourceOptions.merge(opts, ResourceOptions(urn=urn))
props = {
**inputs,
**outputs,
}
super().__init__(t, name, props, opts)

View file

@ -18,7 +18,7 @@ from typing import Optional, List, Any, Mapping, Union, Callable, TYPE_CHECKING,
import copy
from .runtime import known_types
from .runtime.resource import _register_resource, register_resource_outputs, _read_resource
from .runtime.resource import _register_resource, register_resource_outputs, _read_resource, _get_resource
from .runtime.settings import get_root_resource
from .metadata import get_project, get_stack
@ -365,6 +365,11 @@ class ResourceOptions:
An optional existing ID to load, rather than create.
"""
urn: Optional['Input[str]']
"""
An optional existing URN to load, rather than create.
"""
import_: Optional[str]
"""
When provided with a resource ID, import indicates that this resource's provider should import
@ -386,6 +391,7 @@ class ResourceOptions:
aliases: Optional[List['Input[Union[str, Alias]]']] = None,
additional_secret_outputs: Optional[List[str]] = None,
id: Optional['Input[str]'] = None,
urn: Optional['Input[str]'] = None,
import_: Optional[str] = None,
custom_timeouts: Optional['CustomTimeouts'] = None,
transformations: Optional[List[ResourceTransformation]] = None) -> None:
@ -432,6 +438,7 @@ class ResourceOptions:
self.additional_secret_outputs = additional_secret_outputs
self.custom_timeouts = custom_timeouts
self.id = id
self.urn = urn
self.import_ = import_
self.transformations = transformations
@ -503,6 +510,7 @@ class ResourceOptions:
dest.version = dest.version if source.version is None else source.version
dest.custom_timeouts = dest.custom_timeouts if source.custom_timeouts is None else source.custom_timeouts
dest.id = dest.id if source.id is None else source.id
dest.urn = dest.urn if source.urn is None else source.urn
dest.import_ = dest.import_ if source.import_ is None else source.import_
# Now, if we are left with a .providers that is just a single key/value pair, then
@ -704,7 +712,17 @@ class Resource:
self._aliases.append(collapse_alias_to_urn(
alias, name, t, opts.parent))
if opts.id is not None:
if opts.urn is not None:
# Assume that the resource has already been registered by another piece of code, and
# populate this resource object with the state of that resource as retrieved from the
# engine.
result = _get_resource(self, t, name, custom, props, opts)
res.urn = result.urn
if custom:
assert result.id is not None
res = cast('CustomResource', res)
res.id = result.id
elif opts.id is not None:
# If this resource already exists, read its state rather than registering it anew.
if not custom:
raise Exception(

View file

@ -44,3 +44,7 @@ from .stack import (
from .invoke import (
invoke,
)
from .rpc import (
register_proxy_constructor,
)

View file

@ -0,0 +1,166 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: runtime.proto
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
from . import plugin_pb2 as plugin__pb2
from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2
DESCRIPTOR = _descriptor.FileDescriptor(
name='runtime.proto',
package='pulumirpc',
syntax='proto3',
serialized_options=None,
serialized_pb=b'\n\rruntime.proto\x12\tpulumirpc\x1a\x0cplugin.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\"\x95\x01\n\x10\x43onstructRequest\x12\x13\n\x0blibraryPath\x18\x01 \x01(\t\x12\x10\n\x08resource\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12%\n\x04\x61rgs\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\x12%\n\x04opts\x18\x05 \x01(\x0b\x32\x17.google.protobuf.Struct\":\n\x11\x43onstructResponse\x12%\n\x04outs\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct2S\n\x07Runtime\x12H\n\tConstruct\x12\x1b.pulumirpc.ConstructRequest\x1a\x1c.pulumirpc.ConstructResponse\"\x00\x62\x06proto3'
,
dependencies=[plugin__pb2.DESCRIPTOR,google_dot_protobuf_dot_empty__pb2.DESCRIPTOR,google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,])
_CONSTRUCTREQUEST = _descriptor.Descriptor(
name='ConstructRequest',
full_name='pulumirpc.ConstructRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='libraryPath', full_name='pulumirpc.ConstructRequest.libraryPath', index=0,
number=1, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='resource', full_name='pulumirpc.ConstructRequest.resource', index=1,
number=2, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='name', full_name='pulumirpc.ConstructRequest.name', index=2,
number=3, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='args', full_name='pulumirpc.ConstructRequest.args', index=3,
number=4, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='opts', full_name='pulumirpc.ConstructRequest.opts', index=4,
number=5, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=102,
serialized_end=251,
)
_CONSTRUCTRESPONSE = _descriptor.Descriptor(
name='ConstructResponse',
full_name='pulumirpc.ConstructResponse',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='outs', full_name='pulumirpc.ConstructResponse.outs', index=0,
number=1, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=253,
serialized_end=311,
)
_CONSTRUCTREQUEST.fields_by_name['args'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT
_CONSTRUCTREQUEST.fields_by_name['opts'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT
_CONSTRUCTRESPONSE.fields_by_name['outs'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT
DESCRIPTOR.message_types_by_name['ConstructRequest'] = _CONSTRUCTREQUEST
DESCRIPTOR.message_types_by_name['ConstructResponse'] = _CONSTRUCTRESPONSE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
ConstructRequest = _reflection.GeneratedProtocolMessageType('ConstructRequest', (_message.Message,), {
'DESCRIPTOR' : _CONSTRUCTREQUEST,
'__module__' : 'runtime_pb2'
# @@protoc_insertion_point(class_scope:pulumirpc.ConstructRequest)
})
_sym_db.RegisterMessage(ConstructRequest)
ConstructResponse = _reflection.GeneratedProtocolMessageType('ConstructResponse', (_message.Message,), {
'DESCRIPTOR' : _CONSTRUCTRESPONSE,
'__module__' : 'runtime_pb2'
# @@protoc_insertion_point(class_scope:pulumirpc.ConstructResponse)
})
_sym_db.RegisterMessage(ConstructResponse)
_RUNTIME = _descriptor.ServiceDescriptor(
name='Runtime',
full_name='pulumirpc.Runtime',
file=DESCRIPTOR,
index=0,
serialized_options=None,
serialized_start=313,
serialized_end=396,
methods=[
_descriptor.MethodDescriptor(
name='Construct',
full_name='pulumirpc.Runtime.Construct',
index=0,
containing_service=None,
input_type=_CONSTRUCTREQUEST,
output_type=_CONSTRUCTRESPONSE,
serialized_options=None,
),
])
_sym_db.RegisterServiceDescriptor(_RUNTIME)
DESCRIPTOR.services_by_name['Runtime'] = _RUNTIME
# @@protoc_insertion_point(module_scope)

View file

@ -0,0 +1,46 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
import grpc
from . import runtime_pb2 as runtime__pb2
class RuntimeStub(object):
# missing associated documentation comment in .proto file
pass
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.Construct = channel.unary_unary(
'/pulumirpc.Runtime/Construct',
request_serializer=runtime__pb2.ConstructRequest.SerializeToString,
response_deserializer=runtime__pb2.ConstructResponse.FromString,
)
class RuntimeServicer(object):
# missing associated documentation comment in .proto file
pass
def Construct(self, request, context):
# missing associated documentation comment in .proto file
pass
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_RuntimeServicer_to_server(servicer, server):
rpc_method_handlers = {
'Construct': grpc.unary_unary_rpc_method_handler(
servicer.Construct,
request_deserializer=runtime__pb2.ConstructRequest.FromString,
response_serializer=runtime__pb2.ConstructResponse.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'pulumirpc.Runtime', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))

View file

@ -24,6 +24,7 @@ from .. import log
from ..runtime.proto import resource_pb2
from .rpc_manager import RPC_MANAGER
from ..metadata import get_project, get_stack
from .invoke import invoke
if TYPE_CHECKING:
from .. import Resource, ResourceOptions, CustomResource, Inputs, Output
@ -282,6 +283,102 @@ def read_resource(res: 'CustomResource', ty: str, name: str, props: 'Inputs', op
res.id = result.id
def _get_resource(res: 'Resource', ty: str, name: str, custom: bool, props: 'Inputs', opts: 'ResourceOptions') -> _ResourceResult:
"""
TODO
"""
urn = opts.urn
if urn is None:
raise Exception(
"Cannot get resource whose options are lacking a URN value")
log.debug(f"registering resource: ty={ty}, name={name}, custom={custom}")
# Prepare the resource.
# Simply initialize the URN property and get prepared to resolve it later on.
# Note: a resource urn will always get a value, and thus the output property
# for it can always run .apply calls.
log.debug(f"preparing resource for RPC")
urn_future: asyncio.Future[Any] = asyncio.Future()
urn_known: asyncio.Future[bool] = asyncio.Future()
urn_secret: asyncio.Future[bool] = asyncio.Future()
urn_known.set_result(True)
urn_secret.set_result(False)
resolve_urn = urn_future.set_result
resolve_urn_exn = urn_future.set_exception
res.urn = known_types.new_output({res}, urn_future, urn_known, urn_secret)
# If a custom resource, make room for the ID property.
resolve_id: Optional[Callable[[
Any, bool, Optional[Exception]], None]] = None
if custom:
res = cast('CustomResource', res)
resolve_value: asyncio.Future[Any] = asyncio.Future()
resolve_perform_apply: asyncio.Future[bool] = asyncio.Future()
resolve_secret: asyncio.Future[Any] = asyncio.Future()
res.id = known_types.new_output(
{res}, resolve_value, resolve_perform_apply, resolve_secret)
def do_resolve(value: Any, perform_apply: bool, exn: Optional[Exception]):
if exn is not None:
resolve_value.set_exception(exn)
resolve_perform_apply.set_exception(exn)
resolve_secret.set_exception(exn)
else:
resolve_value.set_result(value)
resolve_perform_apply.set_result(perform_apply)
resolve_secret.set_result(False)
resolve_id = do_resolve
# Now "transfer" all input properties into unresolved futures on res. This way,
# this resource will look like it has all its output properties to anyone it is
# passed to. However, those futures won't actually resolve until the RPC returns
resolvers = rpc.transfer_properties(res, props)
async def do_get():
try:
log.debug(f"preparing get: ty={ty}, name={name}, urn={urn}")
_ = await prepare_resource(res, ty, custom, props, opts)
# Resolve the URN that we were given. Note that we are explicitly discarding the list of
# dependencies returned to us from "serialize_property" (the second argument). This is
# because a "get" resource does not actually have any dependencies at all in the cloud
# provider sense, because a read resource already exists. We do not need to track this
# dependency. TODO: Is this actually true for "get"?
resolved_urn = await rpc.serialize_property(urn, [])
log.debug(f"get prepared: ty={ty}, name={name}, urn={urn}")
resp = await invoke("pulumi:pulumi:readStackResource", {"urn": resolved_urn})
except Exception as exn:
log.debug(
f"exception when preparing or executing rpc: {traceback.format_exc()}")
rpc.resolve_outputs_due_to_exception(resolvers, exn)
resolve_urn_exn(exn)
if resolve_id:
resolve_id(None, False, exn)
raise
resp_urn = resp["urn"]
log.debug(f"resource get successful: ty={ty}, urn={resp_urn}")
resolve_urn(resp_urn)
if resolve_id:
id_known = bool(resp["id"])
resolve_id(resp["id"], id_known, None)
await rpc.resolve_properties(resolvers, resp["outputs"])
asyncio.ensure_future(RPC_MANAGER.do_rpc("get resource", do_get)())
def get_resource(res: 'Resource', ty: str, name: str, custom: bool, props: 'Inputs', opts: 'ResourceOptions') -> None:
result = _get_resource(res, ty, name, custom, props, opts)
res.urn = result.urn
if custom:
assert result.id is not None
res = cast('CustomResource', res)
res.id = result.id
# pylint: disable=too-many-locals,too-many-statements
def _register_resource(res: 'Resource',

View file

@ -45,6 +45,9 @@ _special_archive_sig = "0def7320c3a5731c473e5ecbe6d01bc7"
_special_secret_sig = "1b47061264138c4ac30d75fd1eb44270"
"""special_secret_sig is a randomly assigned hash used to identify secrets in maps. See pkg/resource/properties.go"""
_special_resource_sig = "5cf8f73096256a8f31e491e813e4eb8e"
"""special_resource_sig is a randomly assigned hash used to identify resources in maps. See pkg/resource/properties.go"""
_INT_OR_FLOAT = six.integer_types + (float,)
def isLegalProtobufValue(value: Any) -> bool:
@ -102,7 +105,11 @@ async def serialize_property(value: 'Input[Any]',
if known_types.is_custom_resource(value):
resource = cast('CustomResource', value)
deps.append(resource)
return await serialize_property(resource.id, deps, input_transformer)
urn = await serialize_property(value.urn, deps, input_transformer)
return {
_special_sig_key: _special_resource_sig,
"urn": urn,
}
if known_types.is_asset(value):
# Serializing an asset requires the use of a magical signature key, since otherwise it would
@ -235,6 +242,17 @@ def deserialize_properties(props_struct: struct_pb2.Struct, keep_unknowns: Optio
_special_sig_key: _special_secret_sig,
"value": deserialize_property(props_struct["value"])
}
if props_struct[_special_sig_key] == _special_resource_sig:
urn = props_struct["urn"]
urn_parts = urn.split("::")
qualified_type = urn_parts[2]
typ = qualified_type.split("$")[-1]
proxy_constructor = PROXY_CONSTRUCTORS.get(typ, None)
if proxy_constructor is not None:
urn_name = urn_parts[3]
return proxy_constructor(urn_name, {"urn": urn})
print(f"Saw valid URN {urn} during deserialization, but no proxy constructor is registered for type {typ}.")
return urn
raise AssertionError("Unrecognized signature when unmarshalling resource property")
@ -423,6 +441,10 @@ async def resolve_outputs(res: 'Resource',
# the user.
all_properties[translated_key] = translate_output_properties(res, deserialize_property(value))
await resolve_properties(resolvers, all_properties)
async def resolve_properties(resolvers: Dict[str, Resolver], all_properties: Dict[str, Any]):
for key, value in all_properties.items():
# Skip "id" and "urn", since we handle those specially.
if key in ["id", "urn"]:
@ -486,3 +508,11 @@ def resolve_outputs_due_to_exception(resolvers: Dict[str, Resolver], exn: Except
for key, resolve in resolvers.items():
log.debug(f"sending exception to resolver for {key}")
resolve(None, False, False, exn)
PROXY_CONSTRUCTORS: Dict[str, Any] = dict()
def register_proxy_constructor(typ: str, constructor):
existing = PROXY_CONSTRUCTORS.get(typ, None)
if existing is not None:
raise ValueError(f"Cannot re-register type {typ} as a proxy. Previous registration was {existing}, new registration was {constructor}.")
PROXY_CONSTRUCTORS[typ] = constructor

View file

@ -60,6 +60,8 @@ class Settings:
self.dry_run = dry_run
self.test_mode_enabled = test_mode_enabled
self.legacy_apply_enabled = legacy_apply_enabled
self.monitor_addr = monitor
self.engine_addr = engine
if self.test_mode_enabled is None:
self.test_mode_enabled = os.getenv("PULUMI_TEST_MODE", "false") == "true"

View file

@ -34,8 +34,8 @@ class FakeCustomResource:
Fake CustomResource class that duck-types to the real CustomResource.
This class is substituted for the real CustomResource for the below test.
"""
def __init__(self, id):
self.id = id
def __init__(self, urn):
self.urn = Output.from_input(urn)
def async_test(coro):
@ -85,11 +85,13 @@ class NextSerializationTests(unittest.TestCase):
@async_test
async def test_custom_resource(self):
res = FakeCustomResource("some-id")
fake_urn = "urn:pulumi:mystack::myproject::my:mod:Fake::fake"
res = FakeCustomResource(fake_urn)
deps = []
prop = await rpc.serialize_property(res, deps)
self.assertListEqual([res], deps)
self.assertEqual("some-id", prop)
self.assertEqual(rpc._special_resource_sig, prop[rpc._special_sig_key])
self.assertEqual(fake_urn, prop["urn"])
@async_test
async def test_string_asset(self):

View file

@ -6,6 +6,7 @@ import (
"bytes"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"testing"
@ -323,6 +324,48 @@ func TestAccNodeCompatTests(t *testing.T) {
integration.ProgramTest(t, &test)
}
func TestNodeJSMultilang(t *testing.T) {
dir := path.Join(getCwd(t), "multilang", "nodejs")
test := getBaseOptions().
With(integration.ProgramTestOptions{
Dir: dir,
Config: map[string]string{
"aws:region": "us-west-2",
},
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
assert.Equal(t, "foo", stackInfo.Outputs["id2"])
assert.Equal(t, "mydata", stackInfo.Outputs["innerComponent"])
assert.Equal(t, "sg-", stackInfo.Outputs["nodeSecurityGroupId"].(string)[0:3])
assert.Equal(t, 42.0, stackInfo.Outputs["output1"])
},
})
integration.ProgramTest(t, &test)
}
func TestPythonMultilang(t *testing.T) {
dir := path.Join(getCwd(t), "multilang", "python")
test := getPythonBaseOptions().
With(integration.ProgramTestOptions{
Dir: dir,
Dependencies: []string{
filepath.Join("..", "sdk", "python", "env", "src"),
filepath.Join(dir, "mycomponent"),
},
Config: map[string]string{
"aws:region": "us-west-2",
},
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
assert.Equal(t, "foo", stackInfo.Outputs["id2"])
assert.Equal(t, "mydata", stackInfo.Outputs["innerComponent"])
assert.Equal(t, "sg-", stackInfo.Outputs["nodeSecurityGroupId"].(string)[0:3])
assert.Equal(t, 42.0, stackInfo.Outputs["output1"])
},
})
integration.ProgramTest(t, &test)
}
func getCwd(t *testing.T) string {
cwd, err := os.Getwd()
if err != nil {
@ -337,6 +380,14 @@ func getBaseOptions() integration.ProgramTestOptions {
}
}
func getPythonBaseOptions() integration.ProgramTestOptions {
return integration.ProgramTestOptions{
Dependencies: []string{
filepath.Join("..", "sdk", "python", "env", "src"),
},
}
}
func skipIfNotNode610(t *testing.T) {
nodeVer, err := getNodeVersion()
if err != nil && nodeVer.Major == 6 && nodeVer.Minor == 10 {

View file

@ -3,7 +3,6 @@
"align": [
true,
"parameters",
"arguments",
"statements"
],
"ban": false,