Skip to content

Commit 94ba6da

Browse files
authored
Update Provisioner example with ability to trigger dynamic provider change (#513)
* Add a user-settable changeToken property to ConnectionArgs that can be used to detect changes to scripts. * Add the changeToken property to the input args of the provisioner and the componentresources.
1 parent c318e88 commit 94ba6da

File tree

5 files changed

+47
-5
lines changed

5 files changed

+47
-5
lines changed

aws-ts-ec2-provisioners/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
rsa
22
rsa.pub
33
node_modules/
4+
yarn.lock
5+
package-lock.json

aws-ts-ec2-provisioners/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import * as aws from "@pulumi/aws";
44
import * as pulumi from "@pulumi/pulumi";
55
import * as provisioners from "./provisioners";
6+
import { getFileHash } from "./util";
67

78
// Get the config ready to go.
89
const config = new pulumi.Config();
@@ -54,22 +55,25 @@ const server = new aws.ec2.Instance("server", {
5455
keyName: keyName,
5556
vpcSecurityGroupIds: [ secgrp.id ],
5657
});
57-
const conn = {
58+
const conn: provisioners.ConnectionArgs = {
5859
host: server.publicIp,
5960
username: "ec2-user",
6061
privateKey,
6162
privateKeyPassphrase,
6263
};
6364

65+
const changeToken = getFileHash("myapp.conf");
6466
// Copy a config file to our server.
6567
const cpConfig = new provisioners.CopyFile("config", {
68+
changeToken,
6669
conn,
6770
src: "myapp.conf",
6871
dest: "myapp.conf",
6972
}, { dependsOn: server });
7073

7174
// Execute a basic command on our server.
7275
const catConfig = new provisioners.RemoteExec("cat-config", {
76+
changeToken,
7377
conn,
7478
command: "cat myapp.conf",
7579
}, { dependsOn: cpConfig });

aws-ts-ec2-provisioners/provisioners/index.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,22 @@ export class CopyFile extends pulumi.ComponentResource {
129129
`${name}-provisioner`,
130130
{
131131
dep: args.conn,
132+
changeToken: args.changeToken,
132133
onCreate: (conn) => copyFile(conn, args.src, args.dest),
133134
},
134135
{ parent: this },
135136
);
136137
}
137138
}
138139

139-
export interface CopyFileArgs {
140+
interface ChangeTrackingProvisioner {
141+
// changeToken allows you to specify a value that controls the replacement of the dependent dynamic resources.
142+
// Specifing a value that changes with the content of the file(s) allows the provisioner resources to be
143+
// replaced every time the content(s) change.
144+
changeToken?: string;
145+
}
146+
147+
export interface CopyFileArgs extends ChangeTrackingProvisioner {
140148
// conn contains information on how to connect to the destination, in addition to dependency information.
141149
conn: pulumi.Input<ConnectionArgs>;
142150
// src is the source of the file or directory to copy. It can be specified as relative to the current
@@ -175,6 +183,7 @@ export class RemoteExec extends pulumi.ComponentResource {
175183
}
176184
return results;
177185
},
186+
changeToken: args.changeToken,
178187
},
179188
{ parent: this },
180189
);
@@ -184,7 +193,7 @@ export class RemoteExec extends pulumi.ComponentResource {
184193
}
185194
}
186195

187-
export interface RemoteExecArgs {
196+
export interface RemoteExecArgs extends ChangeTrackingProvisioner {
188197
// The connection to use for the remote command execution.
189198
conn: ConnectionArgs;
190199
// The command to execute. Exactly one of 'command' and 'commands' is required.

aws-ts-ec2-provisioners/provisioners/provisioner.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,23 @@ export class Provisioner<T, U> extends pulumi.dynamic.Resource {
1111
constructor(name: string, props: ProvisionerProperties<T, U>, opts?: pulumi.CustomResourceOptions) {
1212
const provider: pulumi.dynamic.ResourceProvider = {
1313
diff: async (id: pulumi.ID, olds: State<T, U>, news: State<T, U>) => {
14-
const replace = JSON.stringify(olds.dep) !== JSON.stringify(news.dep);
14+
let replace = false;
15+
let replacementProperties = [];
16+
if (JSON.stringify(olds.dep) !== JSON.stringify(news.dep)) {
17+
replace = true;
18+
replacementProperties.push("dep");
19+
}
20+
// Only trigger replacement due to the `changeToken` property, IFF
21+
// the changeToken still has a value in the new inputs, and it doesn't
22+
// match with the old value. If, say, the user decides to no longer specify
23+
// the changeToken in the new inputs, then we don't trigger a replacement.
24+
if (news.changeToken && olds.changeToken !== news.changeToken) {
25+
replace = true;
26+
replacementProperties.push("changeToken");
27+
}
1528
return {
1629
changes: replace,
17-
replaces: replace ? ["dep"] : undefined,
30+
replaces: replace ? replacementProperties : undefined,
1831
deleteBeforeReplace: true,
1932
};
2033
},
@@ -32,10 +45,12 @@ export class Provisioner<T, U> extends pulumi.dynamic.Resource {
3245

3346
export interface ProvisionerProperties<T, U> {
3447
dep: pulumi.Input<T>;
48+
changeToken: pulumi.Input<string>;
3549
onCreate: (dep: pulumi.Unwrap<T>) => Promise<pulumi.Unwrap<U>>;
3650
}
3751

3852
interface State<T, U> {
3953
dep: pulumi.Unwrap<T>;
54+
changeToken: pulumi.Unwrap<string>;
4055
result: pulumi.Unwrap<U>;
4156
}

aws-ts-ec2-provisioners/util.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2016-2020, Pulumi Corporation. All rights reserved.
2+
3+
import * as crypto from "crypto";
4+
import * as fs from "fs";
5+
import * as path from "path";
6+
7+
// getFileHash calculates a hash for all of the files under the scripts directory.
8+
export function getFileHash(filename: string): string {
9+
const data = fs.readFileSync(path.join(__dirname, filename), {encoding: "utf8"});
10+
const hash = crypto.createHash("md5").update(data, "utf8");
11+
return hash.digest("hex");
12+
}

0 commit comments

Comments
 (0)