Add python aliases support. (#2974)
This commit is contained in:
parent
c6916051f0
commit
237f8d2222
|
@ -9,6 +9,11 @@ CHANGELOG
|
||||||
- Fix unexpected provider replacements when upgrading from older CLIs and older providers
|
- Fix unexpected provider replacements when upgrading from older CLIs and older providers
|
||||||
[pulumi/pulumi-kubernetes#645](https://github.com/pulumi/pulumi-kubernetes/issues/645)
|
[pulumi/pulumi-kubernetes#645](https://github.com/pulumi/pulumi-kubernetes/issues/645)
|
||||||
|
|
||||||
|
- Add *Python* support for renaming resources via the `aliases` resource option. Adding aliases
|
||||||
|
allows new resources to match resources from previous deployments which used different names,
|
||||||
|
maintaining the identity of the resource and avoiding replacements or re-creation of the resource.
|
||||||
|
This was previously added to the *JavaScript* sdk in 0.17.15.
|
||||||
|
|
||||||
## 0.17.25 (2019-07-19)
|
## 0.17.25 (2019-07-19)
|
||||||
|
|
||||||
- Support for Dynamic Providers in Python [#2900](https://github.com/pulumi/pulumi/pull/2900)
|
- Support for Dynamic Providers in Python [#2900](https://github.com/pulumi/pulumi/pull/2900)
|
||||||
|
|
|
@ -53,12 +53,15 @@ from .metadata import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from .resource import (
|
from .resource import (
|
||||||
|
Alias,
|
||||||
Resource,
|
Resource,
|
||||||
CustomResource,
|
CustomResource,
|
||||||
ComponentResource,
|
ComponentResource,
|
||||||
ProviderResource,
|
ProviderResource,
|
||||||
ResourceOptions,
|
ResourceOptions,
|
||||||
|
create_urn,
|
||||||
export,
|
export,
|
||||||
|
ROOT_STACK_RESOURCE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .output import (
|
from .output import (
|
||||||
|
|
|
@ -15,12 +15,18 @@
|
||||||
"""The Resource module, containing all resource-related definitions."""
|
"""The Resource module, containing all resource-related definitions."""
|
||||||
from typing import Optional, List, Any, Mapping, Union, TYPE_CHECKING
|
from typing import Optional, List, Any, Mapping, Union, TYPE_CHECKING
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
from .runtime import known_types
|
from .runtime import known_types
|
||||||
from .runtime.resource import register_resource, register_resource_outputs, read_resource
|
from .runtime.resource import register_resource, register_resource_outputs, read_resource
|
||||||
from .runtime.settings import get_root_resource
|
from .runtime.settings import get_root_resource
|
||||||
|
|
||||||
|
from .metadata import get_project, get_stack
|
||||||
|
|
||||||
|
from .output import Output
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .output import Output, Inputs
|
from .output import Input, Inputs
|
||||||
|
|
||||||
|
|
||||||
class CustomTimeouts:
|
class CustomTimeouts:
|
||||||
|
@ -49,6 +55,152 @@ class CustomTimeouts:
|
||||||
self.delete = delete
|
self.delete = delete
|
||||||
|
|
||||||
|
|
||||||
|
def inherited_child_alias(
|
||||||
|
child_name: str,
|
||||||
|
parent_name: str,
|
||||||
|
parent_alias: 'Input[str]',
|
||||||
|
child_type: str) -> 'Output[str]':
|
||||||
|
"""
|
||||||
|
inherited_child_alias computes the alias that should be applied to a child based on an alias
|
||||||
|
applied to it's parent. This may involve changing the name of the resource in cases where the
|
||||||
|
resource has a named derived from the name of the parent, and the parent name changed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If the child name has the parent name as a prefix, then we make the assumption that it was
|
||||||
|
# constructed from the convention of using `{name}-details` as the name of the child resource. To
|
||||||
|
# ensure this is aliased correctly, we must then also replace the parent aliases name in the prefix of
|
||||||
|
# the child resource name.
|
||||||
|
#
|
||||||
|
# For example:
|
||||||
|
# * name: "newapp-function"
|
||||||
|
# * opts.parent.__name: "newapp"
|
||||||
|
# * parentAlias: "urn:pulumi:stackname::projectname::awsx:ec2:Vpc::app"
|
||||||
|
# * parentAliasName: "app"
|
||||||
|
# * aliasName: "app-function"
|
||||||
|
# * childAlias: "urn:pulumi:stackname::projectname::aws:s3/bucket:Bucket::app-function"
|
||||||
|
alias_name = Output.from_input(child_name)
|
||||||
|
if child_name.startswith(parent_name):
|
||||||
|
alias_name = Output.from_input(parent_alias).apply(
|
||||||
|
lambda u: u[u.rfind("::") + 2:] + child_name[len(parent_name):])
|
||||||
|
|
||||||
|
return create_urn(alias_name, child_type, parent_alias)
|
||||||
|
|
||||||
|
|
||||||
|
ROOT_STACK_RESOURCE = None
|
||||||
|
"""
|
||||||
|
Constant to represent the 'root stack' resource for a Pulumi application. The purpose of this is
|
||||||
|
solely to make it easy to write an [Alias] like so:
|
||||||
|
|
||||||
|
`aliases=[Alias(parent=pulumi.ROOT_STACK_RESOURCE)]`.
|
||||||
|
|
||||||
|
This indicates that the prior name for a resource was created based on it being parented directly by
|
||||||
|
the stack itself and no other resources. Note: this is equivalent to:
|
||||||
|
|
||||||
|
`aliases=[Alias(parent=None)]`
|
||||||
|
|
||||||
|
However, the former form is preferable as it is more self-descriptive, while the latter may look a
|
||||||
|
bit confusing and may incorrectly look like something that could be removed without changing
|
||||||
|
semantics.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Alias:
|
||||||
|
"""
|
||||||
|
Alias is a partial description of prior named used for a resource. It can be processed in the
|
||||||
|
context of a resource creation to determine what the full aliased URN would be.
|
||||||
|
|
||||||
|
Note there is a semantic difference between attributes being given the `None` value and
|
||||||
|
attributes not being given at all. Specifically, there is a difference between:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
Alias(name="foo", parent=None) # and
|
||||||
|
Alias(name="foo")
|
||||||
|
```
|
||||||
|
|
||||||
|
So the first alias means "the original urn had no parent" while the second alias means "use the
|
||||||
|
current parent".
|
||||||
|
|
||||||
|
Note: to indicate that a resource was previously parented by the root stack, it is recommended
|
||||||
|
that you use:
|
||||||
|
|
||||||
|
`aliases=[Alias(parent=pulumi.ROOT_STACK_RESOURCE)]`
|
||||||
|
|
||||||
|
This form is self-descriptive and makes the intent clearer than using:
|
||||||
|
|
||||||
|
`aliases=[Alias(parent=None)]`
|
||||||
|
"""
|
||||||
|
|
||||||
|
name: Optional[str]
|
||||||
|
"""
|
||||||
|
The previous name of the resource. If not provided, the current name of the resource is used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
type_: Optional[str]
|
||||||
|
"""
|
||||||
|
The previous type of the resource. If not provided, the current type of the resource is used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
parent: Optional[Union['Resource', 'Input[str]']]
|
||||||
|
"""
|
||||||
|
The previous parent of the resource. If not provided (i.e. `Alias(name="foo")`), the current
|
||||||
|
parent of the resource is used (`opts.parent` if provided, else the implicit stack resource
|
||||||
|
parent).
|
||||||
|
|
||||||
|
To specify no original parent, use `Alias(parent=pulumi.rootStackResource)`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
stack: Optional['Input[str]']
|
||||||
|
"""
|
||||||
|
The name of the previous stack of the resource. If not provided, defaults to `pulumi.getStack()`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
project: Optional['Input[str]']
|
||||||
|
"""
|
||||||
|
The previous project of the resource. If not provided, defaults to `pulumi.getProject()`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
name: Optional[str] = ...,
|
||||||
|
type_: Optional[str] = ...,
|
||||||
|
parent: Optional[Union['Resource', 'Input[str]']] = ...,
|
||||||
|
stack: Optional['Input[str]'] = ...,
|
||||||
|
project: Optional['Input[str]'] = ...) -> None:
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
self.type_ = type_
|
||||||
|
self.parent = parent
|
||||||
|
self.stack = stack
|
||||||
|
self.project = project
|
||||||
|
|
||||||
|
|
||||||
|
def collapse_alias_to_urn(
|
||||||
|
alias: 'Input[Union[Alias, str]]',
|
||||||
|
defaultName: str,
|
||||||
|
defaultType: str,
|
||||||
|
defaultParent: Optional['Resource']) -> 'Output[str]':
|
||||||
|
"""
|
||||||
|
collapse_alias_to_urn turns an Alias into a URN given a set of default data
|
||||||
|
"""
|
||||||
|
|
||||||
|
def collapse_alias_to_urn_worker(inner: Union[Alias, str]) -> 'Output[str]':
|
||||||
|
if isinstance(inner, str):
|
||||||
|
return Output.from_input(inner)
|
||||||
|
|
||||||
|
name = inner.name if inner.name is not ... else defaultName
|
||||||
|
type_ = inner.type_ if inner.type_ is not ... else defaultType
|
||||||
|
parent = inner.parent if inner.parent is not ... else defaultParent
|
||||||
|
project = inner.project if inner.project is not ... else get_project()
|
||||||
|
stack = inner.stack if inner.stack is not ... else get_stack()
|
||||||
|
|
||||||
|
if name is None:
|
||||||
|
raise Exception("No valid 'name' passed in for alias.")
|
||||||
|
|
||||||
|
if type_ is None:
|
||||||
|
raise Exception("No valid 'type_' passed in for alias.")
|
||||||
|
|
||||||
|
return create_urn(name, type_, parent, project, stack)
|
||||||
|
|
||||||
|
return Output.from_input(alias).apply(collapse_alias_to_urn_worker)
|
||||||
|
|
||||||
|
|
||||||
class ResourceOptions:
|
class ResourceOptions:
|
||||||
"""
|
"""
|
||||||
|
@ -99,6 +251,11 @@ class ResourceOptions:
|
||||||
used.
|
used.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
aliases: Optional[List['Input[Union[str, Alias]]']]
|
||||||
|
"""
|
||||||
|
An optional list of aliases to treat this resource as matching.
|
||||||
|
"""
|
||||||
|
|
||||||
additional_secret_outputs: [List[str]]
|
additional_secret_outputs: [List[str]]
|
||||||
"""
|
"""
|
||||||
The names of outputs for this resource that should be treated as secrets. This augments the list that
|
The names of outputs for this resource that should be treated as secrets. This augments the list that
|
||||||
|
@ -133,6 +290,7 @@ class ResourceOptions:
|
||||||
delete_before_replace: Optional[bool] = None,
|
delete_before_replace: Optional[bool] = None,
|
||||||
ignore_changes: Optional[List[str]] = None,
|
ignore_changes: Optional[List[str]] = None,
|
||||||
version: Optional[str] = None,
|
version: Optional[str] = None,
|
||||||
|
aliases: Optional[List['Input[Union[str, Alias]]']] = None,
|
||||||
additional_secret_outputs: Optional[List[str]] = None,
|
additional_secret_outputs: Optional[List[str]] = None,
|
||||||
id: Optional[str] = None,
|
id: Optional[str] = None,
|
||||||
import_: Optional[str] = None,
|
import_: Optional[str] = None,
|
||||||
|
@ -168,6 +326,7 @@ class ResourceOptions:
|
||||||
self.delete_before_replace = delete_before_replace
|
self.delete_before_replace = delete_before_replace
|
||||||
self.ignore_changes = ignore_changes
|
self.ignore_changes = ignore_changes
|
||||||
self.version = version
|
self.version = version
|
||||||
|
self.aliases = aliases
|
||||||
self.additional_secret_outputs = additional_secret_outputs
|
self.additional_secret_outputs = additional_secret_outputs
|
||||||
self.custom_timeouts = custom_timeouts
|
self.custom_timeouts = custom_timeouts
|
||||||
self.id = id
|
self.id = id
|
||||||
|
@ -185,7 +344,8 @@ class Resource:
|
||||||
|
|
||||||
urn: 'Output[str]'
|
urn: 'Output[str]'
|
||||||
"""
|
"""
|
||||||
The stable, logical URN used to distinctly address a resource, both before and after deployments.
|
The stable, logical URN used to distinctly address a resource, both before and after
|
||||||
|
deployments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_providers: Mapping[str, 'ProviderResource']
|
_providers: Mapping[str, 'ProviderResource']
|
||||||
|
@ -198,6 +358,16 @@ class Resource:
|
||||||
When set to true, protect ensures this resource cannot be deleted.
|
When set to true, protect ensures this resource cannot be deleted.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_aliases: 'Input[str]'
|
||||||
|
"""
|
||||||
|
A list of aliases applied to this resource.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_name: str
|
||||||
|
"""
|
||||||
|
The name assigned to the resource at construction.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
t: str,
|
t: str,
|
||||||
name: str,
|
name: str,
|
||||||
|
@ -225,6 +395,11 @@ class Resource:
|
||||||
if opts is None:
|
if opts is None:
|
||||||
opts = ResourceOptions()
|
opts = ResourceOptions()
|
||||||
|
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
# Make a shallow clone of opts to ensure we don't modify the value passed in.
|
||||||
|
opts = copy.copy(opts)
|
||||||
|
|
||||||
self._providers = {}
|
self._providers = {}
|
||||||
# Check the parent type if one exists and fill in any default options.
|
# Check the parent type if one exists and fill in any default options.
|
||||||
if opts.parent is not None:
|
if opts.parent is not None:
|
||||||
|
@ -235,6 +410,15 @@ class Resource:
|
||||||
if opts.protect is None:
|
if opts.protect is None:
|
||||||
opts.protect = opts.parent._protect
|
opts.protect = opts.parent._protect
|
||||||
|
|
||||||
|
# Make a copy of the aliases array, and add to it any implicit aliases inherited from
|
||||||
|
# its parent
|
||||||
|
if opts.aliases is None:
|
||||||
|
opts.aliases = []
|
||||||
|
|
||||||
|
opts.aliases = opts.aliases.copy()
|
||||||
|
for parent_alias in opts.parent._aliases:
|
||||||
|
opts.aliases.append(inherited_child_alias(name, opts.parent._name, parent_alias, t))
|
||||||
|
|
||||||
# Infer providers and provider maps from parent, if one was provided.
|
# Infer providers and provider maps from parent, if one was provided.
|
||||||
self._providers = opts.parent._providers
|
self._providers = opts.parent._providers
|
||||||
|
|
||||||
|
@ -246,8 +430,8 @@ class Resource:
|
||||||
# provider from our parent.
|
# provider from our parent.
|
||||||
opts.provider = opts.parent.get_provider(t)
|
opts.provider = opts.parent.get_provider(t)
|
||||||
else:
|
else:
|
||||||
# If a provider was specified, add it to the providers map under this type's package so that
|
# If a provider was specified, add it to the providers map under this type's package
|
||||||
# any children of this resource inherit its provider.
|
# so that any children of this resource inherit its provider.
|
||||||
type_components = t.split(":")
|
type_components = t.split(":")
|
||||||
if len(type_components) == 3:
|
if len(type_components) == 3:
|
||||||
[pkg, _, _] = type_components
|
[pkg, _, _] = type_components
|
||||||
|
@ -258,6 +442,14 @@ class Resource:
|
||||||
|
|
||||||
self._protect = bool(opts.protect)
|
self._protect = bool(opts.protect)
|
||||||
|
|
||||||
|
# Collapse any `Alias`es down to URNs. We have to wait until this point to do so because we
|
||||||
|
# do not know the default `name` and `type` to apply until we are inside the resource
|
||||||
|
# constructor.
|
||||||
|
self._aliases = []
|
||||||
|
if opts.aliases is not None:
|
||||||
|
for alias in opts.aliases:
|
||||||
|
self._aliases.append(collapse_alias_to_urn(alias, name, t, opts.parent))
|
||||||
|
|
||||||
if opts.id is not None:
|
if opts.id is not None:
|
||||||
# If this resource already exists, read its state rather than registering it anow.
|
# If this resource already exists, read its state rather than registering it anow.
|
||||||
if not custom:
|
if not custom:
|
||||||
|
@ -266,6 +458,7 @@ class Resource:
|
||||||
else:
|
else:
|
||||||
register_resource(self, t, name, custom, props, opts)
|
register_resource(self, t, name, custom, props, opts)
|
||||||
|
|
||||||
|
|
||||||
def _convert_providers(self, provider: Optional['ProviderResource'], providers: Union[Mapping[str, 'ProviderResource'], List['ProviderResource']]) -> Mapping[str, 'ProviderResource']:
|
def _convert_providers(self, provider: Optional['ProviderResource'], providers: Union[Mapping[str, 'ProviderResource'], List['ProviderResource']]) -> Mapping[str, 'ProviderResource']:
|
||||||
if provider is not None:
|
if provider is not None:
|
||||||
return self._convert_providers(None, [provider])
|
return self._convert_providers(None, [provider])
|
||||||
|
@ -435,3 +628,37 @@ def export(name: str, value: Any):
|
||||||
stack = get_root_resource()
|
stack = get_root_resource()
|
||||||
if stack is not None:
|
if stack is not None:
|
||||||
stack.output(name, value)
|
stack.output(name, value)
|
||||||
|
|
||||||
|
|
||||||
|
def create_urn(
|
||||||
|
name: 'Input[str]',
|
||||||
|
type_: 'Input[str]',
|
||||||
|
parent: Optional[Union['Resource', 'Input[str]']] = None,
|
||||||
|
project: str = None,
|
||||||
|
stack: str = None) -> 'Output[str]':
|
||||||
|
"""
|
||||||
|
create_urn computes a URN from the combination of a resource name, resource type, optional
|
||||||
|
parent, optional project and optional stack.
|
||||||
|
"""
|
||||||
|
|
||||||
|
parent_prefix = None
|
||||||
|
if parent is not None:
|
||||||
|
parent_urn = None
|
||||||
|
if isinstance(parent, Resource):
|
||||||
|
parent_urn = parent.urn
|
||||||
|
else:
|
||||||
|
parent_urn = Output.from_input(parent)
|
||||||
|
|
||||||
|
parent_prefix = parent_urn.apply(
|
||||||
|
lambda u: u[0:u.rfind("::")] + "$")
|
||||||
|
else:
|
||||||
|
if stack is None:
|
||||||
|
stack = get_stack()
|
||||||
|
|
||||||
|
if project is None:
|
||||||
|
project = get_project()
|
||||||
|
|
||||||
|
parent_prefix = "urn:pulumi:" + stack + "::" + project + "::"
|
||||||
|
|
||||||
|
return Output.all(parent_prefix, type_, name).apply(
|
||||||
|
lambda arr: arr[0] + arr[1] + "::" + arr[2])
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Optional, Any, Callable, List, NamedTuple, Dict, Set, TYPE_CHECKING
|
|
||||||
|
from typing import Optional, Any, Callable, List, NamedTuple, Dict, Set, Union, TYPE_CHECKING
|
||||||
from google.protobuf import struct_pb2
|
from google.protobuf import struct_pb2
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -22,10 +23,13 @@ from . import rpc, settings, known_types
|
||||||
from .. import log
|
from .. import log
|
||||||
from ..runtime.proto import resource_pb2
|
from ..runtime.proto import resource_pb2
|
||||||
from .rpc_manager import RPC_MANAGER
|
from .rpc_manager import RPC_MANAGER
|
||||||
|
from ..metadata import get_project, get_stack
|
||||||
|
|
||||||
|
from ..output import Output
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .. import Resource, ResourceOptions
|
from .. import Resource, ResourceOptions
|
||||||
from ..output import Output, Inputs
|
from ..output import Inputs
|
||||||
|
|
||||||
|
|
||||||
class ResourceResolverOperations(NamedTuple):
|
class ResourceResolverOperations(NamedTuple):
|
||||||
|
@ -58,6 +62,11 @@ class ResourceResolverOperations(NamedTuple):
|
||||||
A map from property name to the URNs of the resources the property depends on.
|
A map from property name to the URNs of the resources the property depends on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
aliases: List[str]
|
||||||
|
"""
|
||||||
|
A list of aliases applied to this resource.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
# Prepares for an RPC that will manufacture a resource, and hence deals with input and output properties.
|
# Prepares for an RPC that will manufacture a resource, and hence deals with input and output properties.
|
||||||
# pylint: disable=too-many-locals
|
# pylint: disable=too-many-locals
|
||||||
|
@ -82,7 +91,8 @@ async def prepare_resource(res: 'Resource',
|
||||||
parent_urn = ""
|
parent_urn = ""
|
||||||
if opts is not None and opts.parent is not None:
|
if opts is not None and opts.parent is not None:
|
||||||
parent_urn = await opts.parent.urn.future()
|
parent_urn = await opts.parent.urn.future()
|
||||||
elif ty != "pulumi:pulumi:Stack": # TODO(sean) is it necessary to check the type here?
|
# TODO(sean) is it necessary to check the type here?
|
||||||
|
elif ty != "pulumi:pulumi:Stack":
|
||||||
# If no parent was provided, parent to the root resource.
|
# If no parent was provided, parent to the root resource.
|
||||||
parent = settings.get_root_resource()
|
parent = settings.get_root_resource()
|
||||||
if parent is not None:
|
if parent is not None:
|
||||||
|
@ -109,19 +119,33 @@ async def prepare_resource(res: 'Resource',
|
||||||
dependencies.add(urn)
|
dependencies.add(urn)
|
||||||
property_dependencies[key] = list(urns)
|
property_dependencies[key] = list(urns)
|
||||||
|
|
||||||
|
# Wait for all aliases. Note that we use `res._aliases` instead of `opts.aliases` as the
|
||||||
|
# former has been processed in the Resource constructor prior to calling
|
||||||
|
# `register_resource` - both adding new inherited aliases and simplifying aliases down
|
||||||
|
# to URNs.
|
||||||
|
aliases = []
|
||||||
|
for alias in res._aliases:
|
||||||
|
alias_val = await Output.from_input(alias).future()
|
||||||
|
if not alias_val in aliases:
|
||||||
|
aliases.append(alias_val)
|
||||||
|
|
||||||
log.debug(f"resource {props} prepared")
|
log.debug(f"resource {props} prepared")
|
||||||
return ResourceResolverOperations(
|
return ResourceResolverOperations(
|
||||||
parent_urn,
|
parent_urn,
|
||||||
serialized_props,
|
serialized_props,
|
||||||
dependencies,
|
dependencies,
|
||||||
provider_ref,
|
provider_ref,
|
||||||
property_dependencies
|
property_dependencies,
|
||||||
|
aliases,
|
||||||
)
|
)
|
||||||
|
|
||||||
# pylint: disable=too-many-locals,too-many-statements
|
# pylint: disable=too-many-locals,too-many-statements
|
||||||
|
|
||||||
|
|
||||||
def read_resource(res: 'Resource', ty: str, name: str, props: 'Inputs', opts: Optional['ResourceOptions']):
|
def read_resource(res: 'Resource', ty: str, name: str, props: 'Inputs', opts: Optional['ResourceOptions']):
|
||||||
if opts.id is None:
|
if opts.id is None:
|
||||||
raise Exception("Cannot read resource whose options are lacking an ID value")
|
raise Exception(
|
||||||
|
"Cannot read resource whose options are lacking an ID value")
|
||||||
|
|
||||||
log.debug(f"reading resource: ty={ty}, name={name}, id={opts.id}")
|
log.debug(f"reading resource: ty={ty}, name={name}, id={opts.id}")
|
||||||
monitor = settings.get_monitor()
|
monitor = settings.get_monitor()
|
||||||
|
@ -148,7 +172,8 @@ def read_resource(res: 'Resource', ty: str, name: str, props: 'Inputs', opts: Op
|
||||||
resolve_value = asyncio.Future()
|
resolve_value = asyncio.Future()
|
||||||
resolve_perform_apply = asyncio.Future()
|
resolve_perform_apply = asyncio.Future()
|
||||||
resolve_secret = asyncio.Future()
|
resolve_secret = asyncio.Future()
|
||||||
res.id = known_types.new_output({res}, resolve_value, resolve_perform_apply, resolve_secret)
|
res.id = known_types.new_output(
|
||||||
|
{res}, resolve_value, resolve_perform_apply, resolve_secret)
|
||||||
|
|
||||||
def do_resolve(value: Any, perform_apply: bool, exn: Optional[Exception]):
|
def do_resolve(value: Any, perform_apply: bool, exn: Optional[Exception]):
|
||||||
if exn is not None:
|
if exn is not None:
|
||||||
|
@ -181,7 +206,8 @@ def read_resource(res: 'Resource', ty: str, name: str, props: 'Inputs', opts: Op
|
||||||
# here.
|
# here.
|
||||||
additional_secret_outputs = opts.additional_secret_outputs
|
additional_secret_outputs = opts.additional_secret_outputs
|
||||||
if res.translate_input_property is not None and opts.additional_secret_outputs is not None:
|
if res.translate_input_property is not None and opts.additional_secret_outputs is not None:
|
||||||
additional_secret_outputs = map(res.translate_input_property, opts.additional_secret_outputs)
|
additional_secret_outputs = map(
|
||||||
|
res.translate_input_property, opts.additional_secret_outputs)
|
||||||
|
|
||||||
req = resource_pb2.ReadResourceRequest(
|
req = resource_pb2.ReadResourceRequest(
|
||||||
type=ty,
|
type=ty,
|
||||||
|
@ -196,11 +222,13 @@ def read_resource(res: 'Resource', ty: str, name: str, props: 'Inputs', opts: Op
|
||||||
additionalSecretOutputs=additional_secret_outputs,
|
additionalSecretOutputs=additional_secret_outputs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ..resource import create_urn
|
||||||
|
mock_urn = await create_urn(name, ty, resolver.parent_urn).future()
|
||||||
|
|
||||||
def do_rpc_call():
|
def do_rpc_call():
|
||||||
if monitor is None:
|
if monitor is None:
|
||||||
# If no monitor is available, we'll need to fake up a response, for testing.
|
# If no monitor is available, we'll need to fake up a response, for testing.
|
||||||
test_urn = create_test_urn(ty, name)
|
return RegisterResponse(mock_urn, None, resolver.serialized_props)
|
||||||
return RegisterResponse(test_urn, None, resolver.serialized_props)
|
|
||||||
|
|
||||||
# If there is a monitor available, make the true RPC request to the engine.
|
# If there is a monitor available, make the true RPC request to the engine.
|
||||||
try:
|
try:
|
||||||
|
@ -218,7 +246,8 @@ def read_resource(res: 'Resource', ty: str, name: str, props: 'Inputs', opts: Op
|
||||||
resp = await asyncio.get_event_loop().run_in_executor(None, do_rpc_call)
|
resp = await asyncio.get_event_loop().run_in_executor(None, do_rpc_call)
|
||||||
|
|
||||||
except Exception as exn:
|
except Exception as exn:
|
||||||
log.debug(f"exception when preparing or executing rpc: {traceback.format_exc()}")
|
log.debug(
|
||||||
|
f"exception when preparing or executing rpc: {traceback.format_exc()}")
|
||||||
rpc.resolve_outputs_due_to_exception(resolvers, exn)
|
rpc.resolve_outputs_due_to_exception(resolvers, exn)
|
||||||
resolve_urn_exn(exn)
|
resolve_urn_exn(exn)
|
||||||
resolve_id(None, False, exn)
|
resolve_id(None, False, exn)
|
||||||
|
@ -226,17 +255,20 @@ def read_resource(res: 'Resource', ty: str, name: str, props: 'Inputs', opts: Op
|
||||||
|
|
||||||
log.debug(f"resource read successful: ty={ty}, urn={resp.urn}")
|
log.debug(f"resource read successful: ty={ty}, urn={resp.urn}")
|
||||||
resolve_urn(resp.urn)
|
resolve_urn(resp.urn)
|
||||||
resolve_id(resolved_id, True, None) # Read IDs are always known.
|
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, props, resp.properties, resolvers)
|
||||||
|
|
||||||
asyncio.ensure_future(RPC_MANAGER.do_rpc("read resource", do_read)())
|
asyncio.ensure_future(RPC_MANAGER.do_rpc("read resource", do_read)())
|
||||||
|
|
||||||
# pylint: disable=too-many-locals,too-many-statements
|
# pylint: disable=too-many-locals,too-many-statements
|
||||||
|
|
||||||
|
|
||||||
def register_resource(res: 'Resource', ty: str, name: str, custom: bool, props: 'Inputs', opts: Optional['ResourceOptions']):
|
def register_resource(res: 'Resource', ty: str, name: str, custom: bool, props: 'Inputs', opts: Optional['ResourceOptions']):
|
||||||
"""
|
"""
|
||||||
registerResource registers a new resource object with a given type t and name. It returns the auto-generated
|
registerResource registers a new resource object with a given type t and name. It returns the
|
||||||
URN and the ID that will resolve after the deployment has completed. All properties will be initialized to property
|
auto-generated URN and the ID that will resolve after the deployment has completed. All
|
||||||
objects that the registration operation will resolve at the right time (or remain unresolved for deployments).
|
properties will be initialized to property objects that the registration operation will resolve
|
||||||
|
at the right time (or remain unresolved for deployments).
|
||||||
"""
|
"""
|
||||||
log.debug(f"registering resource: ty={ty}, name={name}, custom={custom}")
|
log.debug(f"registering resource: ty={ty}, name={name}, custom={custom}")
|
||||||
monitor = settings.get_monitor()
|
monitor = settings.get_monitor()
|
||||||
|
@ -257,12 +289,14 @@ def register_resource(res: 'Resource', ty: str, name: str, custom: bool, props:
|
||||||
res.urn = known_types.new_output({res}, urn_future, urn_known, urn_secret)
|
res.urn = known_types.new_output({res}, urn_future, urn_known, urn_secret)
|
||||||
|
|
||||||
# If a custom resource, make room for the ID property.
|
# If a custom resource, make room for the ID property.
|
||||||
resolve_id: Optional[Callable[[Any, str, Optional[Exception]], None]] = None
|
resolve_id: Optional[Callable[[
|
||||||
|
Any, str, Optional[Exception]], None]] = None
|
||||||
if custom:
|
if custom:
|
||||||
resolve_value = asyncio.Future()
|
resolve_value = asyncio.Future()
|
||||||
resolve_perform_apply = asyncio.Future()
|
resolve_perform_apply = asyncio.Future()
|
||||||
resolve_secret = asyncio.Future()
|
resolve_secret = asyncio.Future()
|
||||||
res.id = known_types.new_output({res}, resolve_value, resolve_perform_apply, resolve_secret)
|
res.id = known_types.new_output(
|
||||||
|
{res}, resolve_value, resolve_perform_apply, resolve_secret)
|
||||||
|
|
||||||
def do_resolve(value: Any, perform_apply: bool, exn: Optional[Exception]):
|
def do_resolve(value: Any, perform_apply: bool, exn: Optional[Exception]):
|
||||||
if exn is not None:
|
if exn is not None:
|
||||||
|
@ -289,18 +323,21 @@ def register_resource(res: 'Resource', ty: str, name: str, custom: bool, props:
|
||||||
|
|
||||||
property_dependencies = {}
|
property_dependencies = {}
|
||||||
for key, deps in resolver.property_dependencies.items():
|
for key, deps in resolver.property_dependencies.items():
|
||||||
property_dependencies[key] = resource_pb2.RegisterResourceRequest.PropertyDependencies(urns=deps)
|
property_dependencies[key] = resource_pb2.RegisterResourceRequest.PropertyDependencies(
|
||||||
|
urns=deps)
|
||||||
|
|
||||||
ignore_changes = opts.ignore_changes
|
ignore_changes = opts.ignore_changes
|
||||||
if res.translate_input_property is not None and opts.ignore_changes is not None:
|
if res.translate_input_property is not None and opts.ignore_changes is not None:
|
||||||
ignore_changes = map(res.translate_input_property, opts.ignore_changes)
|
ignore_changes = map(
|
||||||
|
res.translate_input_property, opts.ignore_changes)
|
||||||
|
|
||||||
# Note that while `additional_secret_outputs` lists property names that are outputs, we call
|
# Note that while `additional_secret_outputs` lists property names that are outputs, we
|
||||||
# `translate_input_property` because it is the method that converts from the language projection
|
# call `translate_input_property` because it is the method that converts from the
|
||||||
# name to the provider name, which is what we want.
|
# language projection name to the provider name, which is what we want.
|
||||||
additional_secret_outputs = opts.additional_secret_outputs
|
additional_secret_outputs = opts.additional_secret_outputs
|
||||||
if res.translate_input_property is not None and opts.additional_secret_outputs is not None:
|
if res.translate_input_property is not None and opts.additional_secret_outputs is not None:
|
||||||
additional_secret_outputs = map(res.translate_input_property, opts.additional_secret_outputs)
|
additional_secret_outputs = map(
|
||||||
|
res.translate_input_property, opts.additional_secret_outputs)
|
||||||
|
|
||||||
req = resource_pb2.RegisterResourceRequest(
|
req = resource_pb2.RegisterResourceRequest(
|
||||||
type=ty,
|
type=ty,
|
||||||
|
@ -317,16 +354,18 @@ def register_resource(res: 'Resource', ty: str, name: str, custom: bool, props:
|
||||||
version=opts.version or "",
|
version=opts.version or "",
|
||||||
acceptSecrets=True,
|
acceptSecrets=True,
|
||||||
additionalSecretOutputs=additional_secret_outputs,
|
additionalSecretOutputs=additional_secret_outputs,
|
||||||
aliases=[],
|
|
||||||
importId=opts.import_,
|
importId=opts.import_,
|
||||||
customTimeouts=opts.custom_timeouts
|
customTimeouts=opts.custom_timeouts,
|
||||||
|
aliases=resolver.aliases,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ..resource import create_urn
|
||||||
|
mock_urn = await create_urn(name, ty, resolver.parent_urn).future()
|
||||||
|
|
||||||
def do_rpc_call():
|
def do_rpc_call():
|
||||||
if monitor is None:
|
if monitor is None:
|
||||||
# If no monitor is available, we'll need to fake up a response, for testing.
|
# If no monitor is available, we'll need to fake up a response, for testing.
|
||||||
test_urn = create_test_urn(ty, name)
|
return RegisterResponse(mock_urn, None, resolver.serialized_props)
|
||||||
return RegisterResponse(test_urn, None, resolver.serialized_props)
|
|
||||||
|
|
||||||
# If there is a monitor available, make the true RPC request to the engine.
|
# If there is a monitor available, make the true RPC request to the engine.
|
||||||
try:
|
try:
|
||||||
|
@ -343,7 +382,8 @@ def register_resource(res: 'Resource', ty: str, name: str, custom: bool, props:
|
||||||
|
|
||||||
resp = await asyncio.get_event_loop().run_in_executor(None, do_rpc_call)
|
resp = await asyncio.get_event_loop().run_in_executor(None, do_rpc_call)
|
||||||
except Exception as exn:
|
except Exception as exn:
|
||||||
log.debug(f"exception when preparing or executing rpc: {traceback.format_exc()}")
|
log.debug(
|
||||||
|
f"exception when preparing or executing rpc: {traceback.format_exc()}")
|
||||||
rpc.resolve_outputs_due_to_exception(resolvers, exn)
|
rpc.resolve_outputs_due_to_exception(resolvers, exn)
|
||||||
resolve_urn_exn(exn)
|
resolve_urn_exn(exn)
|
||||||
if resolve_id is not None:
|
if resolve_id is not None:
|
||||||
|
@ -361,16 +401,19 @@ def register_resource(res: 'Resource', ty: str, name: str, custom: bool, props:
|
||||||
|
|
||||||
await rpc.resolve_outputs(res, props, resp.object, resolvers)
|
await rpc.resolve_outputs(res, props, resp.object, resolvers)
|
||||||
|
|
||||||
asyncio.ensure_future(RPC_MANAGER.do_rpc("register resource", do_register)())
|
asyncio.ensure_future(RPC_MANAGER.do_rpc(
|
||||||
|
"register resource", do_register)())
|
||||||
|
|
||||||
|
|
||||||
def register_resource_outputs(res: 'Resource', outputs: 'Union[Inputs, Awaitable[Inputs], Output[Inputs]]'):
|
def register_resource_outputs(res: 'Resource', outputs: 'Union[Inputs, Awaitable[Inputs], Output[Inputs]]'):
|
||||||
async def do_register_resource_outputs():
|
async def do_register_resource_outputs():
|
||||||
urn = await res.urn.future()
|
urn = await res.urn.future()
|
||||||
serialized_props = await rpc.serialize_properties(outputs, {})
|
serialized_props = await rpc.serialize_properties(outputs, {})
|
||||||
log.debug(f"register resource outputs prepared: urn={urn}, props={serialized_props}")
|
log.debug(
|
||||||
|
f"register resource outputs prepared: urn={urn}, props={serialized_props}")
|
||||||
monitor = settings.get_monitor()
|
monitor = settings.get_monitor()
|
||||||
req = resource_pb2.RegisterResourceOutputsRequest(urn=urn, outputs=serialized_props)
|
req = resource_pb2.RegisterResourceOutputsRequest(
|
||||||
|
urn=urn, outputs=serialized_props)
|
||||||
|
|
||||||
def do_rpc_call():
|
def do_rpc_call():
|
||||||
if monitor is None:
|
if monitor is None:
|
||||||
|
@ -390,9 +433,11 @@ def register_resource_outputs(res: 'Resource', outputs: 'Union[Inputs, Awaitable
|
||||||
raise Exception(details)
|
raise Exception(details)
|
||||||
|
|
||||||
await asyncio.get_event_loop().run_in_executor(None, do_rpc_call)
|
await asyncio.get_event_loop().run_in_executor(None, do_rpc_call)
|
||||||
log.debug(f"resource registration successful: urn={urn}, props={serialized_props}")
|
log.debug(
|
||||||
|
f"resource registration successful: urn={urn}, props={serialized_props}")
|
||||||
|
|
||||||
asyncio.ensure_future(RPC_MANAGER.do_rpc("register resource outputs", do_register_resource_outputs)())
|
asyncio.ensure_future(RPC_MANAGER.do_rpc(
|
||||||
|
"register resource outputs", do_register_resource_outputs)())
|
||||||
|
|
||||||
|
|
||||||
class RegisterResponse:
|
class RegisterResponse:
|
||||||
|
@ -405,10 +450,3 @@ class RegisterResponse:
|
||||||
self.urn = urn
|
self.urn = urn
|
||||||
self.id = id
|
self.id = id
|
||||||
self.object = object
|
self.object = object
|
||||||
|
|
||||||
|
|
||||||
def create_test_urn(ty: str, name: str) -> str:
|
|
||||||
"""
|
|
||||||
Creates a test URN for cases where the engine isn't available to give us one (i.e., test mode).
|
|
||||||
"""
|
|
||||||
return 'urn:pulumi:{0}::{1}::{2}::{3}'.format(settings.get_stack(), settings.get_project(), ty, name)
|
|
||||||
|
|
|
@ -4,20 +4,25 @@ package ints
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pulumi/pulumi/pkg/testing/integration"
|
"github.com/pulumi/pulumi/pkg/testing/integration"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestAliases tests a case where a resource's name changes but it provides an `alias` pointing to the old URN to ensure
|
var dirs = []string{
|
||||||
// the resource is preserved across the update.
|
"rename",
|
||||||
func TestAliases(t *testing.T) {
|
"adopt_into_component",
|
||||||
dirs := []string{
|
"rename_component_and_child",
|
||||||
"rename", "adopt_into_component", "rename_component",
|
"retype_component",
|
||||||
"rename_component_and_child", "retype_component",
|
"rename_component",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestNodejsAliases tests a case where a resource's name changes but it provides an `alias`
|
||||||
|
// pointing to the old URN to ensure the resource is preserved across the update.
|
||||||
|
func TestNodejsAliases(t *testing.T) {
|
||||||
for _, dir := range dirs {
|
for _, dir := range dirs {
|
||||||
d := dir
|
d := path.Join("nodejs", dir)
|
||||||
t.Run(d, func(t *testing.T) {
|
t.Run(d, func(t *testing.T) {
|
||||||
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
||||||
Dir: path.Join(d, "step1"),
|
Dir: path.Join(d, "step1"),
|
||||||
|
@ -34,3 +39,25 @@ func TestAliases(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPythonAliases(t *testing.T) {
|
||||||
|
for _, dir := range dirs {
|
||||||
|
d := path.Join("python", dir)
|
||||||
|
t.Run(d, func(t *testing.T) {
|
||||||
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
||||||
|
Dir: path.Join(d, "step1"),
|
||||||
|
Dependencies: []string{
|
||||||
|
filepath.Join("..", "..", "..", "sdk", "python", "env", "src"),
|
||||||
|
},
|
||||||
|
Quick: true,
|
||||||
|
EditDirs: []integration.EditDir{
|
||||||
|
{
|
||||||
|
Dir: path.Join(d, "step2"),
|
||||||
|
Additive: true,
|
||||||
|
ExpectNoChanges: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ class ComponentThree extends pulumi.ComponentResource {
|
||||||
this.resource2 = new Resource("otherchild", { parent: this });
|
this.resource2 = new Resource("otherchild", { parent: this });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ...but applying an alias to the instance succesfully renames both the component and the children.
|
// ...but applying an alias to the instance successfully renames both the component and the children.
|
||||||
const comp3 = new ComponentThree("newcomp3", {
|
const comp3 = new ComponentThree("newcomp3", {
|
||||||
aliases: [{ name: "comp3" }],
|
aliases: [{ name: "comp3" }],
|
||||||
});
|
});
|
|
@ -2,9 +2,6 @@
|
||||||
|
|
||||||
import * as pulumi from "@pulumi/pulumi";
|
import * as pulumi from "@pulumi/pulumi";
|
||||||
|
|
||||||
const stackName = pulumi.getStack();
|
|
||||||
const projectName = pulumi.getProject();
|
|
||||||
|
|
||||||
class Resource extends pulumi.ComponentResource {
|
class Resource extends pulumi.ComponentResource {
|
||||||
constructor(name: string, opts?: pulumi.ComponentResourceOptions) {
|
constructor(name: string, opts?: pulumi.ComponentResourceOptions) {
|
||||||
super("my:module:Resource", name, {}, opts);
|
super("my:module:Resource", name, {}, opts);
|
|
@ -0,0 +1,3 @@
|
||||||
|
name: aliases_adopt_into_component
|
||||||
|
description: A program that replaces a resource with a new name and alias.
|
||||||
|
runtime: python
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||||||
|
|
||||||
|
from pulumi import ComponentResource, export, Resource, ResourceOptions
|
||||||
|
|
||||||
|
class Resource1(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Resource", name, None, opts)
|
||||||
|
|
||||||
|
|
||||||
|
# Scenario #2 - adopt a resource into a component
|
||||||
|
class Component1(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Component", name, None, opts)
|
||||||
|
|
||||||
|
res2 = Resource1("res2")
|
||||||
|
comp2 = Component1("comp2")
|
||||||
|
|
||||||
|
# Scenario 3: adopt this resource into a new parent.
|
||||||
|
class Component2(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Component2", name, None, opts)
|
||||||
|
|
||||||
|
unparented_comp2 = Component2("unparented")
|
||||||
|
|
||||||
|
# Scenario 4: Make a child resource that is parented by opts instead of 'this'. Fix
|
||||||
|
# in the next step to be parented by this. Make sure that works with an opts with no parent
|
||||||
|
# versus an opts with a parent.
|
||||||
|
|
||||||
|
class Component3(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Component3", name, None, opts)
|
||||||
|
mycomp2 = Component2(name + "-child", opts)
|
||||||
|
|
||||||
|
parented_by_stack_comp3 = Component3("parentedbystack")
|
||||||
|
parented_by_component_comp3 = Component3("parentedbycomponent", ResourceOptions(parent=comp2))
|
||||||
|
|
||||||
|
# Scenario 5: Allow multiple aliases to the same resource.
|
||||||
|
class Component4(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Component4", name)
|
||||||
|
|
||||||
|
comp4 = Component4("duplicateAliases", ResourceOptions(parent=comp2))
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from pulumi import Alias, ComponentResource, export, Resource, ResourceOptions, create_urn, ROOT_STACK_RESOURCE
|
||||||
|
|
||||||
|
class Resource1(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Resource", name, None, opts)
|
||||||
|
|
||||||
|
# Scenario #2 - adopt a resource into a component. The component author is the same as the
|
||||||
|
# component user, and changes the component to be able to adopt the resource that was previously
|
||||||
|
# defined separately...
|
||||||
|
class Component1(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Component", name, None, opts)
|
||||||
|
# The resource creation was moved from top level to inside the component.
|
||||||
|
resource = Resource1(name + "-child", ResourceOptions(
|
||||||
|
# With a new parent
|
||||||
|
parent=self,
|
||||||
|
# But with an alias provided based on knowing where the resource existing before - in
|
||||||
|
# this case at top level. We use an absolute URN instead of a relative `Alias` because
|
||||||
|
# we are referencing a fixed resource that was in some arbitrary other location in the
|
||||||
|
# hierarchy prior to being adopted into this component.
|
||||||
|
aliases=[create_urn("res2", "my:module:Resource")]))
|
||||||
|
|
||||||
|
# The creation of the component is unchanged.
|
||||||
|
comp2 = Component1("comp2")
|
||||||
|
|
||||||
|
|
||||||
|
# Scenario 3: adopt this resource into a new parent.
|
||||||
|
class Component2(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Component2", name, None, opts)
|
||||||
|
|
||||||
|
|
||||||
|
# validate that "parent: undefined" means "i didn't have a parent previously"
|
||||||
|
unparented_comp2 = Component2("unparented", ResourceOptions(
|
||||||
|
aliases=[Alias(parent=ROOT_STACK_RESOURCE)],
|
||||||
|
parent=comp2))
|
||||||
|
|
||||||
|
|
||||||
|
# Scenario 4: Make a child resource that is parented by opts instead of 'this'. Fix in the next
|
||||||
|
# step to be parented by this. Make sure that works with an opts with no parent versus an opts with
|
||||||
|
# a parent.
|
||||||
|
|
||||||
|
class Component3(ComponentResource):
|
||||||
|
def __init__(self, name, opts=ResourceOptions()):
|
||||||
|
super().__init__("my:module:Component3", name, None, opts)
|
||||||
|
mycomp2 = Component2(name + "-child", ResourceOptions(
|
||||||
|
aliases=[Alias(parent=opts.parent)],
|
||||||
|
parent=self))
|
||||||
|
|
||||||
|
parented_by_stack_comp3 = Component3("parentedbystack")
|
||||||
|
parented_by_component_comp3 = Component3("parentedbycomponent", ResourceOptions(parent=comp2))
|
||||||
|
|
||||||
|
# Scenario 5: Allow multiple aliases to the same resource.
|
||||||
|
class Component4(ComponentResource):
|
||||||
|
def __init__(self, name, opts=ResourceOptions()):
|
||||||
|
child_opts = copy.copy(opts)
|
||||||
|
if child_opts.aliases is None:
|
||||||
|
child_opts.aliases = [Alias(parent=ROOT_STACK_RESOURCE), Alias(parent=ROOT_STACK_RESOURCE)]
|
||||||
|
|
||||||
|
super().__init__("my:module:Component4", name, None, child_opts)
|
||||||
|
|
||||||
|
comp4 = Component4("duplicateAliases", ResourceOptions(parent=comp2))
|
|
@ -0,0 +1,3 @@
|
||||||
|
name: aliases_rename
|
||||||
|
description: A program that replaces a resource with a new name and alias.
|
||||||
|
runtime: python
|
10
tests/integration/aliases/python/rename/step1/__main__.py
Normal file
10
tests/integration/aliases/python/rename/step1/__main__.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||||||
|
|
||||||
|
from pulumi import Alias, ComponentResource, export, Resource, ResourceOptions, create_urn, ROOT_STACK_RESOURCE
|
||||||
|
|
||||||
|
class Resource1(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Resource", name, None, opts)
|
||||||
|
|
||||||
|
# Scenario #1 - rename a resource
|
||||||
|
res1 = Resource1("res1")
|
12
tests/integration/aliases/python/rename/step2/__main__.py
Normal file
12
tests/integration/aliases/python/rename/step2/__main__.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||||||
|
|
||||||
|
from pulumi import Alias, ComponentResource, export, Resource, ResourceOptions, create_urn, ROOT_STACK_RESOURCE
|
||||||
|
|
||||||
|
class Resource1(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Resource", name, None, opts)
|
||||||
|
|
||||||
|
# Scenario #1 - rename a resource
|
||||||
|
# This resource was previously named `res1`, we'll alias to the old name.
|
||||||
|
res1 = Resource1("newres1", ResourceOptions(
|
||||||
|
aliases=[Alias(name="res1")]))
|
|
@ -0,0 +1,3 @@
|
||||||
|
name: aliases_rename_component
|
||||||
|
description: A program that replaces a resource with a new name and alias.
|
||||||
|
runtime: python
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||||||
|
|
||||||
|
from pulumi import Alias, ComponentResource, export, Resource, ResourceOptions, create_urn, ROOT_STACK_RESOURCE
|
||||||
|
|
||||||
|
class Resource1(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Resource", name, None, opts)
|
||||||
|
|
||||||
|
# Scenario #3 - rename a component (and all it's children)
|
||||||
|
class ComponentThree(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:ComponentThree", name, None, opts)
|
||||||
|
# Note that both un-prefixed and parent-name-prefixed child names are supported. For the
|
||||||
|
# later, the implicit alias inherited from the parent alias will include replacing the name
|
||||||
|
# prefix to match the parent alias name.
|
||||||
|
resource1 = Resource1(name + "-child", ResourceOptions(parent=self))
|
||||||
|
resource2 = Resource1("otherchild", ResourceOptions(parent=self))
|
||||||
|
|
||||||
|
comp3 = ComponentThree("comp3")
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||||||
|
|
||||||
|
from pulumi import Alias, ComponentResource, export, Resource, ResourceOptions, create_urn, ROOT_STACK_RESOURCE
|
||||||
|
|
||||||
|
class Resource1(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Resource", name, None, opts)
|
||||||
|
|
||||||
|
# Scenario #3 - rename a component (and all it's children)
|
||||||
|
# No change to the component...
|
||||||
|
class ComponentThree(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:ComponentThree", name, None, opts)
|
||||||
|
# Note that both un-prefixed and parent-name-prefixed child names are supported. For the
|
||||||
|
# later, the implicit alias inherited from the parent alias will include replacing the name
|
||||||
|
# prefix to match the parent alias name.
|
||||||
|
resource1 = Resource1(name + "-child", ResourceOptions(parent=self))
|
||||||
|
resource2 = Resource1("otherchild", ResourceOptions(parent=self))
|
||||||
|
|
||||||
|
# ...but applying an alias to the instance successfully renames both the component and the children.
|
||||||
|
comp3 = ComponentThree("newcomp3", ResourceOptions(
|
||||||
|
aliases=[Alias(name="comp3")]))
|
|
@ -0,0 +1,3 @@
|
||||||
|
name: aliases_rename_component_and_child
|
||||||
|
description: A program that replaces a resource with a new name and alias.
|
||||||
|
runtime: python
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||||||
|
|
||||||
|
from pulumi import Alias, ComponentResource, export, Resource, ResourceOptions, create_urn, ROOT_STACK_RESOURCE
|
||||||
|
|
||||||
|
class Resource1(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Resource", name, None, opts)
|
||||||
|
|
||||||
|
# Scenario #5 - composing #1 and #3 and making both changes at the same time
|
||||||
|
class ComponentFive(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:ComponentFive", name, None, opts)
|
||||||
|
res1 = Resource1("otherchild", ResourceOptions(parent=self))
|
||||||
|
|
||||||
|
comp5 = ComponentFive("comp5")
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||||||
|
|
||||||
|
from pulumi import Alias, ComponentResource, export, Resource, ResourceOptions, create_urn, ROOT_STACK_RESOURCE
|
||||||
|
|
||||||
|
class Resource1(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Resource", name, None, opts)
|
||||||
|
|
||||||
|
# Scenario #5 - composing #1 and #3 and making both changes at the same time
|
||||||
|
class ComponentFive(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:ComponentFive", name, None, opts)
|
||||||
|
res1 = Resource1("otherchildrenamed", ResourceOptions(
|
||||||
|
parent=self,
|
||||||
|
aliases=[Alias(name="otherchild", parent=self)]))
|
||||||
|
|
||||||
|
comp5 = ComponentFive("newcomp5", ResourceOptions(
|
||||||
|
aliases=[Alias(name="comp5")]))
|
|
@ -0,0 +1,3 @@
|
||||||
|
name: aliases_retype_component
|
||||||
|
description: A program that replaces a resource with a new name and alias.
|
||||||
|
runtime: python
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||||||
|
|
||||||
|
from pulumi import Alias, ComponentResource, export, Resource, ResourceOptions, create_urn, ROOT_STACK_RESOURCE
|
||||||
|
|
||||||
|
class Resource1(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Resource", name, None, opts)
|
||||||
|
|
||||||
|
# Scenario #4 - change the type of a component
|
||||||
|
class ComponentFour(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:ComponentFour", name, None, opts)
|
||||||
|
res1 = Resource1("otherchild", ResourceOptions(parent=self))
|
||||||
|
|
||||||
|
comp4 = ComponentFour("comp4")
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from pulumi import Alias, ComponentResource, export, Resource, ResourceOptions, create_urn, ROOT_STACK_RESOURCE
|
||||||
|
|
||||||
|
class Resource1(ComponentResource):
|
||||||
|
def __init__(self, name, opts=None):
|
||||||
|
super().__init__("my:module:Resource", name, None, opts)
|
||||||
|
|
||||||
|
# Scenario #4 - change the type of a component
|
||||||
|
class ComponentFour(ComponentResource):
|
||||||
|
def __init__(self, name, opts=ResourceOptions()):
|
||||||
|
# Add an alias that references the old type of this resource...
|
||||||
|
aliases = [Alias(type_="my:module:ComponentFour")]
|
||||||
|
if opts.aliases is not None:
|
||||||
|
for alias in opts.aliases:
|
||||||
|
aliases.append(alias)
|
||||||
|
|
||||||
|
# ..and then make the super call with the new type of this resource and the added alias.
|
||||||
|
opts_copy = copy.copy(opts)
|
||||||
|
opts_copy.aliases = aliases
|
||||||
|
super().__init__("my:differentmodule:ComponentFourWithADifferentTypeName", name, None, opts_copy)
|
||||||
|
|
||||||
|
# The child resource will also pick up an implicit alias due to the new type of the component it is parented
|
||||||
|
# to.
|
||||||
|
res1 = Resource1("otherchild", ResourceOptions(parent=self))
|
||||||
|
|
||||||
|
comp4 = ComponentFour("comp4")
|
Loading…
Reference in a new issue