Skip to content

Commit 744f59c

Browse files
committed
dotnet remote transforms
1 parent 125d467 commit 744f59c

26 files changed

+1755
-143
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
component: sdk
2+
kind: Improvements
3+
body: Add experimental support for the new transforms systemAdd experimental support
4+
for the new transforms system
5+
time: 2024-03-04T13:12:07.2773112Z
6+
custom:
7+
PR: "234"

.github/workflows/pr.yml

+2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ jobs:
142142
run: dotnet run integration test TestProvider
143143
- name: TestDeletedWith
144144
run: dotnet run integration test TestDeletedWith
145+
- name: TestDotNetTransforms
146+
run: dotnet run integration test TestDotNetTransforms
145147

146148
info:
147149
name: gather
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/.pulumi/
2+
[Bb]in/
3+
[Oo]bj/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2016-2024, Pulumi Corporation. All rights reserved.
2+
3+
using System;
4+
using System.Threading.Tasks;
5+
using Pulumi;
6+
7+
class MyComponent : ComponentResource
8+
{
9+
public Random Child { get; }
10+
11+
public MyComponent(string name, ComponentResourceOptions? options = null)
12+
: base("my:component:MyComponent", name, options)
13+
{
14+
this.Child = new Random($"{name}-child",
15+
new RandomArgs { Length = 5 },
16+
new CustomResourceOptions {Parent = this, AdditionalSecretOutputs = {"length"} });
17+
}
18+
}
19+
20+
class TransformsStack : Stack
21+
{
22+
public TransformsStack() : base(new StackOptions { XResourceTransforms = {Scenario3} })
23+
{
24+
// Scenario #1 - apply a transformation to a CustomResource
25+
var res1 = new Random("res1", new RandomArgs { Length = 5 }, new CustomResourceOptions
26+
{
27+
XResourceTransforms =
28+
{
29+
args =>
30+
{
31+
var options = CustomResourceOptions.Merge(
32+
(CustomResourceOptions)args.Options,
33+
new CustomResourceOptions {AdditionalSecretOutputs = {"result"}});
34+
return new ResourceTransformResult(args.Args, options);
35+
}
36+
}
37+
});
38+
39+
// Scenario #2 - apply a transformation to a Component to transform its children
40+
var res2 = new MyComponent("res2", new ComponentResourceOptions
41+
{
42+
XResourceTransforms =
43+
{
44+
args =>
45+
{
46+
if (args.Resource.GetResourceType() == "testprovider:index:Random")
47+
{
48+
var resultArgs = args.Args;
49+
resultArgs = resultArgs.Add("prefix", "newDefault");
50+
51+
var options = CustomResourceOptions.Merge(
52+
(CustomResourceOptions)args.Options,
53+
new CustomResourceOptions {AdditionalSecretOutputs = {"result"}});
54+
55+
return new ResourceTransformResult(resultArgs, resultOpts);
56+
}
57+
58+
return null;
59+
}
60+
}
61+
});
62+
63+
// Scenario #3 - apply a transformation to the Stack to transform all resources in the stack.
64+
var res3 = new Random("res3", new RandomArgs { Length = Output.Secret(5) });
65+
66+
// Scenario #4 - Transforms are applied in order of decreasing specificity
67+
// 1. (not in this example) Child transformation
68+
// 2. First parent transformation
69+
// 3. Second parent transformation
70+
// 4. Stack transformation
71+
var res4 = new MyComponent("res4", new ComponentResourceOptions
72+
{
73+
XResourceTransforms = { args => scenario4(args, "value1"), args => scenario4(args, "value2") }
74+
});
75+
76+
ResourceTransformResult? scenario4(ResourceTransformArgs args, string v)
77+
{
78+
if (args.Resource.GetResourceType() == RandomStringType && args.Args is RandomStringArgs oldArgs)
79+
{
80+
var resultArgs = new RandomStringArgs
81+
{Length = oldArgs.Length, OverrideSpecial = Output.Format($"{oldArgs.OverrideSpecial}{v}")};
82+
return new ResourceTransformResult(resultArgs, args.Options);
83+
}
84+
85+
return null;
86+
}
87+
}
88+
89+
// Scenario #3 - apply a transformation to the Stack to transform all (future) resources in the stack
90+
private static ResourceTransformResult? Scenario3(ResourceTransformArgs args)
91+
{
92+
if (args.Resource.GetResourceType() == "testprovider:index:Random")
93+
{
94+
var resultArgs = args.Args;
95+
resultArgs = resultArgs.Add("prefix", "stackDefault");
96+
97+
var options = CustomResourceOptions.Merge(
98+
(CustomResourceOptions)args.Options,
99+
new CustomResourceOptions {AdditionalSecretOutputs = {"result"}});
100+
101+
return new ResourceTransformResult(resultArgs, resultOpts);
102+
}
103+
104+
return null;
105+
}
106+
107+
private const string RandomStringType = "random:index/randomString:RandomString";
108+
}
109+
110+
class Program
111+
{
112+
static Task<int> Main(string[] args) => Deployment.RunAsync<TransformsStack>();
113+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
name: transformations_dotnet
2+
description: A simple .NET program that uses transformations.
3+
runtime: dotnet
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2016-2021, Pulumi Corporation. All rights reserved.
2+
3+
// Exposes the Random resource from the testprovider.
4+
5+
using Pulumi;
6+
7+
public partial class Random : Pulumi.CustomResource
8+
{
9+
[Output("length")]
10+
public Output<int> Length { get; private set; } = null!;
11+
12+
[Output("result")]
13+
public Output<string> Result { get; private set; } = null!;
14+
15+
public Random(string name, RandomArgs args, CustomResourceOptions? options = null)
16+
: base("testprovider:index:Random", name, args ?? new RandomArgs(), options)
17+
{
18+
}
19+
}
20+
21+
public sealed class RandomArgs : Pulumi.ResourceArgs
22+
{
23+
[Input("length", required: true)]
24+
public Input<int> Length { get; set; } = null!;
25+
26+
public RandomArgs()
27+
{
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net6.0</TargetFramework>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
</Project>

integration_tests/transformations_simple/Program.cs

+14-14
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
class MyComponent : ComponentResource
99
{
1010
public RandomString Child { get; }
11-
11+
1212
public MyComponent(string name, ComponentResourceOptions? options = null)
1313
: base("my:component:MyComponent", name, options)
1414
{
@@ -24,29 +24,29 @@ class MyOtherComponent : ComponentResource
2424
{
2525
public RandomString Child1 { get; }
2626
public RandomString Child2 { get; }
27-
27+
2828
public MyOtherComponent(string name, ComponentResourceOptions? options = null)
2929
: base("my:component:MyComponent", name, options)
3030
{
3131
this.Child1 = new RandomString($"{name}-child1",
3232
new RandomStringArgs { Length = 5 },
3333
new CustomResourceOptions { Parent = this });
34-
34+
3535
this.Child2 = new RandomString($"{name}-child2",
3636
new RandomStringArgs { Length = 6 },
3737
new CustomResourceOptions { Parent = this });
3838
}
3939
}
4040

4141
class TransformationsStack : Stack
42-
{
42+
{
4343
public TransformationsStack() : base(new StackOptions { ResourceTransformations = {Scenario3} })
4444
{
4545
// Scenario #1 - apply a transformation to a CustomResource
4646
var res1 = new RandomString("res1", new RandomStringArgs { Length = 5 }, new CustomResourceOptions
4747
{
4848
ResourceTransformations =
49-
{
49+
{
5050
args =>
5151
{
5252
var options = CustomResourceOptions.Merge(
@@ -56,7 +56,7 @@ public TransformationsStack() : base(new StackOptions { ResourceTransformations
5656
}
5757
}
5858
});
59-
59+
6060
// Scenario #2 - apply a transformation to a Component to transform its children
6161
var res2 = new MyComponent("res2", new ComponentResourceOptions
6262
{
@@ -76,10 +76,10 @@ public TransformationsStack() : base(new StackOptions { ResourceTransformations
7676
}
7777
}
7878
});
79-
79+
8080
// Scenario #3 - apply a transformation to the Stack to transform all resources in the stack.
8181
var res3 = new RandomString("res3", new RandomStringArgs { Length = 5 });
82-
82+
8383
// Scenario #4 - transformations are applied in order of decreasing specificity
8484
// 1. (not in this example) Child transformation
8585
// 2. First parent transformation
@@ -89,7 +89,7 @@ public TransformationsStack() : base(new StackOptions { ResourceTransformations
8989
{
9090
ResourceTransformations = { args => scenario4(args, "value1"), args => scenario4(args, "value2") }
9191
});
92-
92+
9393
ResourceTransformationResult? scenario4(ResourceTransformationArgs args, string v)
9494
{
9595
if (args.Resource.GetResourceType() == RandomStringType && args.Args is RandomStringArgs oldArgs)
@@ -145,18 +145,18 @@ ResourceTransformation transformChild1DependsOnChild2()
145145
return child2Args.Length;
146146
})
147147
.Apply(output => output);
148-
148+
149149
var newArgs = new RandomStringArgs {Length = child2Length};
150-
150+
151151
return new ResourceTransformationResult(newArgs, args.Options);
152152
}
153153
}
154-
154+
155155
return null;
156156
}
157157
}
158158
}
159-
159+
160160
// Scenario #3 - apply a transformation to the Stack to transform all (future) resources in the stack
161161
private static ResourceTransformationResult? Scenario3(ResourceTransformationArgs args)
162162
{
@@ -172,7 +172,7 @@ ResourceTransformation transformChild1DependsOnChild2()
172172
}
173173

174174
return null;
175-
}
175+
}
176176

177177
private const string RandomStringType = "random:index/randomString:RandomString";
178178
}

integration_tests/transformations_simple_test.go

+81
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,84 @@ func dotNetValidator() func(t *testing.T, stack integration.RuntimeValidationSta
8585
assert.True(t, foundRes5Child)
8686
}
8787
}
88+
89+
func TestDotNetTransforms(t *testing.T) {
90+
testDotnetProgram(t, &integration.ProgramTestOptions{
91+
Dir: "transformations_remote",
92+
Quick: true,
93+
ExtraRuntimeValidation: Validator,
94+
LocalProviders: []integration.LocalDependency{
95+
{
96+
Package: "testprovider",
97+
Path: "testprovider",
98+
},
99+
},
100+
})
101+
}
102+
103+
func Validator(t *testing.T, stack integration.RuntimeValidationStackInfo) {
104+
randomResName := "testprovider:index:Random"
105+
foundRes1 := false
106+
foundRes2Child := false
107+
foundRes3 := false
108+
foundRes4Child := false
109+
foundRes5 := false
110+
for _, res := range stack.Deployment.Resources {
111+
// "res1" has a transformation which adds additionalSecretOutputs
112+
if res.URN.Name() == "res1" {
113+
foundRes1 = true
114+
assert.Equal(t, res.Type, tokens.Type(randomResName))
115+
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("result"))
116+
}
117+
// "res2" has a transformation which adds additionalSecretOutputs to it's
118+
// "child"
119+
if res.URN.Name() == "res2-child" {
120+
foundRes2Child = true
121+
assert.Equal(t, res.Type, tokens.Type(randomResName))
122+
assert.Equal(t, res.Parent.Type(), tokens.Type("my:component:MyComponent"))
123+
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("result"))
124+
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("length"))
125+
}
126+
// "res3" is impacted by a global stack transformation which sets
127+
// optionalDefault to "stackDefault"
128+
if res.URN.Name() == "res3" {
129+
foundRes3 = true
130+
assert.Equal(t, res.Type, tokens.Type(randomResName))
131+
optionalPrefix := res.Inputs["prefix"]
132+
assert.NotNil(t, optionalPrefix)
133+
assert.Equal(t, "stackDefault", optionalPrefix.(string))
134+
length := res.Inputs["length"]
135+
assert.NotNil(t, length)
136+
// length should be secret
137+
secret, ok := length.(map[string]interface{})
138+
assert.True(t, ok, "length should be a secret")
139+
assert.Equal(t, resource.SecretSig, secret[resource.SigKey])
140+
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("result"))
141+
}
142+
// "res4" is impacted by two component parent transformations which set
143+
// optionalDefault to "default1" and then "default2" and also a global stack
144+
// transformation which sets optionalDefault to "stackDefault". The end
145+
// result should be "stackDefault".
146+
if res.URN.Name() == "res4-child" {
147+
foundRes4Child = true
148+
assert.Equal(t, res.Type, tokens.Type(randomResName))
149+
assert.Equal(t, res.Parent.Type(), tokens.Type("my:component:MyComponent"))
150+
optionalPrefix := res.Inputs["prefix"]
151+
assert.NotNil(t, optionalPrefix)
152+
assert.Equal(t, "stackDefault", optionalPrefix.(string))
153+
}
154+
// "res5" should have mutated the length
155+
if res.URN.Name() == "res5" {
156+
foundRes5 = true
157+
assert.Equal(t, res.Type, tokens.Type(randomResName))
158+
length := res.Inputs["length"]
159+
assert.NotNil(t, length)
160+
assert.Equal(t, 20.0, length.(float64))
161+
}
162+
}
163+
assert.True(t, foundRes1)
164+
assert.True(t, foundRes2Child)
165+
assert.True(t, foundRes3)
166+
assert.True(t, foundRes4Child)
167+
assert.True(t, foundRes5)
168+
}

0 commit comments

Comments
 (0)