Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for remote transforms #234

Merged
merged 1 commit into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unreleased/Improvements-234.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
component: sdk
kind: Improvements
body: Add experimental support for the new transforms system
time: 2024-03-04T13:12:07.2773112Z
custom:
PR: "234"
2 changes: 2 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ jobs:
run: dotnet run integration test TestProvider
- name: TestDeletedWith
run: dotnet run integration test TestDeletedWith
- name: TestDotNetTransforms
run: dotnet run integration test TestDotNetTransforms

info:
name: gather
Expand Down
3 changes: 3 additions & 0 deletions integration_tests/transformations_remote/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/.pulumi/
[Bb]in/
[Oo]bj/
132 changes: 132 additions & 0 deletions integration_tests/transformations_remote/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2016-2024, Pulumi Corporation. All rights reserved.

using System;
using System.Threading;
using System.Threading.Tasks;
using Pulumi;

class MyComponent : ComponentResource
{
public Random Child { get; }

public MyComponent(string name, ComponentResourceOptions? options = null)
: base("my:component:MyComponent", name, options)
{
this.Child = new Random($"{name}-child",
new RandomArgs { Length = 5 },
new CustomResourceOptions {Parent = this, AdditionalSecretOutputs = {"length"} });
}
}

class TransformsStack : Stack
{
public TransformsStack() : base(new StackOptions { XResourceTransforms = {Scenario3} })
{
// Scenario #1 - apply a transformation to a CustomResource
var res1 = new Random("res1", new RandomArgs { Length = 5 }, new CustomResourceOptions
{
XResourceTransforms =
{
async (args, _) =>
{
var options = CustomResourceOptions.Merge(
(CustomResourceOptions)args.Options,
new CustomResourceOptions {AdditionalSecretOutputs = {"result"}});
return new ResourceTransformResult(args.Args, options);
}
}
});

// Scenario #2 - apply a transformation to a Component to transform its children
var res2 = new MyComponent("res2", new ComponentResourceOptions
{
XResourceTransforms =
{
async (args, _) =>
{
if (args.Type == "testprovider:index:Random")
{
var resultArgs = args.Args;
resultArgs = resultArgs.SetItem("prefix", "newDefault");

var resultOpts = CustomResourceOptions.Merge(
(CustomResourceOptions)args.Options,
new CustomResourceOptions {AdditionalSecretOutputs = {"result"}});

return new ResourceTransformResult(resultArgs, resultOpts);
}

return null;
}
}
});

// Scenario #3 - apply a transformation to the Stack to transform all resources in the stack.
var res3 = new Random("res3", new RandomArgs { Length = Output.CreateSecret(5) });

// Scenario #4 - Transforms 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
var res4 = new MyComponent("res4", new ComponentResourceOptions
{
XResourceTransforms = { (args, _) => scenario4(args, "default1"), (args, _) => scenario4(args, "default2") }
});

async Task<ResourceTransformResult?> scenario4(ResourceTransformArgs args, string v)
{
if (args.Type == "testprovider:index:Random")
{
var resultArgs = args.Args;
resultArgs = resultArgs.SetItem("prefix", v);
return new ResourceTransformResult(resultArgs, args.Options);
}

return null;
}

// Scenario #5 - mutate the properties of a resource
var res5 = new Random("res5", new RandomArgs { Length = 10 }, new CustomResourceOptions
{
XResourceTransforms =
{
async (args, _) =>
{
if (args.Type == "testprovider:index:Random")
{
var resultArgs = args.Args;
var length = (double)resultArgs["length"] * 2;
resultArgs = resultArgs.SetItem("length", length);
return new ResourceTransformResult(resultArgs, args.Options);
}

return null;
}
}
});
}

// Scenario #3 - apply a transformation to the Stack to transform all (future) resources in the stack
private static async Task<ResourceTransformResult?> Scenario3(ResourceTransformArgs args, CancellationToken ct)
{
if (args.Type == "testprovider:index:Random")
{
var resultArgs = args.Args;
resultArgs = resultArgs.SetItem("prefix", "stackDefault");

var resultOpts = CustomResourceOptions.Merge(
(CustomResourceOptions)args.Options,
new CustomResourceOptions {AdditionalSecretOutputs = {"result"}});

return new ResourceTransformResult(resultArgs, resultOpts);
}

return null;
}
}

class Program
{
static Task<int> Main(string[] args) => Deployment.RunAsync<TransformsStack>();
}
3 changes: 3 additions & 0 deletions integration_tests/transformations_remote/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: transformations_dotnet
description: A simple .NET program that uses transformations.
runtime: dotnet
29 changes: 29 additions & 0 deletions integration_tests/transformations_remote/Random.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2016-2021, Pulumi Corporation. All rights reserved.

// Exposes the Random resource from the testprovider.

using Pulumi;

public partial class Random : Pulumi.CustomResource
{
[Output("length")]
public Output<int> Length { get; private set; } = null!;

[Output("result")]
public Output<string> Result { get; private set; } = null!;

public Random(string name, RandomArgs args, CustomResourceOptions? options = null)
: base("testprovider:index:Random", name, args ?? new RandomArgs(), options)
{
}
}

public sealed class RandomArgs : Pulumi.ResourceArgs
{
[Input("length", required: true)]
public Input<int> Length { get; set; } = null!;

public RandomArgs()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
28 changes: 14 additions & 14 deletions integration_tests/transformations_simple/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class MyComponent : ComponentResource
{
public RandomString Child { get; }

public MyComponent(string name, ComponentResourceOptions? options = null)
: base("my:component:MyComponent", name, options)
{
Expand All @@ -24,29 +24,29 @@ class MyOtherComponent : ComponentResource
{
public RandomString Child1 { get; }
public RandomString Child2 { get; }

public MyOtherComponent(string name, ComponentResourceOptions? options = null)
: base("my:component:MyComponent", name, options)
{
this.Child1 = new RandomString($"{name}-child1",
new RandomStringArgs { Length = 5 },
new CustomResourceOptions { Parent = this });

this.Child2 = new RandomString($"{name}-child2",
new RandomStringArgs { Length = 6 },
new CustomResourceOptions { Parent = this });
}
}

class TransformationsStack : Stack
{
{
public TransformationsStack() : base(new StackOptions { ResourceTransformations = {Scenario3} })
{
// Scenario #1 - apply a transformation to a CustomResource
var res1 = new RandomString("res1", new RandomStringArgs { Length = 5 }, new CustomResourceOptions
{
ResourceTransformations =
{
{
args =>
{
var options = CustomResourceOptions.Merge(
Expand All @@ -56,7 +56,7 @@ public TransformationsStack() : base(new StackOptions { ResourceTransformations
}
}
});

// Scenario #2 - apply a transformation to a Component to transform its children
var res2 = new MyComponent("res2", new ComponentResourceOptions
{
Expand All @@ -76,10 +76,10 @@ public TransformationsStack() : base(new StackOptions { ResourceTransformations
}
}
});

// Scenario #3 - apply a transformation to the Stack to transform all resources in the stack.
var res3 = new RandomString("res3", new RandomStringArgs { Length = 5 });

// Scenario #4 - transformations are applied in order of decreasing specificity
// 1. (not in this example) Child transformation
// 2. First parent transformation
Expand All @@ -89,7 +89,7 @@ public TransformationsStack() : base(new StackOptions { ResourceTransformations
{
ResourceTransformations = { args => scenario4(args, "value1"), args => scenario4(args, "value2") }
});

ResourceTransformationResult? scenario4(ResourceTransformationArgs args, string v)
{
if (args.Resource.GetResourceType() == RandomStringType && args.Args is RandomStringArgs oldArgs)
Expand Down Expand Up @@ -145,18 +145,18 @@ ResourceTransformation transformChild1DependsOnChild2()
return child2Args.Length;
})
.Apply(output => output);

var newArgs = new RandomStringArgs {Length = child2Length};

return new ResourceTransformationResult(newArgs, args.Options);
}
}

return null;
}
}
}

// Scenario #3 - apply a transformation to the Stack to transform all (future) resources in the stack
private static ResourceTransformationResult? Scenario3(ResourceTransformationArgs args)
{
Expand All @@ -172,7 +172,7 @@ ResourceTransformation transformChild1DependsOnChild2()
}

return null;
}
}

private const string RandomStringType = "random:index/randomString:RandomString";
}
Expand Down
81 changes: 81 additions & 0 deletions integration_tests/transformations_simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,84 @@ func dotNetValidator() func(t *testing.T, stack integration.RuntimeValidationSta
assert.True(t, foundRes5Child)
}
}

func TestDotNetTransforms(t *testing.T) {
testDotnetProgram(t, &integration.ProgramTestOptions{
Dir: "transformations_remote",
Quick: true,
ExtraRuntimeValidation: Validator,
LocalProviders: []integration.LocalDependency{
{
Package: "testprovider",
Path: "testprovider",
},
},
})
}

func Validator(t *testing.T, stack integration.RuntimeValidationStackInfo) {
randomResName := "testprovider:index:Random"
foundRes1 := false
foundRes2Child := false
foundRes3 := false
foundRes4Child := false
foundRes5 := false
for _, res := range stack.Deployment.Resources {
// "res1" has a transformation which adds additionalSecretOutputs
if res.URN.Name() == "res1" {
foundRes1 = true
assert.Equal(t, res.Type, tokens.Type(randomResName))
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("result"))
}
// "res2" has a transformation which adds additionalSecretOutputs to it's
// "child"
if res.URN.Name() == "res2-child" {
foundRes2Child = true
assert.Equal(t, res.Type, tokens.Type(randomResName))
assert.Equal(t, res.Parent.Type(), tokens.Type("my:component:MyComponent"))
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("result"))
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("length"))
}
// "res3" is impacted by a global stack transformation which sets
// optionalDefault to "stackDefault"
if res.URN.Name() == "res3" {
foundRes3 = true
assert.Equal(t, res.Type, tokens.Type(randomResName))
optionalPrefix := res.Inputs["prefix"]
assert.NotNil(t, optionalPrefix)
assert.Equal(t, "stackDefault", optionalPrefix.(string))
length := res.Inputs["length"]
assert.NotNil(t, length)
// length should be secret
secret, ok := length.(map[string]interface{})
assert.True(t, ok, "length should be a secret")
assert.Equal(t, resource.SecretSig, secret[resource.SigKey])
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("result"))
}
// "res4" is impacted by two component parent transformations which set
// optionalDefault to "default1" and then "default2" and also a global stack
// transformation which sets optionalDefault to "stackDefault". The end
// result should be "stackDefault".
if res.URN.Name() == "res4-child" {
foundRes4Child = true
assert.Equal(t, res.Type, tokens.Type(randomResName))
assert.Equal(t, res.Parent.Type(), tokens.Type("my:component:MyComponent"))
optionalPrefix := res.Inputs["prefix"]
assert.NotNil(t, optionalPrefix)
assert.Equal(t, "stackDefault", optionalPrefix.(string))
}
// "res5" should have mutated the length
if res.URN.Name() == "res5" {
foundRes5 = true
assert.Equal(t, res.Type, tokens.Type(randomResName))
length := res.Inputs["length"]
assert.NotNil(t, length)
assert.Equal(t, 20.0, length.(float64))
}
}
assert.True(t, foundRes1)
assert.True(t, foundRes2Child)
assert.True(t, foundRes3)
assert.True(t, foundRes4Child)
assert.True(t, foundRes5)
}
Loading
Loading