Skip to content
This repository has been archived by the owner on Jul 1, 2024. It is now read-only.

Commit

Permalink
Add new transforms docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Frassle committed Mar 20, 2024
1 parent cbfe853 commit 8d9d6a8
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 2 deletions.
1 change: 1 addition & 0 deletions themes/default/content/docs/concepts/options/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ All resource constructors accept an options argument that provide the following
- [replaceOnChanges](/docs/concepts/options/replaceonchanges/): declare that changes to certain properties should be treated as forcing a replacement.
- [retainOnDelete](/docs/concepts/options/retainondelete/): if true the resource will be retained in the backing cloud provider during a Pulumi delete operation.
- [transformations](/docs/concepts/options/transformations/): dynamically transform a resource’s properties on the fly.
- [transforms](/docs/concepts/options/transforms/): dynamically transform a resource’s properties on the fly.
- [version](/docs/concepts/options/version/): pass a provider plugin version that should be used when operating on a resource.
6 changes: 4 additions & 2 deletions themes/default/content/docs/concepts/options/parent.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,12 @@ Previewing update (dev):

Child resources inherit default values for many other resource options from their `parent`, including:

* [`provider`](/docs/concepts/options/provider): The provider instance used to construct a resource is inherited from it's parent, unless explicitly overridden by the child resource. The parent itself may have inherited the global [default provider](../providers/#default-provider-configuration) if no resource in the parent chain specified a provider instance for the corresponding provider type.
* [`provider`](/docs/concepts/options/provider): The provider instance used to construct a resource is inherited from its parent, unless explicitly overridden by the child resource. The parent itself may have inherited the global [default provider](../providers/#default-provider-configuration) if no resource in the parent chain specified a provider instance for the corresponding provider type.

* [`aliases`](/docs/concepts/options/aliases): Aliases applied to a parent are applied to all child resources, so that changing the type of a parent resource correctly changes the qualified type of a child resource, and changing the name of a parent resource correctly changes the name prefix of child resources.

* [`protect`](/docs/concepts/options/protect): A protected parent will protect all children. This ensures that if a parent is marked as protected, none of it's children will be deleted ahead of the attempt to delete the parent failing.

* [`transformations`](/docs/concepts/options/transformations): Transformations applied to a parent will run on the parent and on all child resources. This allows a transformation to be applied to a component to intercept and modify any resources created by it's children. As a special case, [Stack transformations](/docs/concepts/options/transformations#stack-transformations) will be applied to *all* resources (since all resources ultimately are parented directly or indirectly by the root stack resource).
* [`transformations`](/docs/concepts/options/transformations): Transformations applied to a parent will run on the parent and on all child resources. This allows a transformation to be applied to a component to intercept and modify any resources created by its children. As a special case, [Stack transformations](/docs/concepts/options/transformations#stack-transformations) will be applied to *all* resources (since all resources ultimately are parented directly or indirectly by the root stack resource).

* [`transforms`](/docs/concepts/options/transforms): Transforms applied to a parent will run on the parent and on all child resources. This allows a transform to be applied to a component to intercept and modify any resources created by its children. As a special case, [Stack transforms](/docs/concepts/options/transforms#stack-transforms) will be applied to *all* resources (since all resources ultimately are parented directly or indirectly by the root stack resource).
325 changes: 325 additions & 0 deletions themes/default/content/docs/concepts/options/transforms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
---
title_tag: "transforms | Resource Options"
meta_desc: The transforms resource option provides a list of transforms to apply to a resource and all of its children.
title: "transforms"
h1: "Resource option: transforms"
meta_image: /images/docs/meta-images/docs-meta.png
menu:
concepts:
identifier: transforms
parent: options
weight: 15
aliases:
- /docs/intro/concepts/resources/options/transforms/
---

The `transforms` resource option provides a list of transforms to apply to a resource and all of its children. This option is used to override or modify the inputs to the child resources of a component resource. One example is to use the option to add other resource options (such as `ignoreChanges` or `protect`). Another example is to modify an input property (such as adding to tags or changing a property that is not directly configurable).

Each transform is a callback that gets invoked by the Pulumi engine. It receives the resource type, name, input properties, and resource options. The callback returns a new set of resource input properties and resource options that will be used to construct the resource instead of the original values.

## Transforms vs Transformations

The `transforms` option is the replacement system to [transformations](/docs/concepts/options/transformations/). Calling transform functions is managed by the engine, rather than by each SDK and so transforms apply to resources created inside remote component resources (such as those in [AWSX](https://www.pulumi.com/registry/packages/awsx/)).

However because `transforms` are invoked by the engine the properties are the values after being serialised and deserialised across the wire protocol to the engine and back to the SDK runtime. As such unlike with `transformations` there is no `Resource` object, and the properties will be raw property maps not typed resource arg classes.

The new transform system is also natively async. As such depending on the language your using you may have to lift your transform function to be `async` and/or to handle an extra parameter for the async context (e.g. a `context.Context` in Go, or `System.Threading.CancellationToken` in C#).

As such when moving from `transformations` to `transforms` you will need to update your transform code to handle these differences.

### No resource type

There is no `Resource` object passed to transform functions. But most of the information you could have retrieved from that object is presented on the transform arguments directly.

For example given the following C# transform:

```csharp
args =>
{
if (args.Resource.GetResourceType() == "random:index/randomString:RandomString")
{
var resultOpts = CustomResourceOptions.Merge((CustomResourceOptions)args.Options,
new CustomResourceOptions {AdditionalSecretOutputs = {"length"}});
return new ResourceTransformationResult(resultArgs, resultOpts);
}
return null;
}
```

We can rewrite this quite easily to:

```csharp
async (args, _) =>
{
if (args.Type == "random:index/randomString:RandomString")
{
var resultOpts = CustomResourceOptions.Merge((CustomResourceOptions)args.Options,
new CustomResourceOptions {AdditionalSecretOutputs = {"length"}});
return new ResourceTransformResult(resultArgs, resultOpts);
}
return null;
}
```

### No typed resource args class

In the old transform system the transform function was called with the same object that was passed to the resource constructor. This meant that
in typed languages like Go and C# you could typecast to the typed arguments struct.

```go
func(_ context.Context, rta *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
if rta.Type == "testprovider:index:Random" {
var props *RandomArgs
if rta.Props == nil {
props = &RandomArgs{}
} else {
props = rta.Props.(*RandomArgs)
}
props.Length = props.Length.ToOutput().ApplyT(func(v int) int { return v * 2 })

return &pulumi.ResourceTransformationResult{
Props: props,
Opts: rta.Opts,
}
}
return nil
}
```

The new transform system works over the wire protocol, allowing it to run for resources created in other
processes, but it means the properties object you get is closer to the raw protocol than the typed arguments
you might expect.

In this Go example the old system would have given us a typed `RandomArgs` structure with the length field
being a `pulumi.IntInput`. The new system is just a map where we have to know the key is `"length"` and the
value is a `Float64Output`.

You might ask why `Float64Output` instead of at least `IntOutput`. This is because the wire protocol doesn't
actually have support for integers (being JSON based), so on receiving the untyped properties the transform
callback can only go on their JSON values and all numbers are 64-bit floats.

```go
func(_ context.Context, rta *pulumi.XResourceTransformArgs) *pulumi.XResourceTransformResult {
if rta.Type == "testprovider:index:Random" {
length := rta.Props["length"].(pulumi.Float64Output)
rta.Props["length"] = length.ApplyT(func(v float64) float64 { return v * 2 })

return &pulumi.XResourceTransformResult{
Props: rta.Props,
Opts: rta.Opts,
}
}
return nil
},
```

### Natively async

The new transform API has been designed from the start with async support in mind.

In all applicable languages the transform functions support returning a Promise/Task so you can use standard `await` operators for async calls in the transform.

For Go we pass a `context.Context` in as the first argument for the transform function. This is to indicate its async nature, but also allows you access to the async context for tracing/logging/cancellation.

For DotNet we pass a `System.Threading.CancellationToken` as the last argument of the transform function. This allows you to handle cancellation if needed.

## VPC example

This example looks for all VPC and Subnet resources inside of a component’s child hierarchy and adds an option to ignore any changes for tags properties (perhaps because we manage all VPC and Subnet tags outside of Pulumi):

{{< chooser language "javascript,typescript,python,go,csharp,java,yaml" >}}

{{% choosable language javascript %}}

```javascript
const vpc = new MyVpcComponent("vpc", {}, {
transforms: [args => {
if (args.type === "aws:ec2/vpc:Vpc" || args.type === "aws:ec2/subnet:Subnet") {
return {
props: args.props,
opts: pulumi.mergeOptions(args.opts, { ignoreChanges: ["tags"] })
}
}
return undefined;
}],
});
```

{{% /choosable %}}
{{% choosable language typescript %}}

```typescript
const vpc = new MyVpcComponent("vpc", {}, {
transforms: [args => {
if (args.type === "aws:ec2/vpc:Vpc" || args.type === "aws:ec2/subnet:Subnet") {
return {
props: args.props,
opts: pulumi.mergeOptions(args.opts, { ignoreChanges: ["tags"] })
}
}
return undefined;
}],
});
```

{{% /choosable %}}
{{% choosable language python %}}

```python
def transform(args: ResourcetransformArgs):
if args.type_ == "aws:ec2/vpc:Vpc" or args.type_ == "aws:ec2/subnet:Subnet":
return ResourcetransformResult(
props=args.props,
opts=ResourceOptions.merge(args.opts, ResourceOptions(
ignore_changes=["tags"],
)))

vpc = MyVpcComponent("vpc", opts=ResourceOptions(transforms=[transform]))
```

{{% /choosable %}}
{{% choosable language go %}}

```go
transform := func(_ context.Context, args *pulumi.ResourceTransformArgs) *pulumi.ResourceTransformResult {
if args.Type == "aws:ec2/vpc:Vpc" || args.Type == "aws:ec2/subnet:Subnet" {
return &pulumi.ResourceTransformResult{
Props: args.Props,
Opts: append(args.Opts, pulumi.IgnoreChanges([]string{"tags"}))
}
}
return nil
}

vpc := MyVpcComponent("vpc", pulumi.transforms([]pulumi.ResourceTransform{transform}))
```

{{% /choosable %}}
{{% choosable language csharp %}}

```csharp
var vpc = new MyVpcComponent("vpc", new ComponentResourceOptions
{
ResourceTransforms =
{
(args, _) =>
{
if (args.Type == "aws:ec2/vpc:Vpc" ||
args.Type == "aws:ec2/subnet:Subnet")
{
var options = CustomResourceOptions.Merge(
(CustomResourceOptions) args.Options,
new CustomResourceOptions { IgnoreChanges = {"tags"} });
return new ResourcetransformResult(args.Args, options);
}

return null;
}
}
});
```

{{% /choosable %}}
{{% choosable language java %}}

```java
# Pulumi Java does not support transforms
```

{{% /choosable %}}
{{% choosable language yaml %}}

```yaml
# Pulumi YAML does not support transforms
```

{{% /choosable %}}

{{< /chooser >}}

## Stack transforms

Transforms can also be applied in bulk to many or all resources in a stack by using Stack transforms, which are applied to the root stack resource and as a result inherited by all other resources in the stack.

{{< chooser language "javascript,typescript,python,go,csharp,java,yaml" >}}

{{% choosable language javascript %}}

```javascript
pulumi.runtime.registerStackTransform((args) => {
if (isTaggable(args.type)) {
args.props["tags"] = Object.assign(args.props["tags"], autoTags);
return { props: args.props, opts: args.opts };
}
};
```
{{% /choosable %}}
{{% choosable language typescript %}}
```typescript
pulumi.runtime.registerStackTransform(args => {
// ...
});
```
{{% /choosable %}}
{{% choosable language python %}}
```python
def my_transform(args):
# ...

pulumi.runtime.register_stack_transform(my_transform)
```
{{% /choosable %}}
{{% choosable language go %}}
```go
ctx.RegisterStackTransform(
func(_ context.Context, args *pulumi.ResourceTransformArgs) *pulumi.ResourceTransformResult {
// ...
},
)
```
{{% /choosable %}}
{{% choosable language csharp %}}
```csharp
public class MyStack : Stack
{
public MyStack() : base(new StackOptions { ResourceTransforms = { MyTransform } })
{
...
}

private static ResourceTransformResult? MyTransform(ResourceTransformArgs args, CancellationToken ct)
{
// ...
}
}
```
{{% /choosable %}}
{{% choosable language java %}}
```java
# Pulumi Java does not support transforms
```
{{% /choosable %}}
{{% choosable language yaml %}}
```yaml
# Pulumi YAML does not support transforms
```
{{% /choosable %}}
{{< /chooser >}}
1 change: 1 addition & 0 deletions themes/default/content/docs/concepts/resources/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ aliases:
"#provider": "/docs/concepts/options/provider",
"#replaceonchanges": "/docs/concepts/options/replaceonchanges",
"#transformations": "/docs/concepts/options/transformations",
"#transforms": "/docs/concepts/options/transforms",
"#version": "/docs/concepts/options/version",
"#components": "/docs/concepts/resources/components",
"#authoring-a-new-component-resource": "/docs/concepts/resources/components/#authoring-a-new-component-resource",
Expand Down

0 comments on commit 8d9d6a8

Please sign in to comment.