diff --git a/examples/assets/go.mod b/examples/assets/go.mod index 6072770..0562e0f 100644 --- a/examples/assets/go.mod +++ b/examples/assets/go.mod @@ -47,6 +47,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect diff --git a/examples/assets/go.sum b/examples/assets/go.sum index fdd9b8d..637c460 100644 --- a/examples/assets/go.sum +++ b/examples/assets/go.sum @@ -125,6 +125,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= diff --git a/examples/call/PulumiPlugin.yaml b/examples/call/PulumiPlugin.yaml new file mode 100644 index 0000000..9d33323 --- /dev/null +++ b/examples/call/PulumiPlugin.yaml @@ -0,0 +1 @@ +runtime: go diff --git a/examples/call/consumer/.gitignore b/examples/call/consumer/.gitignore new file mode 100644 index 0000000..d75edea --- /dev/null +++ b/examples/call/consumer/.gitignore @@ -0,0 +1,2 @@ +venv +__pycache__ \ No newline at end of file diff --git a/examples/call/consumer/Pulumi.yaml b/examples/call/consumer/Pulumi.yaml new file mode 100644 index 0000000..f8bb32d --- /dev/null +++ b/examples/call/consumer/Pulumi.yaml @@ -0,0 +1,6 @@ +runtime: python +name: dev +plugins: + providers: + - name: test + path: .. diff --git a/examples/call/consumer/__main__.py b/examples/call/consumer/__main__.py new file mode 100644 index 0000000..84f1cc4 --- /dev/null +++ b/examples/call/consumer/__main__.py @@ -0,0 +1,5 @@ +import pulumi_test as test + +p = test.Provider("explicit-provider") + +p.make_pet() diff --git a/examples/call/consumer/requirements.txt b/examples/call/consumer/requirements.txt new file mode 100644 index 0000000..522cc55 --- /dev/null +++ b/examples/call/consumer/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=v1.129.0 +sdks/test diff --git a/examples/call/consumer/sdks/test/build/lib/pulumi_test/__init__.py b/examples/call/consumer/sdks/test/build/lib/pulumi_test/__init__.py new file mode 100644 index 0000000..df37f7d --- /dev/null +++ b/examples/call/consumer/sdks/test/build/lib/pulumi_test/__init__.py @@ -0,0 +1,23 @@ +# coding=utf-8 +# *** WARNING: this file was generated by pulumi-language-python. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + +from . import _utilities +import typing +# Export this package's modules as members: +from .provider import * +_utilities.register( + resource_modules=""" +[] +""", + resource_packages=""" +[ + { + "pkg": "test", + "token": "pulumi:providers:test", + "fqn": "pulumi_test", + "class": "Provider" + } +] +""" +) diff --git a/examples/call/consumer/sdks/test/build/lib/pulumi_test/_utilities.py b/examples/call/consumer/sdks/test/build/lib/pulumi_test/_utilities.py new file mode 100644 index 0000000..dcf4c01 --- /dev/null +++ b/examples/call/consumer/sdks/test/build/lib/pulumi_test/_utilities.py @@ -0,0 +1,327 @@ +# coding=utf-8 +# *** WARNING: this file was generated by pulumi-language-python. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + + +import asyncio +import functools +import importlib.metadata +import importlib.util +import inspect +import json +import os +import sys +import typing +import warnings +import base64 + +import pulumi +import pulumi.runtime +from pulumi.runtime.sync_await import _sync_await +from pulumi.runtime.proto import resource_pb2 + +from semver import VersionInfo as SemverVersion +from parver import Version as PEP440Version + +C = typing.TypeVar("C", bound=typing.Callable) + + +def get_env(*args): + for v in args: + value = os.getenv(v) + if value is not None: + return value + return None + + +def get_env_bool(*args): + str = get_env(*args) + if str is not None: + # NOTE: these values are taken from https://golang.org/src/strconv/atob.go?s=351:391#L1, which is what + # Terraform uses internally when parsing boolean values. + if str in ["1", "t", "T", "true", "TRUE", "True"]: + return True + if str in ["0", "f", "F", "false", "FALSE", "False"]: + return False + return None + + +def get_env_int(*args): + str = get_env(*args) + if str is not None: + try: + return int(str) + except: + return None + return None + + +def get_env_float(*args): + str = get_env(*args) + if str is not None: + try: + return float(str) + except: + return None + return None + + +def _get_semver_version(): + # __name__ is set to the fully-qualified name of the current module, In our case, it will be + # ._utilities. is the module we want to query the version for. + root_package, *rest = __name__.split('.') + + # pkg_resources uses setuptools to inspect the set of installed packages. We use it here to ask + # for the currently installed version of the root package (i.e. us) and get its version. + + # Unfortunately, PEP440 and semver differ slightly in incompatible ways. The Pulumi engine expects + # to receive a valid semver string when receiving requests from the language host, so it's our + # responsibility as the library to convert our own PEP440 version into a valid semver string. + + pep440_version_string = importlib.metadata.version(root_package) + pep440_version = PEP440Version.parse(pep440_version_string) + (major, minor, patch) = pep440_version.release + prerelease = None + if pep440_version.pre_tag == 'a': + prerelease = f"alpha.{pep440_version.pre}" + elif pep440_version.pre_tag == 'b': + prerelease = f"beta.{pep440_version.pre}" + elif pep440_version.pre_tag == 'rc': + prerelease = f"rc.{pep440_version.pre}" + elif pep440_version.dev is not None: + prerelease = f"dev.{pep440_version.dev}" + + # The only significant difference between PEP440 and semver as it pertains to us is that PEP440 has explicit support + # for dev builds, while semver encodes them as "prerelease" versions. In order to bridge between the two, we convert + # our dev build version into a prerelease tag. This matches what all of our other packages do when constructing + # their own semver string. + return SemverVersion(major=major, minor=minor, patch=patch, prerelease=prerelease) + + +# Determine the version once and cache the value, which measurably improves program performance. +_version = _get_semver_version() +_version_str = str(_version) + +def get_resource_opts_defaults() -> pulumi.ResourceOptions: + return pulumi.ResourceOptions( + version=get_version(), + plugin_download_url=get_plugin_download_url(), + ) + +def get_invoke_opts_defaults() -> pulumi.InvokeOptions: + return pulumi.InvokeOptions( + version=get_version(), + plugin_download_url=get_plugin_download_url(), + ) + +def get_resource_args_opts(resource_args_type, resource_options_type, *args, **kwargs): + """ + Return the resource args and options given the *args and **kwargs of a resource's + __init__ method. + """ + + resource_args, opts = None, None + + # If the first item is the resource args type, save it and remove it from the args list. + if args and isinstance(args[0], resource_args_type): + resource_args, args = args[0], args[1:] + + # Now look at the first item in the args list again. + # If the first item is the resource options class, save it. + if args and isinstance(args[0], resource_options_type): + opts = args[0] + + # If resource_args is None, see if "args" is in kwargs, and, if so, if it's typed as the + # the resource args type. + if resource_args is None: + a = kwargs.get("args") + if isinstance(a, resource_args_type): + resource_args = a + + # If opts is None, look it up in kwargs. + if opts is None: + opts = kwargs.get("opts") + + return resource_args, opts + + +# Temporary: just use pulumi._utils.lazy_import once everyone upgrades. +def lazy_import(fullname): + + import pulumi._utils as u + f = getattr(u, 'lazy_import', None) + if f is None: + f = _lazy_import_temp + + return f(fullname) + + +# Copied from pulumi._utils.lazy_import, see comments there. +def _lazy_import_temp(fullname): + m = sys.modules.get(fullname, None) + if m is not None: + return m + + spec = importlib.util.find_spec(fullname) + + m = sys.modules.get(fullname, None) + if m is not None: + return m + + loader = importlib.util.LazyLoader(spec.loader) + spec.loader = loader + module = importlib.util.module_from_spec(spec) + + m = sys.modules.get(fullname, None) + if m is not None: + return m + + sys.modules[fullname] = module + loader.exec_module(module) + return module + + +class Package(pulumi.runtime.ResourcePackage): + def __init__(self, pkg_info): + super().__init__() + self.pkg_info = pkg_info + + def version(self): + return _version + + def construct_provider(self, name: str, typ: str, urn: str) -> pulumi.ProviderResource: + if typ != self.pkg_info['token']: + raise Exception(f"unknown provider type {typ}") + Provider = getattr(lazy_import(self.pkg_info['fqn']), self.pkg_info['class']) + return Provider(name, pulumi.ResourceOptions(urn=urn)) + + +class Module(pulumi.runtime.ResourceModule): + def __init__(self, mod_info): + super().__init__() + self.mod_info = mod_info + + def version(self): + return _version + + def construct(self, name: str, typ: str, urn: str) -> pulumi.Resource: + class_name = self.mod_info['classes'].get(typ, None) + + if class_name is None: + raise Exception(f"unknown resource type {typ}") + + TheClass = getattr(lazy_import(self.mod_info['fqn']), class_name) + return TheClass(name, pulumi.ResourceOptions(urn=urn)) + + +def register(resource_modules, resource_packages): + resource_modules = json.loads(resource_modules) + resource_packages = json.loads(resource_packages) + + for pkg_info in resource_packages: + pulumi.runtime.register_resource_package(pkg_info['pkg'], Package(pkg_info)) + + for mod_info in resource_modules: + pulumi.runtime.register_resource_module( + mod_info['pkg'], + mod_info['mod'], + Module(mod_info)) + + +_F = typing.TypeVar('_F', bound=typing.Callable[..., typing.Any]) + + +def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]: + """Decorator internally used on {fn}_output lifted function versions + to implement them automatically from the un-lifted function.""" + + func_sig = inspect.signature(func) + + def lifted_func(*args, opts=None, **kwargs): + bound_args = func_sig.bind(*args, **kwargs) + # Convert tuple to list, see pulumi/pulumi#8172 + args_list = list(bound_args.args) + return pulumi.Output.from_input({ + 'args': args_list, + 'kwargs': bound_args.kwargs + }).apply(lambda resolved_args: func(*resolved_args['args'], + opts=opts, + **resolved_args['kwargs'])) + + return (lambda _: lifted_func) + + +def call_plain( + tok: str, + props: pulumi.Inputs, + res: typing.Optional[pulumi.Resource] = None, + typ: typing.Optional[type] = None, +) -> typing.Any: + """ + Wraps pulumi.runtime.plain to force the output and return it plainly. + """ + + output = pulumi.runtime.call(tok, props, res, typ) + + # Ingoring deps silently. They are typically non-empty, r.f() calls include r as a dependency. + result, known, secret, _ = _sync_await(asyncio.ensure_future(_await_output(output))) + + problem = None + if not known: + problem = ' an unknown value' + elif secret: + problem = ' a secret value' + + if problem: + raise AssertionError( + f"Plain resource method '{tok}' incorrectly returned {problem}. " + + "This is an error in the provider, please report this to the provider developer." + ) + + return result + + +async def _await_output(o: pulumi.Output[typing.Any]) -> typing.Tuple[object, bool, bool, set]: + return ( + await o._future, + await o._is_known, + await o._is_secret, + await o._resources, + ) + + +# This is included to provide an upgrade path for users who are using a version +# of the Pulumi SDK (<3.121.0) that does not include the `deprecated` decorator. +def deprecated(message: str) -> typing.Callable[[C], C]: + """ + Decorator to indicate a function is deprecated. + + As well as inserting appropriate statements to indicate that the function is + deprecated, this decorator also tags the function with a special attribute + so that Pulumi code can detect that it is deprecated and react appropriately + in certain situations. + + message is the deprecation message that should be printed if the function is called. + """ + + def decorator(fn: C) -> C: + if not callable(fn): + raise TypeError("Expected fn to be callable") + + @functools.wraps(fn) + def deprecated_fn(*args, **kwargs): + warnings.warn(message) + pulumi.warn(f"{fn.__name__} is deprecated: {message}") + + return fn(*args, **kwargs) + + deprecated_fn.__dict__["_pulumi_deprecated_callable"] = fn + return typing.cast(C, deprecated_fn) + + return decorator + +def get_plugin_download_url(): + return None + +def get_version(): + return _version_str diff --git a/examples/call/consumer/sdks/test/build/lib/pulumi_test/provider.py b/examples/call/consumer/sdks/test/build/lib/pulumi_test/provider.py new file mode 100644 index 0000000..c650574 --- /dev/null +++ b/examples/call/consumer/sdks/test/build/lib/pulumi_test/provider.py @@ -0,0 +1,90 @@ +# coding=utf-8 +# *** WARNING: this file was generated by pulumi-language-python. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + +import copy +import warnings +import pulumi +import pulumi.runtime +from typing import Any, Mapping, Optional, Sequence, Union, overload +from . import _utilities + +__all__ = ['ProviderArgs', 'Provider'] + +@pulumi.input_type +class ProviderArgs: + def __init__(__self__): + """ + The set of arguments for constructing a Provider resource. + """ + pass + + +class Provider(pulumi.ProviderResource): + @overload + def __init__(__self__, + resource_name: str, + opts: Optional[pulumi.ResourceOptions] = None, + __props__=None): + """ + Create a Test resource with the given unique name, props, and options. + :param str resource_name: The name of the resource. + :param pulumi.ResourceOptions opts: Options for the resource. + """ + ... + @overload + def __init__(__self__, + resource_name: str, + args: Optional[ProviderArgs] = None, + opts: Optional[pulumi.ResourceOptions] = None): + """ + Create a Test resource with the given unique name, props, and options. + :param str resource_name: The name of the resource. + :param ProviderArgs args: The arguments to use to populate this resource's properties. + :param pulumi.ResourceOptions opts: Options for the resource. + """ + ... + def __init__(__self__, resource_name: str, *args, **kwargs): + resource_args, opts = _utilities.get_resource_args_opts(ProviderArgs, pulumi.ResourceOptions, *args, **kwargs) + if resource_args is not None: + __self__._internal_init(resource_name, opts, **resource_args.__dict__) + else: + __self__._internal_init(resource_name, *args, **kwargs) + + def _internal_init(__self__, + resource_name: str, + opts: Optional[pulumi.ResourceOptions] = None, + __props__=None): + opts = pulumi.ResourceOptions.merge(_utilities.get_resource_opts_defaults(), opts) + if not isinstance(opts, pulumi.ResourceOptions): + raise TypeError('Expected resource options to be a ResourceOptions instance') + if opts.id is None: + if __props__ is not None: + raise TypeError('__props__ is only valid when passed in combination with a valid opts.id to get an existing resource') + __props__ = ProviderArgs.__new__(ProviderArgs) + + super(Provider, __self__).__init__( + 'test', + resource_name, + __props__, + opts) + + @pulumi.output_type + class MakePetResult: + def __init__(__self__, success=None): + if success and not isinstance(success, bool): + raise TypeError("Expected argument 'success' to be a bool") + pulumi.set(__self__, "success", success) + + @property + @pulumi.getter + def success(self) -> Optional[bool]: + return pulumi.get(self, "success") + + def make_pet(__self__, *, + prefix: Optional[pulumi.Input[str]] = None) -> pulumi.Output['Provider.MakePetResult']: + __args__ = dict() + __args__['__self__'] = __self__ + __args__['prefix'] = prefix + return pulumi.runtime.call('pulumi:providers:test/makePet', __args__, res=__self__, typ=Provider.MakePetResult) + diff --git a/examples/call/consumer/sdks/test/build/lib/pulumi_test/pulumi-plugin.json b/examples/call/consumer/sdks/test/build/lib/pulumi_test/pulumi-plugin.json new file mode 100644 index 0000000..7fd8a3f --- /dev/null +++ b/examples/call/consumer/sdks/test/build/lib/pulumi_test/pulumi-plugin.json @@ -0,0 +1,4 @@ +{ + "resource": true, + "name": "test" +} diff --git a/examples/call/consumer/sdks/test/build/lib/pulumi_test/py.typed b/examples/call/consumer/sdks/test/build/lib/pulumi_test/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/examples/call/consumer/sdks/test/pulumi_test.egg-info/PKG-INFO b/examples/call/consumer/sdks/test/pulumi_test.egg-info/PKG-INFO new file mode 100644 index 0000000..55455db --- /dev/null +++ b/examples/call/consumer/sdks/test/pulumi_test.egg-info/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 2.1 +Name: pulumi_test +Version: 0.0.0 +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +Requires-Dist: parver>=0.2.1 +Requires-Dist: pulumi<4.0.0,>=3.0.0 +Requires-Dist: semver>=2.8.1 + +test Pulumi Package - Development Version diff --git a/examples/call/consumer/sdks/test/pulumi_test.egg-info/SOURCES.txt b/examples/call/consumer/sdks/test/pulumi_test.egg-info/SOURCES.txt new file mode 100644 index 0000000..2ac4a49 --- /dev/null +++ b/examples/call/consumer/sdks/test/pulumi_test.egg-info/SOURCES.txt @@ -0,0 +1,12 @@ +setup.py +pulumi_test/__init__.py +pulumi_test/_utilities.py +pulumi_test/provider.py +pulumi_test/pulumi-plugin.json +pulumi_test/py.typed +pulumi_test.egg-info/PKG-INFO +pulumi_test.egg-info/SOURCES.txt +pulumi_test.egg-info/dependency_links.txt +pulumi_test.egg-info/not-zip-safe +pulumi_test.egg-info/requires.txt +pulumi_test.egg-info/top_level.txt \ No newline at end of file diff --git a/examples/call/consumer/sdks/test/pulumi_test.egg-info/dependency_links.txt b/examples/call/consumer/sdks/test/pulumi_test.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/call/consumer/sdks/test/pulumi_test.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/examples/call/consumer/sdks/test/pulumi_test.egg-info/not-zip-safe b/examples/call/consumer/sdks/test/pulumi_test.egg-info/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/call/consumer/sdks/test/pulumi_test.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/examples/call/consumer/sdks/test/pulumi_test.egg-info/requires.txt b/examples/call/consumer/sdks/test/pulumi_test.egg-info/requires.txt new file mode 100644 index 0000000..3f80a9a --- /dev/null +++ b/examples/call/consumer/sdks/test/pulumi_test.egg-info/requires.txt @@ -0,0 +1,3 @@ +parver>=0.2.1 +pulumi<4.0.0,>=3.0.0 +semver>=2.8.1 diff --git a/examples/call/consumer/sdks/test/pulumi_test.egg-info/top_level.txt b/examples/call/consumer/sdks/test/pulumi_test.egg-info/top_level.txt new file mode 100644 index 0000000..c086e27 --- /dev/null +++ b/examples/call/consumer/sdks/test/pulumi_test.egg-info/top_level.txt @@ -0,0 +1 @@ +pulumi_test diff --git a/examples/call/consumer/sdks/test/pulumi_test/README.md b/examples/call/consumer/sdks/test/pulumi_test/README.md new file mode 100644 index 0000000..e69de29 diff --git a/examples/call/consumer/sdks/test/pulumi_test/__init__.py b/examples/call/consumer/sdks/test/pulumi_test/__init__.py new file mode 100644 index 0000000..df37f7d --- /dev/null +++ b/examples/call/consumer/sdks/test/pulumi_test/__init__.py @@ -0,0 +1,23 @@ +# coding=utf-8 +# *** WARNING: this file was generated by pulumi-language-python. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + +from . import _utilities +import typing +# Export this package's modules as members: +from .provider import * +_utilities.register( + resource_modules=""" +[] +""", + resource_packages=""" +[ + { + "pkg": "test", + "token": "pulumi:providers:test", + "fqn": "pulumi_test", + "class": "Provider" + } +] +""" +) diff --git a/examples/call/consumer/sdks/test/pulumi_test/_utilities.py b/examples/call/consumer/sdks/test/pulumi_test/_utilities.py new file mode 100644 index 0000000..dcf4c01 --- /dev/null +++ b/examples/call/consumer/sdks/test/pulumi_test/_utilities.py @@ -0,0 +1,327 @@ +# coding=utf-8 +# *** WARNING: this file was generated by pulumi-language-python. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + + +import asyncio +import functools +import importlib.metadata +import importlib.util +import inspect +import json +import os +import sys +import typing +import warnings +import base64 + +import pulumi +import pulumi.runtime +from pulumi.runtime.sync_await import _sync_await +from pulumi.runtime.proto import resource_pb2 + +from semver import VersionInfo as SemverVersion +from parver import Version as PEP440Version + +C = typing.TypeVar("C", bound=typing.Callable) + + +def get_env(*args): + for v in args: + value = os.getenv(v) + if value is not None: + return value + return None + + +def get_env_bool(*args): + str = get_env(*args) + if str is not None: + # NOTE: these values are taken from https://golang.org/src/strconv/atob.go?s=351:391#L1, which is what + # Terraform uses internally when parsing boolean values. + if str in ["1", "t", "T", "true", "TRUE", "True"]: + return True + if str in ["0", "f", "F", "false", "FALSE", "False"]: + return False + return None + + +def get_env_int(*args): + str = get_env(*args) + if str is not None: + try: + return int(str) + except: + return None + return None + + +def get_env_float(*args): + str = get_env(*args) + if str is not None: + try: + return float(str) + except: + return None + return None + + +def _get_semver_version(): + # __name__ is set to the fully-qualified name of the current module, In our case, it will be + # ._utilities. is the module we want to query the version for. + root_package, *rest = __name__.split('.') + + # pkg_resources uses setuptools to inspect the set of installed packages. We use it here to ask + # for the currently installed version of the root package (i.e. us) and get its version. + + # Unfortunately, PEP440 and semver differ slightly in incompatible ways. The Pulumi engine expects + # to receive a valid semver string when receiving requests from the language host, so it's our + # responsibility as the library to convert our own PEP440 version into a valid semver string. + + pep440_version_string = importlib.metadata.version(root_package) + pep440_version = PEP440Version.parse(pep440_version_string) + (major, minor, patch) = pep440_version.release + prerelease = None + if pep440_version.pre_tag == 'a': + prerelease = f"alpha.{pep440_version.pre}" + elif pep440_version.pre_tag == 'b': + prerelease = f"beta.{pep440_version.pre}" + elif pep440_version.pre_tag == 'rc': + prerelease = f"rc.{pep440_version.pre}" + elif pep440_version.dev is not None: + prerelease = f"dev.{pep440_version.dev}" + + # The only significant difference between PEP440 and semver as it pertains to us is that PEP440 has explicit support + # for dev builds, while semver encodes them as "prerelease" versions. In order to bridge between the two, we convert + # our dev build version into a prerelease tag. This matches what all of our other packages do when constructing + # their own semver string. + return SemverVersion(major=major, minor=minor, patch=patch, prerelease=prerelease) + + +# Determine the version once and cache the value, which measurably improves program performance. +_version = _get_semver_version() +_version_str = str(_version) + +def get_resource_opts_defaults() -> pulumi.ResourceOptions: + return pulumi.ResourceOptions( + version=get_version(), + plugin_download_url=get_plugin_download_url(), + ) + +def get_invoke_opts_defaults() -> pulumi.InvokeOptions: + return pulumi.InvokeOptions( + version=get_version(), + plugin_download_url=get_plugin_download_url(), + ) + +def get_resource_args_opts(resource_args_type, resource_options_type, *args, **kwargs): + """ + Return the resource args and options given the *args and **kwargs of a resource's + __init__ method. + """ + + resource_args, opts = None, None + + # If the first item is the resource args type, save it and remove it from the args list. + if args and isinstance(args[0], resource_args_type): + resource_args, args = args[0], args[1:] + + # Now look at the first item in the args list again. + # If the first item is the resource options class, save it. + if args and isinstance(args[0], resource_options_type): + opts = args[0] + + # If resource_args is None, see if "args" is in kwargs, and, if so, if it's typed as the + # the resource args type. + if resource_args is None: + a = kwargs.get("args") + if isinstance(a, resource_args_type): + resource_args = a + + # If opts is None, look it up in kwargs. + if opts is None: + opts = kwargs.get("opts") + + return resource_args, opts + + +# Temporary: just use pulumi._utils.lazy_import once everyone upgrades. +def lazy_import(fullname): + + import pulumi._utils as u + f = getattr(u, 'lazy_import', None) + if f is None: + f = _lazy_import_temp + + return f(fullname) + + +# Copied from pulumi._utils.lazy_import, see comments there. +def _lazy_import_temp(fullname): + m = sys.modules.get(fullname, None) + if m is not None: + return m + + spec = importlib.util.find_spec(fullname) + + m = sys.modules.get(fullname, None) + if m is not None: + return m + + loader = importlib.util.LazyLoader(spec.loader) + spec.loader = loader + module = importlib.util.module_from_spec(spec) + + m = sys.modules.get(fullname, None) + if m is not None: + return m + + sys.modules[fullname] = module + loader.exec_module(module) + return module + + +class Package(pulumi.runtime.ResourcePackage): + def __init__(self, pkg_info): + super().__init__() + self.pkg_info = pkg_info + + def version(self): + return _version + + def construct_provider(self, name: str, typ: str, urn: str) -> pulumi.ProviderResource: + if typ != self.pkg_info['token']: + raise Exception(f"unknown provider type {typ}") + Provider = getattr(lazy_import(self.pkg_info['fqn']), self.pkg_info['class']) + return Provider(name, pulumi.ResourceOptions(urn=urn)) + + +class Module(pulumi.runtime.ResourceModule): + def __init__(self, mod_info): + super().__init__() + self.mod_info = mod_info + + def version(self): + return _version + + def construct(self, name: str, typ: str, urn: str) -> pulumi.Resource: + class_name = self.mod_info['classes'].get(typ, None) + + if class_name is None: + raise Exception(f"unknown resource type {typ}") + + TheClass = getattr(lazy_import(self.mod_info['fqn']), class_name) + return TheClass(name, pulumi.ResourceOptions(urn=urn)) + + +def register(resource_modules, resource_packages): + resource_modules = json.loads(resource_modules) + resource_packages = json.loads(resource_packages) + + for pkg_info in resource_packages: + pulumi.runtime.register_resource_package(pkg_info['pkg'], Package(pkg_info)) + + for mod_info in resource_modules: + pulumi.runtime.register_resource_module( + mod_info['pkg'], + mod_info['mod'], + Module(mod_info)) + + +_F = typing.TypeVar('_F', bound=typing.Callable[..., typing.Any]) + + +def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]: + """Decorator internally used on {fn}_output lifted function versions + to implement them automatically from the un-lifted function.""" + + func_sig = inspect.signature(func) + + def lifted_func(*args, opts=None, **kwargs): + bound_args = func_sig.bind(*args, **kwargs) + # Convert tuple to list, see pulumi/pulumi#8172 + args_list = list(bound_args.args) + return pulumi.Output.from_input({ + 'args': args_list, + 'kwargs': bound_args.kwargs + }).apply(lambda resolved_args: func(*resolved_args['args'], + opts=opts, + **resolved_args['kwargs'])) + + return (lambda _: lifted_func) + + +def call_plain( + tok: str, + props: pulumi.Inputs, + res: typing.Optional[pulumi.Resource] = None, + typ: typing.Optional[type] = None, +) -> typing.Any: + """ + Wraps pulumi.runtime.plain to force the output and return it plainly. + """ + + output = pulumi.runtime.call(tok, props, res, typ) + + # Ingoring deps silently. They are typically non-empty, r.f() calls include r as a dependency. + result, known, secret, _ = _sync_await(asyncio.ensure_future(_await_output(output))) + + problem = None + if not known: + problem = ' an unknown value' + elif secret: + problem = ' a secret value' + + if problem: + raise AssertionError( + f"Plain resource method '{tok}' incorrectly returned {problem}. " + + "This is an error in the provider, please report this to the provider developer." + ) + + return result + + +async def _await_output(o: pulumi.Output[typing.Any]) -> typing.Tuple[object, bool, bool, set]: + return ( + await o._future, + await o._is_known, + await o._is_secret, + await o._resources, + ) + + +# This is included to provide an upgrade path for users who are using a version +# of the Pulumi SDK (<3.121.0) that does not include the `deprecated` decorator. +def deprecated(message: str) -> typing.Callable[[C], C]: + """ + Decorator to indicate a function is deprecated. + + As well as inserting appropriate statements to indicate that the function is + deprecated, this decorator also tags the function with a special attribute + so that Pulumi code can detect that it is deprecated and react appropriately + in certain situations. + + message is the deprecation message that should be printed if the function is called. + """ + + def decorator(fn: C) -> C: + if not callable(fn): + raise TypeError("Expected fn to be callable") + + @functools.wraps(fn) + def deprecated_fn(*args, **kwargs): + warnings.warn(message) + pulumi.warn(f"{fn.__name__} is deprecated: {message}") + + return fn(*args, **kwargs) + + deprecated_fn.__dict__["_pulumi_deprecated_callable"] = fn + return typing.cast(C, deprecated_fn) + + return decorator + +def get_plugin_download_url(): + return None + +def get_version(): + return _version_str diff --git a/examples/call/consumer/sdks/test/pulumi_test/provider.py b/examples/call/consumer/sdks/test/pulumi_test/provider.py new file mode 100644 index 0000000..c650574 --- /dev/null +++ b/examples/call/consumer/sdks/test/pulumi_test/provider.py @@ -0,0 +1,90 @@ +# coding=utf-8 +# *** WARNING: this file was generated by pulumi-language-python. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + +import copy +import warnings +import pulumi +import pulumi.runtime +from typing import Any, Mapping, Optional, Sequence, Union, overload +from . import _utilities + +__all__ = ['ProviderArgs', 'Provider'] + +@pulumi.input_type +class ProviderArgs: + def __init__(__self__): + """ + The set of arguments for constructing a Provider resource. + """ + pass + + +class Provider(pulumi.ProviderResource): + @overload + def __init__(__self__, + resource_name: str, + opts: Optional[pulumi.ResourceOptions] = None, + __props__=None): + """ + Create a Test resource with the given unique name, props, and options. + :param str resource_name: The name of the resource. + :param pulumi.ResourceOptions opts: Options for the resource. + """ + ... + @overload + def __init__(__self__, + resource_name: str, + args: Optional[ProviderArgs] = None, + opts: Optional[pulumi.ResourceOptions] = None): + """ + Create a Test resource with the given unique name, props, and options. + :param str resource_name: The name of the resource. + :param ProviderArgs args: The arguments to use to populate this resource's properties. + :param pulumi.ResourceOptions opts: Options for the resource. + """ + ... + def __init__(__self__, resource_name: str, *args, **kwargs): + resource_args, opts = _utilities.get_resource_args_opts(ProviderArgs, pulumi.ResourceOptions, *args, **kwargs) + if resource_args is not None: + __self__._internal_init(resource_name, opts, **resource_args.__dict__) + else: + __self__._internal_init(resource_name, *args, **kwargs) + + def _internal_init(__self__, + resource_name: str, + opts: Optional[pulumi.ResourceOptions] = None, + __props__=None): + opts = pulumi.ResourceOptions.merge(_utilities.get_resource_opts_defaults(), opts) + if not isinstance(opts, pulumi.ResourceOptions): + raise TypeError('Expected resource options to be a ResourceOptions instance') + if opts.id is None: + if __props__ is not None: + raise TypeError('__props__ is only valid when passed in combination with a valid opts.id to get an existing resource') + __props__ = ProviderArgs.__new__(ProviderArgs) + + super(Provider, __self__).__init__( + 'test', + resource_name, + __props__, + opts) + + @pulumi.output_type + class MakePetResult: + def __init__(__self__, success=None): + if success and not isinstance(success, bool): + raise TypeError("Expected argument 'success' to be a bool") + pulumi.set(__self__, "success", success) + + @property + @pulumi.getter + def success(self) -> Optional[bool]: + return pulumi.get(self, "success") + + def make_pet(__self__, *, + prefix: Optional[pulumi.Input[str]] = None) -> pulumi.Output['Provider.MakePetResult']: + __args__ = dict() + __args__['__self__'] = __self__ + __args__['prefix'] = prefix + return pulumi.runtime.call('pulumi:providers:test/makePet', __args__, res=__self__, typ=Provider.MakePetResult) + diff --git a/examples/call/consumer/sdks/test/pulumi_test/pulumi-plugin.json b/examples/call/consumer/sdks/test/pulumi_test/pulumi-plugin.json new file mode 100644 index 0000000..7fd8a3f --- /dev/null +++ b/examples/call/consumer/sdks/test/pulumi_test/pulumi-plugin.json @@ -0,0 +1,4 @@ +{ + "resource": true, + "name": "test" +} diff --git a/examples/call/consumer/sdks/test/pulumi_test/py.typed b/examples/call/consumer/sdks/test/pulumi_test/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/examples/call/consumer/sdks/test/setup.py b/examples/call/consumer/sdks/test/setup.py new file mode 100644 index 0000000..0e0667b --- /dev/null +++ b/examples/call/consumer/sdks/test/setup.py @@ -0,0 +1,37 @@ +# coding=utf-8 +# *** WARNING: this file was generated by pulumi-language-python. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + +import errno +from setuptools import setup, find_packages +from setuptools.command.install import install +from subprocess import check_call + + +VERSION = "0.0.0" +def readme(): + try: + with open('README.md', encoding='utf-8') as f: + return f.read() + except FileNotFoundError: + return "test Pulumi Package - Development Version" + + +setup(name='pulumi_test', + python_requires='>=3.8', + version=VERSION, + long_description=readme(), + long_description_content_type='text/markdown', + packages=find_packages(), + package_data={ + 'pulumi_test': [ + 'py.typed', + 'pulumi-plugin.json', + ] + }, + install_requires=[ + 'parver>=0.2.1', + 'pulumi>=3.0.0,<4.0.0', + 'semver>=2.8.1' + ], + zip_safe=False) diff --git a/examples/call/provider.go b/examples/call/provider.go new file mode 100644 index 0000000..8b65b66 --- /dev/null +++ b/examples/call/provider.go @@ -0,0 +1,115 @@ +// Copyright 2023-2024, 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. + +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + + p "github.com/pulumi/pulumi-go-provider" + random "github.com/pulumi/pulumi-random/sdk/v4/go/random" + "github.com/pulumi/pulumi/pkg/v3/codegen/schema" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func main() { + err := p.RunProvider("test", "1.0.0", provider) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } +} + +var provider = p.Provider{ + Call: func(_ context.Context, req p.CallRequest) (p.CallResponse, error) { + switch req.Tok { + case callMakePetToken: + return callMakePet(req) + default: + return p.CallResponse{}, fmt.Errorf("unknown token") + } + }, + GetSchema: func(context.Context, p.GetSchemaRequest) (p.GetSchemaResponse, error) { + b, err := json.Marshal(pkgSchema) + return p.GetSchemaResponse{Schema: string(b)}, err + }, +} + +var pkgSchema = schema.PackageSpec{ + Name: "test", + Provider: schema.ResourceSpec{ + Methods: map[string]string{ + "makePet": callMakePetToken, + }, + }, + AllowedPackageNames: []string{"pulumi"}, // Allow extending the Provider resource with a method + Functions: map[string]schema.FunctionSpec{ + callMakePetToken: { + Inputs: &schema.ObjectTypeSpec{ + Properties: map[string]schema.PropertySpec{ + "__self__": schema.PropertySpec{ + TypeSpec: schema.TypeSpec{Type: "string"}, + }, + "prefix": schema.PropertySpec{ + TypeSpec: schema.TypeSpec{ + Type: "string", + }, + Default: "pre-", + }, + }, + }, + Outputs: &schema.ObjectTypeSpec{ + Properties: map[string]schema.PropertySpec{ + "success": schema.PropertySpec{ + TypeSpec: schema.TypeSpec{ + Type: "boolean", + }, + }, + }, + }, + }, + }, +} + +const callMakePetToken = "pulumi:providers:test/makePet" + +func callMakePet(req p.CallRequest) (p.CallResponse, error) { + args := req.Args + prefix, ok := args["prefix"] + if !ok { + return p.CallResponse{ + Failures: []p.CheckFailure{ + {Property: "prefix", Reason: "missing"}, + }, + }, nil + } + if !prefix.IsString() { + return p.CallResponse{}, fmt.Errorf("unexpected type for prefix arg: %q", prefix.TypeString()) + } + _, err := random.NewRandomPet(req.Context, "call", &random.RandomPetArgs{ + Prefix: pulumi.String(prefix.StringValue()), + }) + if err != nil { + return p.CallResponse{}, err + } + return p.CallResponse{ + Return: resource.PropertyMap{ + "success": resource.NewProperty(true), + }, + }, nil +} diff --git a/examples/credentials/go.mod b/examples/credentials/go.mod index 8ef5592..480c629 100644 --- a/examples/credentials/go.mod +++ b/examples/credentials/go.mod @@ -50,6 +50,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect diff --git a/examples/credentials/go.sum b/examples/credentials/go.sum index 539164f..0c5c9b3 100644 --- a/examples/credentials/go.sum +++ b/examples/credentials/go.sum @@ -125,6 +125,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= diff --git a/examples/dna-store/go.mod b/examples/dna-store/go.mod index c15b09b..a2abb99 100644 --- a/examples/dna-store/go.mod +++ b/examples/dna-store/go.mod @@ -47,6 +47,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect diff --git a/examples/dna-store/go.sum b/examples/dna-store/go.sum index fdd9b8d..637c460 100644 --- a/examples/dna-store/go.sum +++ b/examples/dna-store/go.sum @@ -125,6 +125,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= diff --git a/examples/file/go.mod b/examples/file/go.mod index 151673d..fef766b 100644 --- a/examples/file/go.mod +++ b/examples/file/go.mod @@ -50,6 +50,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect diff --git a/examples/file/go.sum b/examples/file/go.sum index 539164f..0c5c9b3 100644 --- a/examples/file/go.sum +++ b/examples/file/go.sum @@ -125,6 +125,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= diff --git a/examples/random-login/go.mod b/examples/random-login/go.mod index 021d8a4..03543d9 100644 --- a/examples/random-login/go.mod +++ b/examples/random-login/go.mod @@ -7,7 +7,7 @@ go 1.21 require ( github.com/blang/semver v3.5.1+incompatible github.com/pulumi/pulumi-go-provider v0.0.0-00010101000000-000000000000 - github.com/pulumi/pulumi-random/sdk/v4 v4.8.0 + github.com/pulumi/pulumi-random/sdk/v4 v4.16.3 github.com/pulumi/pulumi/sdk/v3 v3.126.0 github.com/stretchr/testify v1.9.0 ) @@ -53,6 +53,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect diff --git a/examples/random-login/go.sum b/examples/random-login/go.sum index 3baf7c6..27f08b7 100644 --- a/examples/random-login/go.sum +++ b/examples/random-login/go.sum @@ -125,6 +125,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -158,8 +160,8 @@ github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 h1:vkHw5I/plNdTr435 github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231/go.mod h1:murToZ2N9hNJzewjHBgfFdXhZKjY3z5cYC1VXk+lbFE= github.com/pulumi/esc v0.9.1 h1:HH5eEv8sgyxSpY5a8yePyqFXzA8cvBvapfH8457+mIs= github.com/pulumi/esc v0.9.1/go.mod h1:oEJ6bOsjYlQUpjf70GiX+CXn3VBmpwFDxUTlmtUN84c= -github.com/pulumi/pulumi-random/sdk/v4 v4.8.0 h1:zaSdfNqcaRqKld2jTB+YZIN7O4BsQ2m/AEFx7qNZJUY= -github.com/pulumi/pulumi-random/sdk/v4 v4.8.0/go.mod h1:czSwj+jZnn/VWovMpTLUs/RL/ZS4PFHRdmlXrkvHqeI= +github.com/pulumi/pulumi-random/sdk/v4 v4.16.3 h1:nlN42MRSIuDh5Pc5nLq4b0lwZaX2ZUAW67Nw+OlNOig= +github.com/pulumi/pulumi-random/sdk/v4 v4.16.3/go.mod h1:yRfWJSLEAVZvkwgXajr3S9OmFkAZTxfO44Ef2HfixXQ= github.com/pulumi/pulumi/pkg/v3 v3.126.0 h1:XaZU1ehjHN2I5ihkfwxK/UFMDiCDM9FSt2TBnbldAx4= github.com/pulumi/pulumi/pkg/v3 v3.126.0/go.mod h1:1P4/oK9zceOJUm48QQl/TqjDN68lfsdnTR1FITTFddw= github.com/pulumi/pulumi/sdk/v3 v3.126.0 h1:6GQVhwG2jgnG7wjRiWgrq0/sU39onctAiBcvTlqb20s= diff --git a/examples/str/go.mod b/examples/str/go.mod index 60e07ed..8b554c4 100644 --- a/examples/str/go.mod +++ b/examples/str/go.mod @@ -52,6 +52,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect diff --git a/examples/str/go.sum b/examples/str/go.sum index ddf28fe..2862a5e 100644 --- a/examples/str/go.sum +++ b/examples/str/go.sum @@ -125,6 +125,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= diff --git a/go.mod b/go.mod index d52c07d..847832c 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/blang/semver v3.5.1+incompatible github.com/mitchellh/mapstructure v1.5.0 + github.com/pulumi/pulumi-random/sdk/v4 v4.16.3 github.com/pulumi/pulumi/pkg/v3 v3.126.0 github.com/pulumi/pulumi/sdk/v3 v3.126.0 google.golang.org/grpc v1.63.2 diff --git a/go.sum b/go.sum index d774118..d1f00fe 100644 --- a/go.sum +++ b/go.sum @@ -160,6 +160,8 @@ github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 h1:vkHw5I/plNdTr435 github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231/go.mod h1:murToZ2N9hNJzewjHBgfFdXhZKjY3z5cYC1VXk+lbFE= github.com/pulumi/esc v0.9.1 h1:HH5eEv8sgyxSpY5a8yePyqFXzA8cvBvapfH8457+mIs= github.com/pulumi/esc v0.9.1/go.mod h1:oEJ6bOsjYlQUpjf70GiX+CXn3VBmpwFDxUTlmtUN84c= +github.com/pulumi/pulumi-random/sdk/v4 v4.16.3 h1:nlN42MRSIuDh5Pc5nLq4b0lwZaX2ZUAW67Nw+OlNOig= +github.com/pulumi/pulumi-random/sdk/v4 v4.16.3/go.mod h1:yRfWJSLEAVZvkwgXajr3S9OmFkAZTxfO44Ef2HfixXQ= github.com/pulumi/pulumi/pkg/v3 v3.126.0 h1:XaZU1ehjHN2I5ihkfwxK/UFMDiCDM9FSt2TBnbldAx4= github.com/pulumi/pulumi/pkg/v3 v3.126.0/go.mod h1:1P4/oK9zceOJUm48QQl/TqjDN68lfsdnTR1FITTFddw= github.com/pulumi/pulumi/sdk/v3 v3.126.0 h1:6GQVhwG2jgnG7wjRiWgrq0/sU39onctAiBcvTlqb20s= diff --git a/infer/tests/go.mod b/infer/tests/go.mod index 53901b7..5edccbb 100644 --- a/infer/tests/go.mod +++ b/infer/tests/go.mod @@ -53,6 +53,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect diff --git a/infer/tests/go.sum b/infer/tests/go.sum index ddf28fe..2862a5e 100644 --- a/infer/tests/go.sum +++ b/infer/tests/go.sum @@ -125,6 +125,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= diff --git a/provider.go b/provider.go index abbca83..916f29f 100644 --- a/provider.go +++ b/provider.go @@ -44,6 +44,7 @@ import ( "google.golang.org/protobuf/types/known/structpb" "github.com/pulumi/pulumi-go-provider/internal/key" + "github.com/pulumi/pulumi-go-provider/resourcex" ) type GetSchemaRequest struct { @@ -342,7 +343,9 @@ type Provider struct { Read func(context.Context, ReadRequest) (ReadResponse, error) Update func(context.Context, UpdateRequest) (UpdateResponse, error) Delete func(context.Context, DeleteRequest) error - // TODO Call + + // Call ... + Call func(context.Context, CallRequest) (CallResponse, error) // Components Resources Construct func(context.Context, ConstructRequest) (ConstructResponse, error) @@ -420,6 +423,11 @@ func (d Provider) WithDefaults() Provider { return nyi("Delete") } } + if d.Call == nil { + d.Call = func(context.Context, CallRequest) (CallResponse, error) { + return CallResponse{}, nyi("Call") + } + } if d.Construct == nil { d.Construct = func(context.Context, ConstructRequest) (ConstructResponse, error) { return ConstructResponse{}, nyi("Construct") @@ -718,8 +726,78 @@ func (p *provider) StreamInvoke(*rpc.InvokeRequest, rpc.ResourceProvider_StreamI return status.Error(codes.Unimplemented, "StreamInvoke is not yet implemented") } -func (p *provider) Call(context.Context, *rpc.CallRequest) (*rpc.CallResponse, error) { - return nil, status.Error(codes.Unimplemented, "Call is not yet implemented") +func (p *provider) Call(ctx context.Context, req *rpc.CallRequest) (*rpc.CallResponse, error) { + + configPropertyMap := make(presource.PropertyMap, len(req.GetConfig())) + for k, v := range req.GetConfig() { + configPropertyMap[presource.PropertyKey(k)] = presource.NewProperty(v) + } + pulumiContext, err := pulumi.NewContext(ctx, pulumi.RunInfo{ + Project: req.GetProject(), + Stack: req.GetStack(), + Config: req.GetConfig(), + ConfigSecretKeys: req.GetConfigSecretKeys(), + ConfigPropertyMap: configPropertyMap, + Parallel: int(req.GetParallel()), + DryRun: req.GetDryRun(), + MonitorAddr: req.GetMonitorEndpoint(), + Organization: req.GetOrganization(), + }) + if err != nil { + return nil, fmt.Errorf("failed to build pulumi.Context: %w", err) + } + + args, err := p.getMap(req.GetArgs()) + if err != nil { + return nil, fmt.Errorf("unable to convert args into a property map") + } + + resp, err := p.client.Call(ctx, CallRequest{ + Tok: tokens.ModuleMember(req.GetTok()), + Args: args, + Context: pulumiContext, + }) + if err != nil { + return nil, err + } + + returnDependencies := map[string]*rpc.CallResponse_ReturnDependencies{} + for name, v := range resp.Return { + var urns []string + resourcex.Walk(v, func(v presource.PropertyValue, state resourcex.WalkState) { + if state.Entering || !v.IsOutput() { + return + } + for _, dep := range v.OutputValue().Dependencies { + urns = append(urns, string(dep)) + } + }) + + returnDependencies[string(name)] = &rpc.CallResponse_ReturnDependencies{Urns: urns} + } + + _return, err := p.asStruct(resp.Return) + if err != nil { + return nil, err + } + + return &rpc.CallResponse{ + Return: _return, + ReturnDependencies: returnDependencies, + Failures: checkFailureList(resp.Failures).rpc(), + }, nil +} + +type CallRequest struct { + Tok tokens.ModuleMember + Args presource.PropertyMap + Context *pulumi.Context +} +type CallResponse struct { + // The returned values, if the call was successful. + Return presource.PropertyMap + // The failures if any arguments didn't pass verification. + Failures []CheckFailure } func (p *provider) Check(ctx context.Context, req *rpc.CheckRequest) (*rpc.CheckResponse, error) { diff --git a/resourcex/traverse.go b/resourcex/traverse.go index 325116a..ae73ecf 100644 --- a/resourcex/traverse.go +++ b/resourcex/traverse.go @@ -73,3 +73,47 @@ func Traverse(v resource.PropertyValue, p resource.PropertyPath, f func(resource } } } + +// Walk traverses a property value along all paths, performing a depth first search. +func Walk(v resource.PropertyValue, f func(resource.PropertyValue, WalkState)) { + walk(v, f, WalkState{}) +} + +type WalkState struct { + // We are entering the value. The children of the value have not yet been visited. + Entering bool + // The value is not transitively computed. + IsKnown bool + // The value is not transitively secret. + IsSecret bool +} + +func walk(v resource.PropertyValue, f func(resource.PropertyValue, WalkState), state WalkState) { + state.Entering = true + f(v, state) + switch { + case v.IsObject(): + for _, v := range v.ObjectValue() { + walk(v, f, state) + } + case v.IsArray(): + for _, v := range v.ArrayValue() { + walk(v, f, state) + } + case v.IsSecret(): + elemState := state + state.IsSecret = true + walk(v.SecretValue().Element, f, elemState) + case v.IsOutput(): + elemState := state + state.IsSecret = v.OutputValue().Secret + state.IsKnown = v.OutputValue().Known + walk(v.OutputValue().Element, f, elemState) + case v.IsComputed(): + elemState := state + state.IsKnown = false + walk(v.V.(resource.Computed).Element, f, elemState) + } + state.Entering = false + f(v, state) +} diff --git a/tests/go.mod b/tests/go.mod index f4fa11b..6744de6 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -8,6 +8,7 @@ require ( github.com/blang/semver v3.5.1+incompatible github.com/pulumi/providertest v0.0.12 github.com/pulumi/pulumi-go-provider v0.0.0-00010101000000-000000000000 + github.com/pulumi/pulumi-random/sdk/v4 v4.16.3 github.com/pulumi/pulumi/pkg/v3 v3.126.0 github.com/pulumi/pulumi/sdk/v3 v3.126.0 github.com/stretchr/testify v1.9.0 @@ -57,6 +58,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect diff --git a/tests/go.sum b/tests/go.sum index 96e27da..d01ec1e 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -126,6 +126,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -162,6 +164,8 @@ github.com/pulumi/esc v0.9.1 h1:HH5eEv8sgyxSpY5a8yePyqFXzA8cvBvapfH8457+mIs= github.com/pulumi/esc v0.9.1/go.mod h1:oEJ6bOsjYlQUpjf70GiX+CXn3VBmpwFDxUTlmtUN84c= github.com/pulumi/providertest v0.0.12 h1:UjcFQHHs4AGJyJqxhvC2q8yVQ7Li+UyCyP95HZcK03U= github.com/pulumi/providertest v0.0.12/go.mod h1:REAoaN+hGOtdWJGirfWYqcSjCejlbGfzyVTUuemJTuE= +github.com/pulumi/pulumi-random/sdk/v4 v4.16.3 h1:nlN42MRSIuDh5Pc5nLq4b0lwZaX2ZUAW67Nw+OlNOig= +github.com/pulumi/pulumi-random/sdk/v4 v4.16.3/go.mod h1:yRfWJSLEAVZvkwgXajr3S9OmFkAZTxfO44Ef2HfixXQ= github.com/pulumi/pulumi/pkg/v3 v3.126.0 h1:XaZU1ehjHN2I5ihkfwxK/UFMDiCDM9FSt2TBnbldAx4= github.com/pulumi/pulumi/pkg/v3 v3.126.0/go.mod h1:1P4/oK9zceOJUm48QQl/TqjDN68lfsdnTR1FITTFddw= github.com/pulumi/pulumi/sdk/v3 v3.126.0 h1:6GQVhwG2jgnG7wjRiWgrq0/sU39onctAiBcvTlqb20s= diff --git a/tests/grpc/call/provider.go b/tests/grpc/call/provider.go new file mode 100644 index 0000000..0479e89 --- /dev/null +++ b/tests/grpc/call/provider.go @@ -0,0 +1,111 @@ +// Copyright 2023-2024, 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. + +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + + p "github.com/pulumi/pulumi-go-provider" + random "github.com/pulumi/pulumi-random/sdk/v4/go/random" + "github.com/pulumi/pulumi/pkg/v3/codegen/schema" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func main() { + err := p.RunProvider("test", "1.0.0", provider) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } +} + +var provider = p.Provider{ + Call: func(_ context.Context, req p.CallRequest) (p.CallResponse, error) { + switch req.Tok { + case "pulumi:package:Provider/makePet": + return callMakePet(req) + default: + return p.CallResponse{}, fmt.Errorf("unknown token") + } + }, + GetSchema: func(context.Context, p.GetSchemaRequest) (p.GetSchemaResponse, error) { + b, err := json.Marshal(pkgSchema) + return p.GetSchemaResponse{Schema: string(b)}, err + }, +} + +var pkgSchema = schema.PackageSpec{ + Name: "test", + Provider: schema.ResourceSpec{ + Methods: map[string]string{ + "makePet": "test:index:callMakePet", + }, + }, + Functions: map[string]schema.FunctionSpec{ + "test:index:callMakePet": { + Inputs: &schema.ObjectTypeSpec{ + Properties: map[string]schema.PropertySpec{ + "prefix": schema.PropertySpec{ + TypeSpec: schema.TypeSpec{ + Type: "string", + }, + Default: "pre-", + }, + }, + }, + Outputs: &schema.ObjectTypeSpec{ + Properties: map[string]schema.PropertySpec{ + "success": schema.PropertySpec{ + TypeSpec: schema.TypeSpec{ + Type: "bool", + }, + }, + }, + }, + }, + }, +} + +func callMakePet(req p.CallRequest) (p.CallResponse, error) { + args := req.Args + prefix, ok := args["prefix"] + if !ok { + return p.CallResponse{ + Failures: []p.CheckFailure{ + {Property: "prefix", Reason: "missing"}, + }, + }, nil + } + if !prefix.IsString() { + return p.CallResponse{}, fmt.Errorf("unexpected type for prefix arg: %q", prefix.TypeString()) + } + _, err := random.NewRandomPet(req.Context, "call", &random.RandomPetArgs{ + Prefix: pulumi.String(prefix.StringValue()), + }) + if err != nil { + return p.CallResponse{}, err + } + return p.CallResponse{ + Return: resource.PropertyMap{ + "success": resource.NewProperty(true), + }, + }, nil +} + +func Provider() p.Provider { return provider }