diff --git a/changelog/pending/20221121--sdk-python--dont-error-on-type-mismatches-when-using-input-values-for-outputs.yaml b/changelog/pending/20221121--sdk-python--dont-error-on-type-mismatches-when-using-input-values-for-outputs.yaml new file mode 100644 index 000000000000..d5c043894196 --- /dev/null +++ b/changelog/pending/20221121--sdk-python--dont-error-on-type-mismatches-when-using-input-values-for-outputs.yaml @@ -0,0 +1,4 @@ +changes: +- type: fix + scope: sdk/python + description: Don't error on type mismatches when using input values for outputs diff --git a/sdk/python/lib/pulumi/runtime/rpc.py b/sdk/python/lib/pulumi/runtime/rpc.py index 7f08f385077d..da9515e37007 100644 --- a/sdk/python/lib/pulumi/runtime/rpc.py +++ b/sdk/python/lib/pulumi/runtime/rpc.py @@ -789,6 +789,7 @@ def translate_output_properties( typ: Optional[type] = None, transform_using_type_metadata: bool = False, path: Optional["_Path"] = None, + return_none_on_dict_type_mismatch: bool = False, ) -> Any: """ Recursively rewrite keys of objects returned by the engine to conform with a naming @@ -827,7 +828,12 @@ def translate_output_properties( if is_rpc_secret(output): unwrapped = unwrap_rpc_secret(output) result = translate_output_properties( - unwrapped, output_transformer, typ, transform_using_type_metadata + unwrapped, + output_transformer, + typ, + transform_using_type_metadata, + path, + return_none_on_dict_type_mismatch, ) return wrap_rpc_secret(result) @@ -860,7 +866,8 @@ def translate_output_properties( output_transformer, get_type(k), transform_using_type_metadata, - path=_Path(k, parent=path), + _Path(k, parent=path), + return_none_on_dict_type_mismatch, ) for k, v in output.items() } @@ -878,6 +885,8 @@ def translate_output_properties( if transform_using_type_metadata: # pylint: disable=C3001 translate = lambda k: k + elif return_none_on_dict_type_mismatch: + return None else: raise AssertionError( ( @@ -893,7 +902,8 @@ def translate_output_properties( output_transformer, get_type(k), transform_using_type_metadata, - path=_Path(k, parent=path), + _Path(k, parent=path), + return_none_on_dict_type_mismatch, ) for k, v in output.items() } @@ -906,7 +916,8 @@ def translate_output_properties( output_transformer, element_type, transform_using_type_metadata, - path=_Path(str(i), parent=path), + _Path(str(i), parent=path), + return_none_on_dict_type_mismatch, ) for i, v in enumerate(output) ] @@ -1044,14 +1055,19 @@ def resolve_outputs( for key, value in list(serialized_props.items()): translated_key = translate(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. + # input prop the engine didn't give us a final value for. + # Just use the value passed into the resource by the user. + # Set `return_none_on_dict_type_mismatch` to return `None` rather than raising an error when the value + # is a dict and the type doesn't match (which is what would happen if the value didn't exist as an + # input prop). This allows `pulumi up` to work without erroring when there is an input and output prop + # with the same name but different types. all_properties[translated_key] = translate_output_properties( deserialize_property(value), translate_to_pass, types.get(key), transform_using_type_metadata, path=_Path(translated_key, resource=f"{res._name}"), + return_none_on_dict_type_mismatch=True, ) resolve_properties(resolvers, all_properties, translated_deps) diff --git a/sdk/python/lib/test/langhost/input_values_for_outputs/__init__.py b/sdk/python/lib/test/langhost/input_values_for_outputs/__init__.py new file mode 100644 index 000000000000..91774a967494 --- /dev/null +++ b/sdk/python/lib/test/langhost/input_values_for_outputs/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2016-2022, 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. diff --git a/sdk/python/lib/test/langhost/input_values_for_outputs/__main__.py b/sdk/python/lib/test/langhost/input_values_for_outputs/__main__.py new file mode 100644 index 000000000000..f6d8b81b3b98 --- /dev/null +++ b/sdk/python/lib/test/langhost/input_values_for_outputs/__main__.py @@ -0,0 +1,81 @@ +# Copyright 2016-2022, 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. + +from typing import Optional + +import pulumi + + +class Instance(pulumi.CustomResource): + public_ip: pulumi.Output[str] + def __init__(self, resource_name, name: pulumi.Input[str] = None, value: pulumi.Input[str] = None, opts = None): + if opts is None: + opts = pulumi.ResourceOptions() + if name is None and not opts.urn: + raise TypeError("Missing required property 'name'") + __props__: dict = dict() + __props__["public_ip"] = None + __props__["name"] = name + __props__["value"] = value + super(Instance, self).__init__("aws:ec2/instance:Instance", resource_name, __props__, opts) + + +@pulumi.input_type +class DefaultLogGroupArgs: + def __init__(self, *, skip: Optional[bool] = None): + if skip is not None: + pulumi.set(self, "skip", skip) + + @property + @pulumi.getter + def skip(self) -> Optional[bool]: + return pulumi.get(self, "skip") + + @skip.setter + def skip(self, value: Optional[bool]): + pulumi.set(self, "skip", value) + + +@pulumi.input_type +class FargateTaskDefinitionArgs: + def __init__(self, *, log_group: Optional[DefaultLogGroupArgs] = None): + if log_group is not None: + pulumi.set(self, "log_group", log_group) + + @property + @pulumi.getter(name="logGroup") + def log_group(self) -> Optional[DefaultLogGroupArgs]: + return pulumi.get(self, "log_group") + + @log_group.setter + def log_group(self, value: Optional[DefaultLogGroupArgs]): + pulumi.set(self, "log_group", value) + + +# This resource has an input named `logGroup` typed as `DefaultLogGroupArgs` and an output named `logGroup` typed +# as `Instance`. When the provider returns no value for `logGroup`, it should not try to set the output to the +# input value due to the type mismatch. +class FargateTaskDefinition(pulumi.ComponentResource): + def __init__(self, resource_name: str, log_group: Optional[pulumi.InputType[DefaultLogGroupArgs]] = None): + __props__ = FargateTaskDefinitionArgs.__new__(FargateTaskDefinitionArgs) + __props__.__dict__["log_group"] = log_group + super().__init__("awsx:ecs:FargateTaskDefinition", resource_name, __props__, None, remote=True) + + @property + @pulumi.getter(name="logGroup") + def log_group(self) -> pulumi.Output[Optional[Instance]]: + return pulumi.get(self, "log_group") + + +task_def = FargateTaskDefinition("task_def", log_group=DefaultLogGroupArgs(skip=True)) diff --git a/sdk/python/lib/test/langhost/input_values_for_outputs/test_input_values_for_outputs.py b/sdk/python/lib/test/langhost/input_values_for_outputs/test_input_values_for_outputs.py new file mode 100644 index 000000000000..71e89543b5c2 --- /dev/null +++ b/sdk/python/lib/test/langhost/input_values_for_outputs/test_input_values_for_outputs.py @@ -0,0 +1,34 @@ +# Copyright 2016-2022, 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. + +from os import path +from ..util import LanghostTest + + +class InputValuesForOutputsTest(LanghostTest): + """ + """ + def test_input_values_for_outputs(self): + self.run_test( + program=path.join(self.base_path(), "input_values_for_outputs"), + expected_resource_count=1) + + def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect, + _provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import, + _replace_on_changes): + return { + "urn": self.make_urn(ty, name), + "id": name, + "object": {} # return no outputs + }