Skip to content

Commit fa03b2e

Browse files
author
Luke Hoban
authored
Fix up replacement semantics (#17)
1 parent b39f6c5 commit fa03b2e

File tree

24 files changed

+289
-144
lines changed

24 files changed

+289
-144
lines changed

examples/examples_nodejs_test.go

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import (
99
"path/filepath"
1010
"testing"
1111

12-
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
13-
1412
"github.com/aws/aws-sdk-go/aws"
1513
"github.com/aws/aws-sdk-go/aws/session"
1614
"github.com/aws/aws-sdk-go/service/ec2"
1715
"github.com/pulumi/pulumi/pkg/v3/testing/integration"
16+
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
17+
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
1818
"github.com/stretchr/testify/assert"
1919
)
2020

@@ -36,11 +36,43 @@ func TestSimple(t *testing.T) {
3636
With(integration.ProgramTestOptions{
3737
Dir: filepath.Join(getCwd(t), "simple"),
3838
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {},
39-
EditDirs: []integration.EditDir{{
40-
Dir: filepath.Join("simple", "fail"),
41-
Additive: true,
42-
ExpectFailure: true,
43-
}},
39+
EditDirs: []integration.EditDir{
40+
{
41+
Dir: filepath.Join("simple", "update"),
42+
Additive: true,
43+
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
44+
replaces := 0
45+
for _, ev := range stack.Events {
46+
if ev.ResourcePreEvent != nil {
47+
if ev.ResourcePreEvent.Metadata.Op == apitype.OpReplace {
48+
replaces++
49+
}
50+
}
51+
}
52+
assert.Equal(t, 0, replaces)
53+
},
54+
},
55+
{
56+
Dir: filepath.Join("simple", "replace"),
57+
Additive: true,
58+
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
59+
replaces := 0
60+
for _, ev := range stack.Events {
61+
if ev.ResourcePreEvent != nil {
62+
if ev.ResourcePreEvent.Metadata.Op == apitype.OpReplace {
63+
replaces++
64+
}
65+
}
66+
}
67+
assert.Equal(t, 4, replaces)
68+
},
69+
},
70+
{
71+
Dir: filepath.Join("simple", "fail"),
72+
Additive: true,
73+
ExpectFailure: true,
74+
},
75+
},
4476
})
4577
integration.ProgramTest(t, &test)
4678
}

examples/simple/extras.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const len = 10;
2+
export const fail = false;
3+
export const update = false;

examples/simple/fail/extras.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const len = 20;
2+
export const fail = true;
3+
export const update = false;

examples/simple/fail/index.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

examples/simple/index.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,44 @@
11
import * as local from "@pulumi/command/local";
22
import * as random from "@pulumi/random";
33
import { interpolate } from "@pulumi/pulumi";
4+
import { len, fail, update } from "./extras";
45

5-
const pw = new random.RandomPassword("pw", { length: 10 });
6+
const pw = new random.RandomPassword("pw", { length: len });
67

78
const pwd = new local.Command("pwd", {
89
create: interpolate`echo "${pw.result}" > password.txt`,
910
delete: `rm -f password.txt`,
11+
triggers: [pw.result],
1012
}, { deleteBeforeReplace: true });
1113

14+
15+
let deleteCommand = "rm -f password2.txt";
16+
if (update) {
17+
deleteCommand += " && echo 'deleted'";
18+
}
1219
const pwd2 = new local.Command("pwd2", {
1320
create: `echo "$PASSWORD" > password2.txt`,
14-
delete: `rm -f password2.txt`,
21+
delete: deleteCommand,
1522
environment: {
1623
PASSWORD: pw.result,
17-
}
24+
},
25+
triggers: [pw.result],
1826
}, { deleteBeforeReplace: true });
1927

20-
// const fail = new local.Command("fail", {
21-
// create: `echo "couldn't do what I wanted..." && false`,
22-
// });
28+
// Manage an external artifact which is created after a resource is created,
29+
// deleted before a resource is destroyed, and recreated when the resource is
30+
// replaced. This could also register/deregister the resource with an external
31+
// registration or other remote API instead of just writing to local disk.
32+
const pwd3 = new local.Command("pwd3", {
33+
create: interpolate`touch "${pw.result}.txt"`,
34+
delete: interpolate`rm "${pw.result}.txt"`,
35+
triggers: [pw.result],
36+
})
37+
38+
if (fail) {
39+
new local.Command("fail", {
40+
create: `echo "couldn't do what I wanted..." && false`,
41+
});
42+
}
2343

2444
export const output = pwd.stdout;

examples/simple/replace/extras.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const len = 20;
2+
export const fail = false;
3+
export const update = false;

examples/simple/update/extras.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const len = 10;
2+
export const fail = false;
3+
export const update = true;

provider/cmd/pulumi-resource-command/schema.json

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,17 @@
6868
"type": "string"
6969
}
7070
},
71+
"triggers": {
72+
"type": "array",
73+
"description": "Trigger replacements on changes to this input.",
74+
"items": {
75+
"$ref": "pulumi.json#/Any"
76+
}
77+
},
7178
"create": {
7279
"type": "string",
7380
"description": "The command to run on create."
7481
},
75-
"update": {
76-
"type": "string",
77-
"description": "The command to run on update."
78-
},
7982
"delete": {
8083
"type": "string",
8184
"description": "The command to run on delete."
@@ -112,14 +115,16 @@
112115
"type": "string"
113116
}
114117
},
118+
"triggers": {
119+
"type": "array",
120+
"items": {
121+
"$ref": "pulumi.json#/Any"
122+
}
123+
},
115124
"create": {
116125
"type": "string",
117126
"description": "The command to run on create."
118127
},
119-
"update": {
120-
"type": "string",
121-
"description": "The command to run on update."
122-
},
123128
"delete": {
124129
"type": "string",
125130
"description": "The command to run on delete."
@@ -141,14 +146,17 @@
141146
"type": "string"
142147
}
143148
},
149+
"triggers": {
150+
"description": "Trigger replacements on changes to this input.",
151+
"type": "array",
152+
"items": {
153+
"$ref": "pulumi.json#/Any"
154+
}
155+
},
144156
"create": {
145157
"description": "The command to run on create.",
146158
"type": "string"
147159
},
148-
"update": {
149-
"description": "The command to run on update.",
150-
"type": "string"
151-
},
152160
"delete": {
153161
"description": "The command to run on delete.",
154162
"type": "string"
@@ -178,14 +186,17 @@
178186
"type": "string"
179187
}
180188
},
189+
"triggers": {
190+
"description": "Trigger replacements on changes to this input.",
191+
"type": "array",
192+
"items": {
193+
"$ref": "pulumi.json#/Any"
194+
}
195+
},
181196
"create": {
182197
"description": "The command to run on create.",
183198
"type": "string"
184199
},
185-
"update": {
186-
"description": "The command to run on update.",
187-
"type": "string"
188-
},
189200
"delete": {
190201
"description": "The command to run on delete.",
191202
"type": "string"
@@ -202,6 +213,13 @@
202213
"description": "The parameters with which to connect to the remote host.",
203214
"$ref": "#/types/command:remote:Connection"
204215
},
216+
"triggers": {
217+
"description": "Trigger replacements on changes to this input.",
218+
"type": "array",
219+
"items": {
220+
"$ref": "pulumi.json#/Any"
221+
}
222+
},
205223
"localPath": {
206224
"description": "The path of the file to be copied.",
207225
"type": "string"
@@ -221,6 +239,13 @@
221239
"description": "The parameters with which to connect to the remote host.",
222240
"$ref": "#/types/command:remote:Connection"
223241
},
242+
"triggers": {
243+
"description": "Trigger replacements on changes to this input.",
244+
"type": "array",
245+
"items": {
246+
"$ref": "pulumi.json#/Any"
247+
}
248+
},
224249
"localPath": {
225250
"description": "The path of the file to be copied.",
226251
"type": "string"

provider/pkg/provider/command.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type command struct {
3535
Interpreter *[]string `pulumi:"interpreter,optional"`
3636
Dir *string `pulumi:"dir,optional"`
3737
Environment *map[string]string `pulumi:"environment,optional"`
38+
Triggers *[]interface{} `pulumi:"triggers,optional"`
3839
Create string `pulumi:"create"`
3940
Delete *string `pulumi:"delete,optional"`
4041

provider/pkg/provider/provider.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,24 +130,39 @@ func (k *commandProvider) Diff(ctx context.Context, req *pulumirpc.DiffRequest)
130130
}
131131

132132
changes := pulumirpc.DiffResponse_DIFF_NONE
133-
replaces := []string{}
133+
var diffs, replaces []string
134+
properties := map[string]bool{
135+
"environment": false,
136+
"dir": false,
137+
"interpreter": false,
138+
"create": false,
139+
"delete": false,
140+
"localPath": false,
141+
"remotePath": false,
142+
"connection": true,
143+
"triggers": true,
144+
}
134145
if d := olds.Diff(news); d != nil {
135-
// TODO: Non-replace changes
136-
for _, replaceKey := range []string{"environment", "dir", "interpreter", "create", "connection", "localPath", "remotePath"} {
137-
i := sort.SearchStrings(req.IgnoreChanges, replaceKey)
138-
if i < len(req.IgnoreChanges) && req.IgnoreChanges[i] == replaceKey {
146+
for key, replace := range properties {
147+
i := sort.SearchStrings(req.IgnoreChanges, key)
148+
if i < len(req.IgnoreChanges) && req.IgnoreChanges[i] == key {
139149
continue
140150
}
141-
if d.Changed(resource.PropertyKey(replaceKey)) {
151+
152+
if d.Changed(resource.PropertyKey(key)) {
142153
changes = pulumirpc.DiffResponse_DIFF_SOME
143-
replaces = append(replaces, replaceKey)
154+
diffs = append(diffs, key)
155+
156+
if replace {
157+
replaces = append(replaces, key)
158+
}
144159
}
145160
}
146161
}
147-
// TODO: Detailed diffs
148162

149163
return &pulumirpc.DiffResponse{
150164
Changes: changes,
165+
Diffs: diffs,
151166
Replaces: replaces,
152167
}, nil
153168
}
@@ -256,13 +271,14 @@ func (k *commandProvider) Update(ctx context.Context, req *pulumirpc.UpdateReque
256271
ctx = k.addContext(ctx)
257272
defer k.removeContext(ctx)
258273
urn := resource.URN(req.GetUrn())
259-
ty := urn.Type()
260274
if err := check(urn); err != nil {
261275
return nil, err
262276
}
263277

264-
// Our Random resource will never be updated - if there is a diff, it will be a replacement.
265-
return nil, status.Errorf(codes.Unimplemented, "Update is not yet implemented for %q", ty)
278+
// Updates are currently no-ops. The `create` command does not re-run except on replacement.
279+
return &pulumirpc.UpdateResponse{
280+
Properties: req.GetNews(),
281+
}, nil
266282
}
267283

268284
// Delete tears down an existing resource with the given ID. If it fails, the resource is assumed

0 commit comments

Comments
 (0)