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:
parent
00bc1287af
commit
06034fab40
|
@ -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
|
||||
|
|
|
@ -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)())
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
Loading…
Reference in a new issue