139 lines
5.7 KiB
Python
139 lines
5.7 KiB
Python
|
# Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||
|
|
||
|
import asyncio
|
||
|
from pulumi import Output, ComponentResource, ResourceOptions, ResourceTransformationArgs, ResourceTransformationResult
|
||
|
from pulumi.dynamic import Resource, ResourceProvider, CreateResult
|
||
|
from pulumi.runtime import register_stack_transformation
|
||
|
|
||
|
class SimpleProvider(ResourceProvider):
|
||
|
def create(self, inputs):
|
||
|
return CreateResult("0", { "output": "a", "output2": "b" })
|
||
|
|
||
|
|
||
|
class SimpleResource(Resource):
|
||
|
output: Output[str]
|
||
|
output2: Output[str]
|
||
|
def __init__(self, name, args, opts = None):
|
||
|
super().__init__(SimpleProvider(),
|
||
|
name,
|
||
|
{ **args, "outputs": None, "output2": None },
|
||
|
opts)
|
||
|
|
||
|
class MyComponent(ComponentResource):
|
||
|
child: SimpleResource
|
||
|
def __init__(self, name, opts = None):
|
||
|
super().__init__("my:component:MyComponent", name, {}, opts)
|
||
|
childOpts = ResourceOptions(parent=self,
|
||
|
additional_secret_outputs=["output2"])
|
||
|
self.child = SimpleResource(f"{name}-child", { "input": "hello" }, childOpts)
|
||
|
self.register_outputs({})
|
||
|
|
||
|
# Scenario #1 - apply a transformation to a CustomResource
|
||
|
def res1_transformation(args: ResourceTransformationArgs):
|
||
|
print("res1 transformation")
|
||
|
return ResourceTransformationResult(
|
||
|
props=args.props,
|
||
|
opts=ResourceOptions.merge(args.opts, ResourceOptions(
|
||
|
additional_secret_outputs=["output"],
|
||
|
))
|
||
|
)
|
||
|
|
||
|
res1 = SimpleResource(
|
||
|
name="res1",
|
||
|
args={"input": "hello"},
|
||
|
opts=ResourceOptions(transformations=[res1_transformation]))
|
||
|
|
||
|
|
||
|
# Scenario #2 - apply a transformation to a Component to transform it's children
|
||
|
def res2_transformation(args: ResourceTransformationArgs):
|
||
|
print("res2 transformation")
|
||
|
if args.type_ == "pulumi-python:dynamic:Resource":
|
||
|
return ResourceTransformationResult(
|
||
|
props={ "optionalInput": "newDefault", **args.props },
|
||
|
opts=ResourceOptions.merge(args.opts, ResourceOptions(
|
||
|
additional_secret_outputs=["output"],
|
||
|
)))
|
||
|
|
||
|
res2 = MyComponent(
|
||
|
name="res2",
|
||
|
opts=ResourceOptions(transformations=[res2_transformation]))
|
||
|
|
||
|
# Scenario #3 - apply a transformation to the Stack to transform all (future) resources in the stack
|
||
|
def res3_transformation(args: ResourceTransformationArgs):
|
||
|
print("stack transformation")
|
||
|
if args.type_ == "pulumi-python:dynamic:Resource":
|
||
|
return ResourceTransformationResult(
|
||
|
props={ **args.props, "optionalInput": "stackDefault" },
|
||
|
opts=ResourceOptions.merge(args.opts, ResourceOptions(
|
||
|
additional_secret_outputs=["output"],
|
||
|
)))
|
||
|
|
||
|
register_stack_transformation(res3_transformation)
|
||
|
|
||
|
res3 = SimpleResource("res3", { "input": "hello" });
|
||
|
|
||
|
# Scenario #4 - transformations are applied in order of decreasing specificity
|
||
|
# 1. (not in this example) Child transformation
|
||
|
# 2. First parent transformation
|
||
|
# 3. Second parent transformation
|
||
|
# 4. Stack transformation
|
||
|
def res4_transformation_1(args: ResourceTransformationArgs):
|
||
|
print("res4 transformation")
|
||
|
if args.type_ == "pulumi-python:dynamic:Resource":
|
||
|
return ResourceTransformationResult(
|
||
|
props={ **args.props, "optionalInput": "default1" },
|
||
|
opts=args.opts)
|
||
|
def res4_transformation_2(args: ResourceTransformationArgs):
|
||
|
print("res4 transformation2")
|
||
|
if args.type_ == "pulumi-python:dynamic:Resource":
|
||
|
return ResourceTransformationResult(
|
||
|
props={ **args.props, "optionalInput": "default2" },
|
||
|
opts=args.opts)
|
||
|
|
||
|
res4 = MyComponent(
|
||
|
name="res4",
|
||
|
opts=ResourceOptions(transformations=[
|
||
|
res4_transformation_1,
|
||
|
res4_transformation_2]))
|
||
|
|
||
|
# Scenario #5 - cross-resource transformations that inject dependencies on one resource into another.
|
||
|
|
||
|
class MyOtherComponent(ComponentResource):
|
||
|
child1: SimpleResource
|
||
|
child2: SimpleResource
|
||
|
def __init__(self, name, opts = None):
|
||
|
super().__init__("my:component:MyComponent", name, {}, opts)
|
||
|
self.child = SimpleResource(f"{name}-child1", { "input": "hello" }, ResourceOptions(parent=self))
|
||
|
self.child = SimpleResource(f"{name}-child2", { "input": "hello" }, ResourceOptions(parent=self))
|
||
|
self.register_outputs({})
|
||
|
|
||
|
def transform_child1_depends_on_child2():
|
||
|
# Create a future that wil be resolved once we find child2. This is needed because we do not
|
||
|
# know what order we will see the resource registrations of child1 and child2.
|
||
|
child2_future = asyncio.Future()
|
||
|
def transform(args: ResourceTransformationArgs):
|
||
|
print("res4 transformation")
|
||
|
if args.name.endswith("-child2"):
|
||
|
# Resolve the child2 promise with the child2 resource.
|
||
|
child2_future.set_result(args.resource)
|
||
|
return None
|
||
|
elif args.name.endswith("-child1"):
|
||
|
# Overwrite the `input` to child2 with a dependency on the `output2` from child1.
|
||
|
async def getOutput2(input):
|
||
|
if input != "hello":
|
||
|
# Not strictly necessary - but shows we can confirm invariants we expect to be
|
||
|
# true.
|
||
|
raise Exception("unexpected input value")
|
||
|
child2 = await child2_future
|
||
|
return child2.output2
|
||
|
child2_input = Output.from_input(args.props["input"]).apply(getOutput2)
|
||
|
# Finally - overwrite the input of child2.
|
||
|
return ResourceTransformationResult(
|
||
|
props={ **args.props, "input": child2_input },
|
||
|
opts=args.opts)
|
||
|
return transform
|
||
|
|
||
|
res5 = MyOtherComponent(
|
||
|
name="res5",
|
||
|
opts=ResourceOptions(transformations=[transform_child1_depends_on_child2()]))
|