Cloud Manager Slack Notifications with Project Firefly
Automating notifications is a common DevOps task. In this post, I'll explain how to send Adobe Cloud Manager pipeline status notifications to Slack using a Project Firefly headless application.
In the Getting Started with Project Firefly post, we created a Firefly project and used the Developer Console to add the Cloud Manager API, as well as a credential (JWT) to our project.
Slack API
To begin this integration, follow the instructions on the Slack website to create an incoming webhook.
After your Slack App is created, make a note of the webhook URL, we will use it when coding the application.
Coding the Application
Open the AIO CLI documentation and Cloud Manager API documentation in a browser tab for quick reference. Using the terminal, navigate to your code project and run the following command:
aio app:add:action
The action creation wizard will walk you through generating a new action. Select, Generic
as the action type and give the action an appropriate name (e.g. notify-slack
). Agree to overwrite the package.json and manifest.yml files when prompted.
Open the code project within an editor and open the .env file. Notice the environment variables reflect the values that appear within the Developer Console. Add the following environment variables to the .env file.
- PRIVATE_KEY=<YOUR-PRIVATE-KEY>
These environment variables will be made available to the application in the next step when we update the manifest.yml file.
Note: It is not recommended to store your private key in an environment variable, for the sake of brevity I will in this tutorial, but in your production application you should read the private key from a file. Learn how to create Zipped Action Folder Resources in Project Firefly and Adobe I/O Runtime.
Open the manifest.yml file and locate the entry for the notify-slack
action. Items within the Inputs block will be made available to our application code within the params
object. Environment variables (.env file) can be used within the manifest.yml file through the use of the $ symbol. Map the environment variables to the input properties as shown below.
From the terminal, run the following command to install the jsrasign dependency.
npm install jsrsasign --save
Open the code project within an editor and create a new file within the actions > notify-slack folder named token.js. The token.js module will be responsible for exchanging a JWT token for an OAuth 2.0 access token which we'll need to work with the Cloud Manager API. Paste the following code into token.js. Save and close the file.
const jsrsasign = require("jsrsasign");
const fetch = require("node-fetch");
const { URLSearchParams } = require("url");
async function getAccessToken(params) {
const EXPIRATION = 60 * 60; // 1 hour
const header = {
alg: "RS256",
typ: "JWT",
};
const payload = {
exp: Math.round(new Date().getTime() / 1000) + EXPIRATION,
iss: params.IMS_ORG_ID,
sub: params.TECHNICAL_ACCOUNT_ID,
aud: `https://ims-na1.adobelogin.com/c/${params.SERVICE_API_KEY}`,
"https://ims-na1.adobelogin.com/s/ent_cloudmgr_sdk": true,
};
const jwtToken = jsrsasign.jws.JWS.sign(
"RS256",
JSON.stringify(header),
JSON.stringify(payload),
params.PRIVATE_KEY
);
const response = await fetch(
"https://ims-na1.adobelogin.com/ims/exchange/jwt",
{
method: "POST",
body: new URLSearchParams({
client_id: params.SERVICE_API_KEY,
client_secret: params.CLIENT_SECRET,
jwt_token: jwtToken,
}),
}
);
const json = await response.json();
return json["access_token"];
}
module.exports = {
getAccessToken
}
Open the index.js file within the editor and replace the contents with the following code. Update the SLACK_WEBHOOK
constant variable with the appropriate value for your Slack App.
const fetch = require('node-fetch')
const { Core } = require('@adobe/aio-sdk')
const { getAccessToken } = require('./token');
const { errorResponse, getBearerToken, stringParameters, checkMissingRequestInputs } = require('../utils')
const SLACK_WEBHOOK = 'https://hooks.slack.com/services/<YOUR-WEBHOOK-URL>';
async function sendNotification (params) {
const accessToken = await getAccessToken(params);
let type = params.event['@type'].indexOf('started') > -1 ? 'STARTED' : 'ENDED';
let id = params.event['activitystreams:object']['@id'];
// Make GET Request to Cloud Manager API
let pipelineRequest = await fetch(id, {
'method': 'GET',
'headers': { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}`, 'x-gw-ims-org-id': params.IMS_ORG_ID, 'x-api-key': params.SERVICE_API_KEY}
});
let pipeline = await pipelineRequest.json();
let notification = `Cloud Manager Pipeline Notification \n Event Type: ${type} \n Status: ${pipeline.status} \n ID: ${id}`;
//Make POST to Slack Webhook
const message = await fetch(SLACK_WEBHOOK, {
'method': 'POST',
'headers': { 'Content-Type': 'application/json' },
'body': JSON.stringify({
'text': notification
})
})
const response = {
statusCode: 200,
body: message
}
return response
}
async function main (params) {
const logger = Core.Logger('main', { level: params.LOG_LEVEL || 'info' })
try {
const requiredParams = ['event', 'SERVICE_API_KEY', 'CLIENT_SECRET', 'IMS_ORG_ID', 'TECHNICAL_ACCOUNT_ID']
const requiredHeaders = []
const errorMessage = checkMissingRequestInputs(params, requiredParams, requiredHeaders)
if (errorMessage) {
return errorResponse(400, errorMessage, logger)
}
let response = await sendNotification(params);
return response;
} catch (error) {
logger.error(error)
return errorResponse(500, 'server error', logger)
}
}
exports.main = main
You should now have two modules within your actions > notify-slack folder.
From the terminal, run the following command to start the application.
aio app run
Notice that the console output provides two URLs to view the application.
- Local Application - Renders the application from a local HTTPS server
https://localhost:9080
- Experience Cloud Shell - Renders the application within the Experience Cloud Shell with an authenticated session
https://experience.adobe.com/?devMode=true#/custom-apps/?localDevUrl=https://localhost:9080
Confirm that the action is available from the menu.
Registering I/O Event Listener
1) Return to the Developer Console and click the Add Event button within the production workspace of the project.
2) Select the Experience Cloud category and then select the Cloud Manager service.
3) Click the Next button. Select the events you want to subscribe to. For this demo, I selected the Pipeline Execution Started and Pipeline Execution Ended events.
4) Click the Next button. Select the Service Account Credential to associate with the application.
5) Click the Next button. Enter a unique name for this event registration (e.g. PipelineSlackNotification
). Select the action that we created earlier (e.g. notify-slack
) from the Runtime action drop-down menu.
6) Click the Save Configurated Events button. The Event Registration should reflect an active status.
Building Cloud Manager Pipeline
1) Initiate a build on the Cloud Manager Pipeline
2) Open Slack channel and verify the notification was sent