Skip to content

Commit b0b222d

Browse files
committed
feat: CDN release
1 parent 191ef24 commit b0b222d

File tree

6 files changed

+200
-3
lines changed

6 files changed

+200
-3
lines changed

.env.example

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1-
BROWSERSTACK_USER=whoami
21
BROWSERSTACK_KEY=xxx
2+
BROWSERSTACK_USER=whoami
3+
CDN_DISTRIBUTION_ID=xxx
4+
CDN_BUCKET_NAME=xxx
5+
GITHUB_TOKEN=xxx

integration-tests/devServer/templateProvider.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616

1717
const path = require('path');
1818
const fs = require('fs');
19+
const packageConfig = require('../../package.json');
1920

2021
const INJECT_TEMPLATE = `<script src="<%= file -%>"></script>
2122
<script>
@@ -38,7 +39,7 @@ exports.registerTemplateProvider = ({app, addHeaders, enableHttps, render}) => {
3839

3940
addHeaders(res);
4041
return res.render(filepath, {
41-
renderAgent(userOpts = {}, noInit = false, file = '/dist/splunk-rum.js') {
42+
renderAgent(userOpts = {}, noInit = false, file = '/dist/splunk-rum.js', cdn = false) {
4243
const options = {
4344
beaconUrl: beaconUrl.toString(),
4445
app: 'splunk-otel-js-dummy-app',
@@ -47,6 +48,10 @@ exports.registerTemplateProvider = ({app, addHeaders, enableHttps, render}) => {
4748
...userOpts
4849
};
4950

51+
if (cdn) {
52+
options.file = `https://cdn.signalfx.com/o11y-gdi-rum/v${packageConfig.version}/splunk-rum.js`;
53+
}
54+
5055
return render(INJECT_TEMPLATE, {
5156
file,
5257
noInit,

integration-tests/tests/cdn/index.ejs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>CDN test with an unhandled error</title>
6+
7+
<%- renderAgent({ cdn: true }) %>
8+
<script id="scenario">
9+
var test = null;
10+
setTimeout(() => {
11+
test.prop1 = true;
12+
});
13+
</script>
14+
</head>
15+
<body>
16+
<h1>CDN test with an unhandled error</h1>
17+
<pre id="scenarioDisplay"></pre>
18+
<script>scenarioDisplay.innerHTML = scenario.innerHTML;</script>
19+
</body>
20+
</html>
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
Copyright 2020 Splunk Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
module.exports = {
18+
'JS unhandled error': async function(browser) {
19+
browser.globals.clearReceivedSpans();
20+
await browser.url(browser.globals.getUrl('/cdn/index.ejs'));
21+
22+
const errorSpan = await browser.globals.findSpan(s => s.name === 'onerror');
23+
await browser.assert.ok(!!errorSpan, 'Checking presence of error span.');
24+
25+
const tags = errorSpan.tags;
26+
await browser.assert.strictEqual(tags['component'], 'error');
27+
await browser.assert.strictEqual(tags['error'], 'true');
28+
await browser.assert.strictEqual(tags['error.object'], 'TypeError');
29+
30+
switch (browser.options.desiredCapabilities.browserName.toLowerCase()) {
31+
case 'chrome':
32+
await browser.assert.strictEqual(tags['error.message'], 'Cannot set property \'prop1\' of null');
33+
break;
34+
case 'firefox':
35+
await browser.assert.strictEqual(tags['error.message'], 'test is null');
36+
break;
37+
case 'safari':
38+
await browser.assert.strictEqual(tags['error.message'], 'null is not an object (evaluating \'test.prop1 = true\')');
39+
break;
40+
}
41+
42+
await browser.end();
43+
},
44+
};

package.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
"build:debug": "DEBUG_BUILD=1 npx rollup -c",
2929
"build:prod": "npx rollup -c",
3030
"build:development": "npm-run-all -s build:debug build:prod",
31-
"build:release": "npm-run-all -s lint test build:debug build:prod"
31+
"build:release": "npm-run-all -s lint test build:debug build:prod",
32+
"release:cdn": "node scripts/release-cdn.mjs"
3233
},
3334
"author": "",
3435
"license": "Apache-2.0",
@@ -37,7 +38,11 @@
3738
"web-vitals": "^1.1.0"
3839
},
3940
"devDependencies": {
41+
"@aws-sdk/client-cloudfront": "^3.6.0",
42+
"@aws-sdk/client-s3": "^3.6.0",
4043
"@babel/preset-env": "^7.12.11",
44+
"@octokit/graphql": "^4.6.0",
45+
"@octokit/request": "^5.4.14",
4146
"@rollup/plugin-alias": "^3.1.1",
4247
"@rollup/plugin-babel": "^5.2.2",
4348
"@rollup/plugin-commonjs": "^17.0.0",

scripts/release-cdn.mjs

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { default as dotenv } from 'dotenv';
2+
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
3+
import { CloudFrontClient, CreateInvalidationCommand } from "@aws-sdk/client-cloudfront";
4+
import fetch from 'node-fetch';
5+
import fs from 'fs';
6+
import { join, dirname } from 'path';
7+
import { fileURLToPath } from 'url';
8+
import { request } from '@octokit/request';
9+
import readline from 'readline';
10+
11+
const OWNER = 'signalfx';
12+
const REPO = 'splunk-otel-js-browser';
13+
14+
const __dirname = dirname(fileURLToPath(import.meta.url));
15+
const packageConfig = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString());
16+
dotenv.config();
17+
18+
if (!process.env.CDN_DISTRIBUTION_ID) {
19+
throw new Error('You are missing an environment variable CDN_DISTRIBUTION_ID.');
20+
}
21+
const { CDN_DISTRIBUTION_ID } = process.env;
22+
23+
if (!process.env.CDN_BUCKET_NAME) {
24+
throw new Error('You are missing an environment variable CDN_BUCKET_NAME.');
25+
}
26+
const { CDN_BUCKET_NAME } = process.env;
27+
28+
const requestWithAuth = request.defaults({
29+
headers: {
30+
authorization: `token ${process.env.GITHUB_TOKEN}`,
31+
},
32+
});
33+
34+
const { data: releases, status } = await requestWithAuth(`GET /repos/${OWNER}/${REPO}/releases`);
35+
if (status >= 400) {
36+
throw new Error('There was an error while trying to fetch the list of releases.');
37+
}
38+
39+
const latestRelease = releases[0];
40+
if (!latestRelease) {
41+
throw new Error('Latest release not found.');
42+
}
43+
console.log(`I have found the latest version to be: ${latestRelease.tag_name}.`);
44+
45+
if (!latestRelease.draft) {
46+
console.warn('This release is already published and may have been uploaded to CDN already.');
47+
}
48+
49+
const rl = readline.createInterface({input: process.stdin, output: process.stdout});
50+
const question = (text) => new Promise(resolve => rl.question(text, (answer) => resolve(answer)));
51+
const confirmation = await question('Please retype the version to confirm uploading to CDN: ');
52+
rl.close();
53+
54+
if (confirmation !== latestRelease.tag_name) {
55+
throw new Error('You need to confirm the version before proceeding.');
56+
}
57+
58+
console.log(`Uploading release "${latestRelease.name}" to CDN using your AWS credentials.`);
59+
60+
const { data: assets } = await requestWithAuth(`GET /repos/${OWNER}/${REPO}/releases/${latestRelease.id}/assets`);
61+
console.log(`Release has the following assets: ${assets.map(asset => asset.name).join(", ")}.`);
62+
63+
const s3Client = new S3Client({region: 'us-east-1'});
64+
const cfClient = new CloudFrontClient({region: 'us-east-1'});
65+
const cdnLinks = ['\n## CDN'];
66+
for (const asset of assets) {
67+
console.log(`Fetching ${asset.name} from ${asset.browser_download_url}.`);
68+
const response = await fetch(asset.browser_download_url);
69+
const assetBuffer = await response.buffer();
70+
71+
const versionParts = packageConfig.version.split('.');
72+
let isFinalVersion = true;
73+
while (versionParts.length) {
74+
const version = `v${versionParts.join('.')}`;
75+
const key = `o11y-gdi-rum/${version}/${asset.name}`;
76+
await s3Client.send(new PutObjectCommand({
77+
Body: assetBuffer,
78+
Bucket: CDN_BUCKET_NAME,
79+
Key: `cdn/${key}`,
80+
ACL: 'public-read',
81+
ContentType: asset.content_type,
82+
CacheControl: 'max-age=3600',
83+
}));
84+
const publicUrl = `https://cdn.signalfx.com/${key}`;
85+
console.log(`Uploaded ${asset.name} as ${publicUrl}`);
86+
87+
if (asset.name == 'splunk-rum.js') {
88+
cdnLinks.push(
89+
`### Version ${version}
90+
${(isFinalVersion ? '' : '**WARNING: Content behind this URL might be updated when we release a new version.**')}
91+
\`\`\`html
92+
<script src="${publicUrl}" crossorigin="anonymous"></script>
93+
\`\`\`
94+
`
95+
);
96+
}
97+
98+
versionParts.pop(); // 1.2.3 -> 1.2
99+
isFinalVersion = false;
100+
}
101+
}
102+
103+
console.log('Creating an invalidation to refresh shared versions.');
104+
const invalidationRef = `o11y-gdi-rum-${new Date().toISOString()}`;
105+
const { Invalidation } = await cfClient.send(new CreateInvalidationCommand({
106+
DistributionId: CDN_DISTRIBUTION_ID,
107+
InvalidationBatch: {
108+
CallerReference: invalidationRef,
109+
Paths: {
110+
Items: ['/cdn/o11y-gdi-rum/*'],
111+
Quantity: 1,
112+
},
113+
},
114+
}));
115+
console.log(`Invalidation ${Invalidation.Id} sent. Typically it takes about 5 minutes to execute.`);
116+
117+
console.log('Appending CDN instructions to release description.');
118+
await requestWithAuth(`PATCH /repos/${OWNER}/${REPO}/releases/${latestRelease.id}`, {
119+
body: latestRelease.body + cdnLinks.join('\n'),
120+
})

0 commit comments

Comments
 (0)