From 7fef102bc3980de4f45e67946eb93aa5db9d9204 Mon Sep 17 00:00:00 2001 From: Pat Gavlin Date: Fri, 9 Aug 2019 16:48:28 -0700 Subject: [PATCH] Check for valid PB types in serialize_property (#3060) Just what it says on the tin. This allows us to return an incrementally better error. Fixes #2939. --- sdk/python/Pipfile | 1 + sdk/python/Pipfile.lock | 41 ++++++++++--------- sdk/python/lib/pulumi/runtime/rpc.py | 14 +++++++ .../resource_op_bad_inputs/__init__.py | 13 ++++++ .../resource_op_bad_inputs/__main__.py | 24 +++++++++++ .../test_resource_op_bad_inputs.py | 27 ++++++++++++ sdk/python/lib/test/test_next_serialize.py | 15 +++++++ 7 files changed, 115 insertions(+), 20 deletions(-) create mode 100644 sdk/python/lib/test/langhost/resource_op_bad_inputs/__init__.py create mode 100644 sdk/python/lib/test/langhost/resource_op_bad_inputs/__main__.py create mode 100644 sdk/python/lib/test/langhost/resource_op_bad_inputs/test_resource_op_bad_inputs.py diff --git a/sdk/python/Pipfile b/sdk/python/Pipfile index 0d90ff82f..3505366e7 100644 --- a/sdk/python/Pipfile +++ b/sdk/python/Pipfile @@ -7,6 +7,7 @@ name = "pypi" protobuf = ">=3.6.0" grpcio = ">=1.9.1" dill = ">=0.3.0" +six = ">=1.12.0" [dev-packages] pylint = ">=2.1" diff --git a/sdk/python/Pipfile.lock b/sdk/python/Pipfile.lock index 367c43c08..87e12f605 100644 --- a/sdk/python/Pipfile.lock +++ b/sdk/python/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "420aade1da2a271ef162050a84c18d86047a509e9bd25f5111f139b411223f3c" + "sha256": "b51d443cfdc6681b0f87cca71b6fa6a930ca951c3e86e082e70d307133b36381" }, "pipfile-spec": 6, "requires": {}, @@ -61,33 +61,33 @@ }, "protobuf": { "hashes": [ - "sha256:03f43eac9d5b651f976e91cf46a25b75e5779d98f0f4114b0abfed83376d75f8", - "sha256:0c94b21e6de01362f91a86b372555d22a60b59708599ca9d5032ae9fdf8e3538", - "sha256:2d2a9f30f61f4063fadd7fb68a2510a6939b43c0d6ceeec5c4704f22225da28e", - "sha256:34a0b05fca061e4abb77dd180209f68d8637115ff319f51e28a6a9382d69853a", - "sha256:358710fd0db25372edcf1150fa691f48376a134a6c69ce29f38f185eea7699e6", - "sha256:41e47198b94c27ba05a08b4a95160656105745c462af574e4bcb0807164065c0", - "sha256:8c61cc8a76e9d381c665aecc5105fa0f1878cf7db8b5cd17202603bcb386d0fc", - "sha256:a6eebc4db759e58fdac02efcd3028b811effac881d8a5bad1996e4e8ee6acb47", - "sha256:a9c12f7c98093da0a46ba76ec40ace725daa1ac4038c41e4b1466afb5c45bb01", - "sha256:cb95068492ba0859b8c9e61fa8ba206a83c64e5d0916fb4543700b2e2b214115", - "sha256:cd98476ce7bb4dcd6a7b101f5eecdc073dafea19f311e36eb8fba1a349346277", - "sha256:ce64cfbea18c535176bdaa10ba740c0fc4c6d998a3f511c17bedb0ae4b3b167c", - "sha256:dcbb59eac73fd454e8f2c5fba9e3d3320fd4707ed6a9d3ea3717924a6f0903ea", - "sha256:dd67f34458ae716029e2a71ede998e9092493b62a519236ca52e3c5202096c87", - "sha256:e3c96056eb5b7284a20e256cb0bf783c8f36ad82a4ae5434a7b7cd02384144a7", - "sha256:f612d584d7a27e2f39e7b17878430a959c1bc09a74ba09db096b468558e5e126", - "sha256:f6de8a7d6122297b81566e5bd4df37fd5d62bec14f8f90ebff8ede1c9726cd0a", - "sha256:fa529d9261682b24c2aaa683667253175c9acebe0a31105394b221090da75832" + "sha256:00a1b0b352dc7c809749526d1688a64b62ea400c5b05416f93cfb1b11a036295", + "sha256:01acbca2d2c8c3f7f235f1842440adbe01bbc379fa1cbdd80753801432b3fae9", + "sha256:0a795bca65987b62d6b8a2d934aa317fd1a4d06a6dd4df36312f5b0ade44a8d9", + "sha256:0ec035114213b6d6e7713987a759d762dd94e9f82284515b3b7331f34bfaec7f", + "sha256:31b18e1434b4907cb0113e7a372cd4d92c047ce7ba0fa7ea66a404d6388ed2c1", + "sha256:32a3abf79b0bef073c70656e86d5bd68a28a1fbb138429912c4fc07b9d426b07", + "sha256:55f85b7808766e5e3f526818f5e2aeb5ba2edcc45bcccede46a3ccc19b569cb0", + "sha256:64ab9bc971989cbdd648c102a96253fdf0202b0c38f15bd34759a8707bdd5f64", + "sha256:64cf847e843a465b6c1ba90fb6c7f7844d54dbe9eb731e86a60981d03f5b2e6e", + "sha256:917c8662b585470e8fd42f052661fc66d59fccaae450a60044307dcbf82a3335", + "sha256:afed9003d7f2be2c3df20f64220c30faec441073731511728a2cb4cab4cd46a6", + "sha256:b883d7eb129b1b57c5128146bc7c2d1f15de457e96a549827fbee6f26eeedc46", + "sha256:bf8e05d638b585d1752c5a84247134a0350d3a8b73d3632489a014a9f6f1e758", + "sha256:d831b047bd69becaf64019a47179eb22118a50dd008340655266a906c69c6417", + "sha256:de2760583ed28749ff885789c1cbc6c9c06d6de92fc825740ab99deb2f25ea4d", + "sha256:eabc4cf1bc19689af8022ba52fd668564a8d96e0d08f3b4732d26a64255216a4", + "sha256:fcff6086c86fb1628d94ea455c7b9de898afc50378042927a59df8065a79a549" ], "index": "pypi", - "version": "==3.8.0" + "version": "==3.9.1" }, "six": { "hashes": [ "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" ], + "index": "pypi", "version": "==1.12.0" } }, @@ -149,6 +149,7 @@ "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" ], + "index": "pypi", "version": "==1.12.0" }, "typed-ast": { diff --git a/sdk/python/lib/pulumi/runtime/rpc.py b/sdk/python/lib/pulumi/runtime/rpc.py index 6cbe66166..c66cad7ac 100644 --- a/sdk/python/lib/pulumi/runtime/rpc.py +++ b/sdk/python/lib/pulumi/runtime/rpc.py @@ -21,6 +21,7 @@ import inspect from typing import List, Any, Callable, Dict, Optional, TYPE_CHECKING from google.protobuf import struct_pb2 +import six from . import known_types, settings from .. import log @@ -43,6 +44,15 @@ _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""" +_INT_OR_FLOAT = six.integer_types + (float,) + +def isLegalProtobufValue(value: Any) -> bool: + """ + Returns True if the given value is a legal Protobuf value as per the source at + https://github.com/protocolbuffers/protobuf/blob/master/python/google/protobuf/internal/well_known_types.py#L714-L732 + """ + return value is None or isinstance(value, (bool, six.string_types, _INT_OR_FLOAT, dict, list)) + async def serialize_properties(inputs: 'Inputs', property_deps: Dict[str, List['Resource']], input_transformer: Optional[Callable[[str], str]] = None) -> struct_pb2.Struct: @@ -166,6 +176,10 @@ async def serialize_property(value: 'Input[Any]', return obj + # Ensure that we have a value that Protobuf understands. + if not isLegalProtobufValue(value): + raise ValueError(f"unexpected input of type {type(value).__name__}") + return value # pylint: disable=too-many-return-statements diff --git a/sdk/python/lib/test/langhost/resource_op_bad_inputs/__init__.py b/sdk/python/lib/test/langhost/resource_op_bad_inputs/__init__.py new file mode 100644 index 000000000..334a89c27 --- /dev/null +++ b/sdk/python/lib/test/langhost/resource_op_bad_inputs/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/sdk/python/lib/test/langhost/resource_op_bad_inputs/__main__.py b/sdk/python/lib/test/langhost/resource_op_bad_inputs/__main__.py new file mode 100644 index 000000000..304711a6f --- /dev/null +++ b/sdk/python/lib/test/langhost/resource_op_bad_inputs/__main__.py @@ -0,0 +1,24 @@ +# 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. +from pulumi import CustomResource + +class MyClass: + def __init__(self): + self.prop = "oh no!" + +class MyResource(CustomResource): + def __init__(self, name): + CustomResource.__init__(self, "test:index:MyResource", name, {"class": MyClass()}) + +MyResource("testResource1") diff --git a/sdk/python/lib/test/langhost/resource_op_bad_inputs/test_resource_op_bad_inputs.py b/sdk/python/lib/test/langhost/resource_op_bad_inputs/test_resource_op_bad_inputs.py new file mode 100644 index 000000000..100e4d067 --- /dev/null +++ b/sdk/python/lib/test/langhost/resource_op_bad_inputs/test_resource_op_bad_inputs.py @@ -0,0 +1,27 @@ +# 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. +from os import path +from ..util import LanghostTest + + +class UnhandledExceptionTest(LanghostTest): + def test_unhandled_exception(self): + self.run_test( + program=path.join(self.base_path(), "resource_op_bad_inputs"), + expected_error="Program exited with non-zero exit code: 1") + + def register_resource(self, _ctx, _dry_run, _ty, _name, _resource, + _dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace, + _ignore_changes, _version): + raise Exception("oh no") diff --git a/sdk/python/lib/test/test_next_serialize.py b/sdk/python/lib/test/test_next_serialize.py index 507525d99..459173315 100644 --- a/sdk/python/lib/test/test_next_serialize.py +++ b/sdk/python/lib/test/test_next_serialize.py @@ -235,6 +235,21 @@ class NextSerializationTests(unittest.TestCase): self.assertEqual(rpc._special_archive_sig, prop[rpc._special_sig_key]) self.assertEqual("foo.tar.gz", prop["path"]) + @async_test + async def test_bad_inputs(self): + class MyClass: + def __init__(self): + self.prop = "oh no!" + + error = None + try: + prop = await rpc.serialize_property(MyClass(), []) + except ValueError as err: + error = err + + self.assertIsNotNone(error) + self.assertEqual("unexpected input of type MyClass", str(error)) + class DeserializationTests(unittest.TestCase): def test_unsupported_sig(self): struct = struct_pb2.Struct()