-
Notifications
You must be signed in to change notification settings - Fork 442
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Website: Add admin page to manage the Fleet Sandbox waitlist (#13111)
Closes: #12954 Changes: - Added an admin page that displays a table containing all of the users that are currently on the Fleet Sandbox waitlist where admins can approve waitlisted users. - Added a new email template that tells users that their Fleet Sandbox instance is ready. - Added a new action: `admin/provision-sandbox-instance-and-deliver-email.js`, an action that provisions a Fleet sandbox instance for a single user and sends them an email telling them that their Fleet Sandbox Instance is ready. - Added a script that provisions a Fleet Sandbox instance for the user who has been on the waitlist the longest and sends them an email telling them that their Sandbox instance is ready.
- Loading branch information
Showing
20 changed files
with
480 additions
and
69 deletions.
There are no files selected for viewing
66 changes: 66 additions & 0 deletions
66
website/api/controllers/admin/provision-sandbox-instance-and-deliver-email.js
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
module.exports = { | ||
|
||
|
||
friendlyName: 'Provision sandbox instance and deliver email', | ||
|
||
|
||
description: 'Provisions a Fleet sandbox for a user and delivers an email to a user letting them know their Fleet Sandbox instance is ready.', | ||
|
||
|
||
inputs: { | ||
userId: { | ||
type: 'number', | ||
description: 'The database ID of the user who is currently on the Fleet Sandbox waitlist', | ||
required: true | ||
} | ||
}, | ||
|
||
|
||
exits: { | ||
success: { | ||
description: 'A user was successfully removed from the Fleet Sandbox waitlist.' | ||
}, | ||
}, | ||
|
||
|
||
fn: async function ({userId}) { | ||
|
||
let userToRemoveFromSandboxWaitlist = await User.findOne({id: userId}); | ||
|
||
if(!userToRemoveFromSandboxWaitlist.inSandboxWaitlist) { | ||
throw new Error(`When attempting to provision a Fleet Sandbox instance for a user (id:${userId}) who is on the waitlist, the user record associated with the provided ID has already been removed from the waitlist.`); | ||
} | ||
|
||
let sandboxInstanceDetails = await sails.helpers.fleetSandboxCloudProvisioner.provisionNewFleetSandboxInstance.with({ | ||
firstName: userToRemoveFromSandboxWaitlist.firstName, | ||
lastName: userToRemoveFromSandboxWaitlist.lastName, | ||
emailAddress: userToRemoveFromSandboxWaitlist.emailAddress, | ||
}) | ||
.intercept((err)=>{ | ||
return new Error(`When attempting to provision a new Fleet Sandbox instance for a User (id:${userToRemoveFromSandboxWaitlist.id}), an error occured. Full error: ${err}`); | ||
}); | ||
|
||
await User.updateOne({id: userId}).set({ | ||
fleetSandboxURL: sandboxInstanceDetails.fleetSandboxURL, | ||
fleetSandboxExpiresAt: sandboxInstanceDetails.fleetSandboxExpiresAt, | ||
fleetSandboxDemoKey: sandboxInstanceDetails.fleetSandboxDemoKey, | ||
inSandboxWaitlist: false, | ||
}); | ||
|
||
// Send the user an email to let them know that their Fleet sandbox instance is ready. | ||
await sails.helpers.sendTemplateEmail.with({ | ||
to: userToRemoveFromSandboxWaitlist.emailAddress, | ||
from: sails.config.custom.fromEmailAddress, | ||
fromName: sails.config.custom.fromName, | ||
subject: 'Your Fleet Sandbox instance is ready!', | ||
template: 'email-sandbox-ready-approved', | ||
templateData: {}, | ||
}); | ||
|
||
// All done. | ||
return; | ||
|
||
} | ||
|
||
|
||
}; |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
module.exports = { | ||
|
||
|
||
friendlyName: 'View sandbox waitlist', | ||
|
||
|
||
description: 'Display "Sandbox waitlist" page.', | ||
|
||
|
||
exits: { | ||
|
||
success: { | ||
viewTemplatePath: 'pages/admin/sandbox-waitlist' | ||
} | ||
|
||
}, | ||
|
||
|
||
fn: async function () { | ||
|
||
let usersCurrentlyOnWaitlist = await User.find({inSandboxWaitlist: true}) | ||
.sort('createdAt ASC'); | ||
|
||
return { | ||
usersWaitingForSandboxInstance: usersCurrentlyOnWaitlist | ||
}; | ||
|
||
} | ||
|
||
|
||
}; |
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
117 changes: 117 additions & 0 deletions
117
website/api/helpers/fleet-sandbox-cloud-provisioner/provision-new-fleet-sandbox-instance.js
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
module.exports = { | ||
|
||
|
||
friendlyName: 'Provision new Fleet Sandbox instance', | ||
|
||
|
||
description: 'Provisions a new Fleet Sandbox instance and returns the details of the Sandbox instance.', | ||
|
||
|
||
inputs: { | ||
|
||
firstName: { | ||
type: 'string', | ||
required: true, | ||
description: 'The first name of the user who is having a Fleet Sandbox instance provisioned for them.', | ||
extendedDescription: 'This will be used in the Fleet instance' | ||
}, | ||
lastName: { | ||
type: 'string', | ||
required: true, | ||
description: 'The last name of the user who is having a Fleet Sandbox instance provisioned for them.', | ||
extendedDescription: 'This will be used in the Fleet instance' | ||
}, | ||
emailAddress: { | ||
type: 'string', | ||
required: true, | ||
description: 'The email address of the User record that is having a Fleet sandbox instance provisioned for them.', | ||
extendedDescription: 'This will be used in the Fleet instance' | ||
}, | ||
|
||
}, | ||
|
||
|
||
exits: { | ||
|
||
success: { | ||
description: 'All done.', | ||
outputFriendlyName: 'Sandbox instance details', | ||
outputType: { | ||
fleetSandboxDemoKey: 'string', | ||
fleetSandboxExpiresAt: 'number', | ||
fleetSandboxURL: 'string', | ||
}, | ||
}, | ||
|
||
requestToProvisionerTimedOut: { | ||
description: 'The request to the Fleet Sandbox provisioner exceeded the set timeout.', | ||
}, | ||
}, | ||
|
||
|
||
fn: async function ({firstName, lastName, emailAddress}) { | ||
|
||
const FIVE_DAYS_IN_MS = (5*24*60*60*1000); | ||
// Creating an expiration JS timestamp for the Fleet sandbox instance. NOTE: We send this value to the cloud provisioner API as an ISO 8601 string. | ||
let fleetSandboxExpiresAt = Date.now() + FIVE_DAYS_IN_MS; | ||
|
||
// Creating a fleetSandboxDemoKey, this will be used for the user's password when we log them into their Sandbox instance. | ||
let fleetSandboxDemoKey = await sails.helpers.strings.uuid(); | ||
|
||
// Send a POST request to the cloud provisioner API | ||
let cloudProvisionerResponseData = await sails.helpers.http.post.with({ | ||
url: 'https://sandbox.fleetdm.com/new', | ||
data: { | ||
'name': firstName + ' ' + lastName, | ||
'email': emailAddress, | ||
'password': fleetSandboxDemoKey, //« this provisioner API was originally designed to accept passwords, but rather than specifying the real plaintext password, since users always access Fleet Sandbox from their fleetdm.com account anyway, this generated demo key is used instead to avoid any confusion | ||
'sandbox_expiration': new Date(fleetSandboxExpiresAt).toISOString(), // sending expiration_timestamp as an ISO string. | ||
}, | ||
headers: { | ||
'Authorization':sails.config.custom.cloudProvisionerSecret | ||
} | ||
}) | ||
.timeout(10000) | ||
.intercept(['requestFailed', 'non200Response'], (err)=>{ | ||
// If we received a non-200 response from the cloud provisioner API, we'll throw a 500 error. | ||
return new Error('When attempting to provision a Sandbox instance for a user on the Fleet Sandbox waitlist ('+emailAddress+'), the cloud provisioner gave a non 200 response. Raw response received from provisioner: '+err.stack); | ||
}) | ||
.intercept({name: 'TimeoutError'},()=>{ | ||
// If the request timed out, log a warning and return a 'requestToSandboxTimedOut' response. | ||
return 'requestToProvisionerTimedOut'; | ||
}); | ||
|
||
if(!cloudProvisionerResponseData.URL) { | ||
// If we didn't receive a URL in the response from the cloud provisioner API, we'll throw an error before we save the new user record and the user will need to try to sign up again. | ||
throw new Error( | ||
`The response data from the cloud provisioner API was malformed. It did not contain a valid Fleet Sandbox instance URL in its expected "URL" property. | ||
Here is the malformed response data (parsed response body) from the cloud provisioner API: ${cloudProvisionerResponseData}` | ||
); | ||
} | ||
|
||
// Start polling the /healthz endpoint of the created Fleet Sandbox instance, once it returns a 200 response, we'll continue. | ||
await sails.helpers.flow.until( async()=>{ | ||
let healthCheckResponse = await sails.helpers.http.sendHttpRequest('GET', cloudProvisionerResponseData.URL+'/healthz') | ||
.timeout(5000) | ||
.tolerate('non200Response') | ||
.tolerate('requestFailed') | ||
.tolerate({name: 'TimeoutError'}); | ||
if(healthCheckResponse) { | ||
return true; | ||
} | ||
}, 10000)//∞ | ||
.intercept('tookTooLong', ()=>{ | ||
return new Error('This newly provisioned Fleet Sandbox instance (for '+emailAddress+') is taking too long to respond with a 2xx status code, even after repeatedly polling the health check endpoint. Note that failed requests and non-2xx responses from the health check endpoint were ignored during polling. Search for a bit of non-dynamic text from this error message in the fleetdm.com source code for more info on exactly how this polling works.'); | ||
}); | ||
|
||
return { | ||
fleetSandboxDemoKey, | ||
fleetSandboxExpiresAt, | ||
fleetSandboxURL: cloudProvisionerResponseData.URL, | ||
}; | ||
|
||
} | ||
|
||
|
||
}; | ||
|
Oops, something went wrong.