Atlas Cluster Automation Using Scheduled Triggers
Architecture
Three example scheduled triggers are provided in this solution. Each trigger has an associated trigger function. The bulk of the work is handled by the modifyCluster function, which as the name implies is a generic function for making modifications to a cluster.
Preparation
Identify Trigger Host Cluster
Atlas triggers do not fire when their associated cluster is paused, so the trigger needs to be linked to a cluster that never pauses. Fortunately, Atlas supports a forever free sandbox cluster (M0). If you don’t have a cluster you can use that can never be paused (such as a production cluster), create an M0 cluster to use for this exercise. Please note that you can only have one M0 cluster per project.
Note the Project ID
We’ll need to pass the project ID as a path parameter in our API calls. Click the 3 dots in the upper left corner of the UI to open the Project Settings.
Under which you’ll find your Project ID:
Generate an API Key
Even if you already have an API Key, it’s good to generate a new one for this use case so we can easily identify that the API was called by the Atlas Trigger.
At the Organization level (not the Project level), select Access Manager from the menu on the left.
Then select the API Keys tab:
Click Next and make a note of your Private Key:
Let's limit who can use our API key by adding an access list. In our case, the API key is going to be used by a Trigger which is a component of Atlas App Services. You will find the list of IP addresses used by App Services in the documentation under
Firewall Configuration. Note, each IP address must be added individually.
18.211.240.224
18.210.66.32
18.213.24.164
54.69.74.169
54.203.157.107
18.202.2.23
54.76.145.131
52.63.26.53
13.236.189.10
Create Trigger:
Scheduled Pause
The ability to pause and resume a cluster is supported by the Modify Cluster API. To begin, select Triggers from the menu on the left:
Set the Trigger Type to Scheduled and the name to pauseTrigger
As for the schedule, you have the full power of CRON Expressions at your fingertips. For this Setup, let’s assume we want to pause the cluster every evening at 9pm. Select Advanced and set the CRON schedule to 30 15 * . Note, the time is in GMT, so adjust accordingly for your timezone.
Check the Next Events window to validate the job will run when you desire.
The next step is to link the trigger to the cluster, and it’s important to link to the cluster we identified above that will always be running. I created a M0 cluster called Automation for this purpose
Once the cluster is linked for the first trigger, the cluster will not need to be relinked for additional triggers created in the same project.
Finally, we need to provide the function that will run when the trigger fires. Replace the provided function code with the following, filling in the value for your projectID and clusterNames. Note the function as written can pause multiple clusters, providing the cluster names as an array
################################################################
/*
* Iterates over the provided projects and clusters, pausing those clusters
*/
exports = async function() {
// Supply projectIDs and clusterNames...
const projectIDs = [{id:'64b7a8ea2182c6121c839973', names:['Cluster0']}, {id:'64a6971832ff6b58735877c1', names:['AtlasCluster']}];
// Get stored credentials...
const username = context.values.get("AtlasPublicKey");
const password = context.values.get("AtlasPrivateKey");
// Set desired state...
const body = {paused: true};
var result = "";
projectIDs.forEach(async function (project) {
project.names.forEach(async function (cluster) {
result = await context.functions.execute('modifyCluster', username, password,
project.id
, cluster, body);
console.log("Cluster " + cluster + ": " + EJSON.stringify(result));
});
});
return "Clusters Paused";
};
############################################################################
And Save the trigger.
If you attempt to run the trigger, it will fail because it’s looking up values and calling a wrapper function, modifyCluster, we haven’t written yet. Let’s implement them now…
Supply Atlas Credentials and Implement modifyCluster
When we created the Atlas Trigger, behind the scenes, a Realm application was created to run the trigger. It’s in that application where we’ll host our Atlas credentials and modifyCluster function.
Select Realm from the menu on the top:
And you’ll find a Triggers RealmApp has been created for us:
Click the Triggers RealmApp to open the application.
Atlas Credentials
Our Atlas Trigger references two values that represent our Atlas credentials, the AtlasPublicKey and AtlasPrivateKey. Here’s where we’ll supply those values to the trigger.
Select Values under Build from the menu on the left:
Click the Create New Value button.
Set the Value Name to AtlasPublicKey and enter your public key value you generated earlier, enclosed in quotes:
Create a Secret containing your private key (the secret is not in quotes):
The Secret cannot be accessed directly, so create a second Value that links to the secret:
modifyCluster Function
Create a New Function named modifyCluster.
Set the function to Private as it will only be called by our trigger. The other default settings are fine:
Switch to the Function Editor tab and paste the following code:
###########################################################################
/*
* Modifies the cluster as defined by the body parameter.
* See
https://docs.atlas.mongodb.com/reference/api/clusters-modify-one/
*
*/
exports = async function(username, password, projectID, clusterName, body) {
// Easy testing from the console
if (username == "Hello world!") {
username = await context.values.get("AtlasPublicKey");
password = await context.values.get("AtlasPrivateKey");
projectID = "5c5db514c56c983b7e4a8701";
clusterName = "Demo";
body = {paused: false}
}
const arg = {
scheme: 'https',
host: '
cloud.mongodb.com
',
path: 'api/atlas/v1.0/groups/' + projectID + '/clusters/' + clusterName,
username: username,
password: password,
headers: {'Content-Type': ['application/json'], 'Accept-Encoding': ['bzip, deflate']},
digestAuth:true,
body: JSON.stringify(body)
};
// The response body is a BSON.Binary object. Parse it and return.
response = await context.http.patch(arg);
return EJSON.parse(response.body.text());
};
############################################################################
As stated above, this is simply a generic wrapper around the modify cluster API. Save the function then click REVIEW & DEPLOY CHANGES:
Resume the Cluster
You could opt to manually resume the cluster(s) as it’s needed. But for completeness, let’s assume we want the cluster(s) to automatically resume at 8am US East every weekday morning.
Add a new Atlas scheduled trigger named resumeClusters. Set the CRON schedule to: 30 3 * . The Next Events validates for us this is exactly what we want:
The function code is almost identical to pauseCluster. We simply set paused to false and update our return statement to indicate clusters were resumed:
############################################################################
/*
* Iterates over the provided projects and clusters, pausing those clusters
*/
exports = async function() {
// Supply projectIDs and clusterNames...
const projectIDs = [{id:'64b7a8ea2182c6121c839973', names:['Cluster0']}, {id:'64a6971832ff6b58735877c1', names:['AtlasCluster']}];
// Get stored credentials...
const username = context.values.get("AtlasPublicKey");
const password = context.values.get("AtlasPrivateKey");
// Set desired state...
const body = {paused: false};
var result = "";
projectIDs.forEach(async function (project) {
project.names.forEach(async function (cluster) {
result = await context.functions.execute('modifyCluster', username, password,
project.id
, cluster, body);
console.log("Cluster " + cluster + ": " + EJSON.stringify(result));
});
});
return " clusters resumed";
};
#########################################################################
Subscribe to my newsletter
Read articles from Vipul Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by