-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathupdate-lambda-edge-function.js
executable file
·172 lines (133 loc) · 6.64 KB
/
update-lambda-edge-function.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env node
// Author: Einar Egilsson, https://github.com/einaregilsson/update-lambda-edge-function
const awsApiRequest = require('./aws-api-request');
const env = process.env;
const IS_GITHUB_ACTION = !!process.env.GITHUB_ACTIONS;
if (!IS_GITHUB_ACTION) {
if (process.argv.length === 5) {
[_, _, env.CLOUDFRONT_DISTRIBUTION_ID, env.INPUT_FUNCTION_NAME, env.INPUT_NEW_VERSION_NR] = process.argv;
} else {
console.log('\n\n*** Update Lambda@Edge function version nr ***');
console.log('\nUpdate the version of a Lambda@Edge function used in a Cloudfront Distribution');
console.log('\nhttps://github.com/einaregilsson/update-lambda-edge-function\n');
console.log(' Usage: update-lambda-edge-function.js <cloudfront-distribution-id> <function-name> <new-version-nr>\n');
console.log('Environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be defined for the program to work.');
console.log('\nThe program will attempt to find any usages of function <function-name> in the distribution, and update them to use version <new-version-nr>.\n');
process.exit(1);
}
}
function warn(msg) {
console.log(`::warning::${msg}`);
}
function fail(error) {
if (IS_GITHUB_ACTION) {
console.log(`::error::ERROR: ${error}\n`);
} else {
console.error(`ERROR: ${error}\n`);
}
process.exit(1);
}
function strip(val) {
//Strip leadig or trailing whitespace
return (val || '').replace(/^\s*|\s*$/g, '');
}
//Process input params
awsApiRequest.accessKey = env.INPUT_AWS_ACCESS_KEY_ID || env.AWS_ACCESS_KEY_ID;
awsApiRequest.secretKey = env.INPUT_AWS_SECRET_ACCESS_KEY || env.AWS_SECRET_ACCESS_KEY;
var funcName = strip(env.INPUT_FUNCTION_NAME),
newFuncVersion = strip(env.INPUT_NEW_VERSION_NR),
distributionId = strip(env.INPUT_CLOUDFRONT_DISTRIBUTION_ID);
if (!funcName.match(/^[a-zA-Z][a-zA-Z0-9_-]+$/)) {
fail(`Invalid function name "${funcName}". Should only be a simple name, not a full ARN, or anything with special characters.`);
} else if (!newFuncVersion.match(/^\d+$/)) {
fail(`Invalid version nr: "${newFuncVersion}". Must be a normal positive number`);
} else if (!distributionId.match(/^\w+$/)) {
fail(`Invalid distribution id: "${distributionId}"`);
}
const isDryRun = !!strip(process.env.INPUT_DRY_RUN).match(/true|1/i);
if (isDryRun) {
warn('***** THIS IS A DRY RUN. THE DISTRIBUTION WILL NOT BE UPDATED, WE WILL ONLY SHOW HOW THE CONFIG WOULD BE CHANGED.');
}
let prevArnWithoutVersion;
function getDistributionConfig(distributionId) {
return awsApiRequest({
service: 'cloudfront',
path: `/2020-05-31/distribution/${distributionId}/config`,
region: 'us-east-1',
host: 'cloudfront.amazonaws.com'
});
}
function updateDistribution(distributionId, distributionConfig, etag) {
return awsApiRequest({
service: 'cloudfront',
path: `/2020-05-31/distribution/${distributionId}/config`,
region: 'us-east-1',
host: 'cloudfront.amazonaws.com',
method: 'PUT',
payload: JSON.stringify(distributionConfig),
headers: { 'Content-Type': 'application/json', 'If-Match' : etag }
});
}
function validateResult(result) {
if (result.statusCode !== 200) {
let extraMsg = result.data && result.data.Message || '';
fail(`Request returned with unexpected statusCode: ${result.statusCode}. ${extraMsg}\n\nFull Response:\n\n ${JSON.stringify(result, null, 2)}`);
}
}
getDistributionConfig(distributionId).then(result => {
validateResult(result);
const etag = result.headers.etag;
let distributionConfig = result.data;
console.log('Got distribution, etag: ' + etag);
let cacheBehaviours = [distributionConfig.DefaultCacheBehavior];
if (distributionConfig.CacheBehaviors && distributionConfig.CacheBehaviors.Items) {
cacheBehaviours.push(...distributionConfig.CacheBehaviors.Items);
}
let possibleCName = '';
if (distributionConfig.Aliases && distributionConfig.Aliases.Items) {
possibleCName = ` (${distributionConfig.Aliases.Items[0]})`;
}
console.log(`Got configuration for distribution ${distributionId}${possibleCName}, ETag is ${etag}`);
let countUpdated = 0;
console.log(`Distribution has ${cacheBehaviours.length} cache behaviours`);
let rx = new RegExp(`:function:${funcName}:\\d+$`);
for (let cb of cacheBehaviours) {
console.log('IS: ' + JSON.stringify(cb.LambdaFunctionAssociations))
if (cb.LambdaFunctionAssociations.Items !== null) {
for (let func of cb.LambdaFunctionAssociations.Items) {
if (func.LambdaFunctionARN.match(rx)) {
console.log(`Found ARN matching function name ${funcName}: ${func.LambdaFunctionARN}`);
let arnWithoutVersion = func.LambdaFunctionARN.replace(/:\d+$/, '');
if (prevArnWithoutVersion && prevArnWithoutVersion !== arnWithoutVersion) {
fail(`ERROR: Two possible matches for func name "${funcName}". Both "${prevArnWithoutVersion}" and "${arnWithoutVersion}" match, aborting!`)
}
prevFuncWithoutArn = arnWithoutVersion;
//Replace the version nr at the end
let fullNewFunctionArn = arnWithoutVersion + ':' + newFuncVersion;
console.log(`Updating function arn on path ${cb.PathPattern} from \n\n ${func.LambdaFunctionARN} \n\nto \n\n ${fullNewFunctionArn}\n`);
func.LambdaFunctionARN = fullNewFunctionArn;
countUpdated++;
}
}
}
}
if (countUpdated === 0) {
fail(`Found no Lambda@Edge function matching the name "${funcName}" in distribution ${distributionId}`);
}
if (isDryRun) {
console.log('The updated distribution configuration that we would send to Cloudfront in a real run looks like this:\n');
console.log(JSON.stringify(distributionConfig, null, 2));
console.log('\n\n');
warn('***** DRY RUN FINISHED. RUN THE ACTION AGAIN WITHOUT SETTING dry_run=true TO ACTUALLY UPDATE YOUR DISTRIBUTION.\n');
process.exit(0);
}
console.log('About to update distribution...\n');
return updateDistribution(distributionId, distributionConfig, etag);
}).then(result => {
validateResult(result);
console.log('Result of distribution update:');
console.log(JSON.stringify(result.data, null, 2));
console.log('\n\nUpdate was successful. It may take a few minutes for the changes to the distribution to be fully deployed.\n');
}).catch(err => {
fail(err);
});