Only await input properties once. (#3024)

These changes fix a bug in the Python runtime that would cause any
awaitable input properties passed to a resource that are missing
from the resource's output properties to be awaited twice. The fix is
straightforward: rather than roundtripping an input property through
serialize/deserialize, just deserialized the already-serialized input
property.

Fixes #2940.
This commit is contained in:
Pat Gavlin 2019-08-03 10:29:19 -07:00 committed by GitHub
parent 00bc1287af
commit 06034fab40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 25 additions and 14 deletions

View file

@ -25,6 +25,9 @@ CHANGELOG
- Make `pulumi.runtime.invoke` synchronous in the Python SDK [#3019](https://github.com/pulumi/pulumi/pull/3019)
- Fix a bug in the Python SDK that caused input properties that are coroutines to be awaited twice.
[#3024](https://github.com/pulumi/pulumi/pull/3024)
### Compatibility
- Deprecated functions in `@pulumi/pulumi` will now issue warnings if you call them. Please migrate

View file

@ -256,7 +256,7 @@ def read_resource(res: 'Resource', ty: str, name: str, props: 'Inputs', opts: Op
log.debug(f"resource read successful: ty={ty}, urn={resp.urn}")
resolve_urn(resp.urn)
resolve_id(resolved_id, True, None) # Read IDs are always known.
await rpc.resolve_outputs(res, props, resp.properties, resolvers)
await rpc.resolve_outputs(res, resolver.serialized_props, resp.properties, resolvers)
asyncio.ensure_future(RPC_MANAGER.do_rpc("read resource", do_read)())
@ -399,7 +399,7 @@ def register_resource(res: 'Resource', ty: str, name: str, custom: bool, props:
is_known = bool(resp.id)
resolve_id(resp.id, is_known, None)
await rpc.resolve_outputs(res, props, resp.object, resolvers)
await rpc.resolve_outputs(res, resolver.serialized_props, resp.object, resolvers)
asyncio.ensure_future(RPC_MANAGER.do_rpc(
"register resource", do_register)())

View file

@ -348,7 +348,11 @@ def translate_output_properties(res: 'Resource', output: Any) -> Any:
return output
async def resolve_outputs(res: 'Resource', props: 'Inputs', outputs: struct_pb2.Struct, resolvers: Dict[str, Resolver]):
async def resolve_outputs(res: 'Resource',
serialized_props: struct_pb2.Struct,
outputs: struct_pb2.Struct,
resolvers: Dict[str, Resolver]):
# Produce a combined set of property states, starting with inputs and then applying
# outputs. If the same property exists in the inputs and outputs states, the output wins.
all_properties = {}
@ -360,16 +364,12 @@ async def resolve_outputs(res: 'Resource', props: 'Inputs', outputs: struct_pb2.
log.debug(f"incoming output value translated: {value} -> {translated_value}")
all_properties[translated_key] = translated_value
for key, value in props.items():
if key not in all_properties:
# input prop the engine didn't give us a final value for. Just use the value passed into the resource
# after round-tripping it through serialization. We do the round-tripping primarily s.t. we ensure that
# Output values are handled properly w.r.t. unknowns.
input_prop = await serialize_property(value, [])
if input_prop is None:
continue
all_properties[key] = deserialize_property(input_prop)
for key, value in list(serialized_props.items()):
translated_key = res.translate_output_property(key)
if translated_key not in all_properties:
# input prop the engine didn't give us a final value for.Just use the value passed into the resource by
# the user.
all_properties[translated_key] = translate_output_properties(res, deserialize_property(value))
for key, value in all_properties.items():
# Skip "id" and "urn", since we handle those specially.

View file

@ -11,19 +11,26 @@
# 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
from functools import partial
from pulumi import CustomResource, Output
def assert_eq(l, r):
assert l == r
async def inprop2():
await asyncio.sleep(0)
return 42
class ResourceA(CustomResource):
inprop: Output[int]
inprop_2: Output[int]
outprop: Output[str]
def __init__(self, name: str) -> None:
CustomResource.__init__(self, "test:index:ResourceA", name, {
"inprop": 777,
"inprop_2": inprop2(),
"outprop": None
})
@ -41,6 +48,7 @@ class ResourceB(CustomResource):
a = ResourceA("resourceA")
a.urn.apply(lambda urn: assert_eq(urn, "test:index:ResourceA::resourceA"))
a.inprop.apply(lambda v: assert_eq(v, 777))
a.inprop_2.apply(lambda v: assert_eq(v, 42))
a.outprop.apply(lambda v: assert_eq(v, "output yeah"))
b = ResourceB("resourceB", a)

View file

@ -34,7 +34,7 @@ class ResourceThensTest(LanghostTest):
_ignore_changes, _version):
if ty == "test:index:ResourceA":
self.assertEqual(name, "resourceA")
self.assertDictEqual(res, {"inprop": 777})
self.assertDictEqual(res, {"inprop": 777, "inprop_2": 42})
urn = self.make_urn(ty, name)
res_id = ""
props = {}