Last active
December 20, 2022 09:23
-
-
Save mnanchev/0c41dae3fc7ca2456f087de3dfdc0173 to your computer and use it in GitHub Desktop.
AWS cloudwatch canaries
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { URL } = require('url'); | |
const synthetics = require('Synthetics'); | |
const log = require('SyntheticsLogger'); | |
const syntheticsConfiguration = synthetics.getConfiguration(); | |
const syntheticsLogHelper = require('SyntheticsLogHelper'); | |
const AWS = require('aws-sdk'); | |
AWS.config.update({ | |
region: 'eu-central-1', | |
}); | |
//Username and password should be passed by using Secrets Manager get secret value api call. | |
//Don't use this approach in production | |
const USERNAME = 'martin.nanchev@example.com'; | |
const PASSWORD = 'probkoTesto123!'; | |
// Performs the get request for each url | |
// The request is signed/authenticated using the bearer token | |
const loadBlueprint = async function() { | |
const response = login(USERNAME, PASSWORD); | |
const bearerToken = (await response).AuthenticationResult.IdToken; | |
const urls = ['https://probkotestov.io/api-users/setting']; | |
// Set screenshot option | |
const takeScreenshot = true; | |
/* Disabling default step screen shots taken during Synthetics.executeStep() calls | |
* Step will be used to publish metrics on time taken to load dom content but | |
* Screenshots will be taken outside the executeStep to allow for page to completely load with domcontentloaded | |
* You can change it to load, networkidle0, networkidle2 depending on what works best for you. | |
*/ | |
syntheticsConfiguration.disableStepScreenshots(); | |
syntheticsConfiguration.setConfig({ | |
continueOnStepFailure: true, | |
includeRequestHeaders: true, // Enable if headers should be displayed in HAR | |
includeResponseHeaders: true, // Enable if headers should be displayed in HAR | |
restrictedHeaders: [], // Value of these headers will be redacted from logs and reports | |
restrictedUrlParameters: [], // Values of these url parameters will be redacted from logs and reports | |
}); | |
// Start chrome headless browser tab | |
// Set of the required headers | |
let page = await synthetics.getPage(); | |
page.setExtraHTTPHeaders({ | |
'accept': 'application/json', | |
'Authorization': 'Bearer ' + bearerToken, | |
}); | |
// Load each url | |
for (const url of urls) { | |
await loadUrl(page, url, takeScreenshot); | |
} | |
}; | |
// Reset the page in-between | |
const resetPage = async function(page) { | |
try { | |
await page.goto('about:blank', { waitUntil: ['load', 'networkidle0'], timeout: 30000 }); | |
} catch (ex) { | |
synthetics.addExecutionError('Unable to open a blank page ', ex); | |
} | |
}; | |
// For each url it waits until domcontent is returned and takes screenshot | |
// If it fails you will get the error | |
const loadUrl = async function(page, url, takeScreenshot) { | |
let stepName = null; | |
let domcontentloaded = false; | |
try { | |
stepName = new URL(url).hostname; | |
} catch (error) { | |
const errorString = `Error parsing url: ${url}. ${error}`; | |
log.error(errorString); | |
/* If we fail to parse the URL, don't emit a metric with a stepName based on it. | |
It may not be a legal CloudWatch metric dimension name and we may not have an alarms | |
setup on the malformed URL stepName. Instead, fail this step which will | |
show up in the logs and will fail the overall canary and alarm on the overall canary | |
success rate. | |
*/ | |
throw error; | |
} | |
await synthetics.executeStep(stepName, async function() { | |
const sanitizedUrl = syntheticsLogHelper.getSanitizedUrl(url); | |
/* You can customize the wait condition here. For instance, using 'networkidle2' or 'networkidle0' to load page completely. | |
networkidle0: Navigation is successful when the page has had no network requests for half a second. This might never happen if page is constantly loading multiple resources. | |
networkidle2: Navigation is successful when the page has no more then 2 network requests for half a second. | |
domcontentloaded: It's fired as soon as the page DOM has been loaded, without waiting for resources to finish loading. Can be used and then add explicit await page.waitFor(timeInMs) | |
*/ | |
const response = await page.goto(url, { waitUntil: ['domcontentloaded'], timeout: 30000 }); | |
if (response) { | |
domcontentloaded = true; | |
const status = response.status(); | |
const statusText = response.statusText(); | |
logResponseString = `Response from url: ${sanitizedUrl} Status: ${status} Status Text: ${statusText}`; | |
//If the response status code is not a 2xx success code | |
if (response.status() < 200 || response.status() > 299) { | |
throw `Failed to load url: ${sanitizedUrl} ${response.status()} ${response.statusText()}`; | |
} | |
} else { | |
const logNoResponseString = `No response returned for url: ${sanitizedUrl}`; | |
log.error(logNoResponseString); | |
throw new Error(logNoResponseString); | |
} | |
}); | |
// Wait for 15 seconds to let page load fully before taking screenshot. | |
if (domcontentloaded && takeScreenshot) { | |
await page.waitFor(15000); | |
await synthetics.takeScreenshot(stepName, 'loaded'); | |
await resetPage(page); | |
} | |
}; | |
exports.handler = async () => { | |
return await loadBlueprint(); | |
}; | |
// Get Jwt token | |
async function login(email, password) { | |
try { | |
const cognito = new AWS.CognitoIdentityServiceProvider(); | |
return await cognito.initiateAuth({ | |
AuthFlow: 'USER_PASSWORD_AUTH', | |
ClientId: 'XXXXXXXXXXXXXXXXXX', | |
AuthParameters: { | |
USERNAME: email, | |
PASSWORD: password, | |
}, | |
}).promise(); | |
} catch (err) { | |
throw err; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment