Monitor your AWS CodeBuilds via Lambda and Slack

I recently setup AWS CodePipeline and CodeBuild to perform continuous integration and testing. The piece that was missing out of the box was build notifications. I want to know if my build passes or fails and what the errors are.

I was able to throw together a quick solution using AWS CloudWatch Events, Lambda, and Slack. Here’s how it works…

CloudWatch Events trigger a Lambda for all CodeBuild phases. The Lambda POST’s a message to a Slack web hook.

I used CloudFormation to define and deploy the stack.

Lambda Permissions & Function

#
# Role that our Lambda will assume to provide access to other AWS resources
#
IamRoleLambdaExecution:
Type:
AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version:
'2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: '/'

#
# Create a Policy and attach it to our Lambda Role.
#
IamPolicyLambdaExecution:
Type:
AWS::IAM::Policy
DependsOn: IamRoleLambdaExecution
Properties:
PolicyName:
IamPolicyLambdaExecution
PolicyDocument:
Version:
'2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:*
Resource: '*'
Roles:
- Ref: IamRoleLambdaExecution

#
# Lambda Function
#

SlackFunction:
Type:
AWS::Lambda::Function
Properties:
Handler:
slack.handler
Timeout: 5
Role:
Fn::GetAtt:
- IamRoleLambdaExecution
- Arn
Code:
S3Bucket:
<your s3 bucket>
S3Key: 'slack.js.zip'
Runtime: nodejs6.10
Environment:
Variables:
SLACK_HOOK_URL:
<your slack url>

Upload the lambda function below to an S3 bucket as a zip file. Replace the bucket path and slack hook url in the CloudFormation snippet above.

CloudWatch Events

#
# CloudWatch Event to trigger lambda for build slack notifications.
#
BuildEventRule:
Type:
'AWS::Events::Rule'
Properties:
Description:
'BuildEventRule'
EventPattern:
source:
- 'aws.codebuild'
detail-type:
- 'CodeBuild Build State Change'
detail:
build-status:
- 'IN_PROGRESS'
- 'SUCCEEDED'
- 'FAILED'
- 'STOPPED'
State: 'ENABLED'
Targets:
-
Arn: !GetAtt SlackFunction.Arn
Id: 'BuildRuleLambdaTarget'

#
# Permission for CloudWatch to invoke our Lambda
#

PermissionForBuildEventsToInvokeLambda:
Type:
'AWS::Lambda::Permission'
Properties:
FunctionName:
!Ref SlackFunction
Action: 'lambda:InvokeFunction'
Principal: 'events.amazonaws.com'
SourceArn: !GetAtt BuildEventRule.Arn

Now our Lambda will be invoked when CodeBuild changes state.

Lambda Code

'use strict';

const https = require('https');
const url = require('url');

var functions = {};

//
// Post a message to the slack hook.
//
functions.slack = function(text, hook_url, done) {
const slack_hook_url_parts =
url.parse(hook_url);

const options = {
hostname: slack_hook_url_parts.host,
port: 443,
path: slack_hook_url_parts.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
};
console.log(JSON.stringify(options));

var req = https.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function(chunk) {
console.log('data');
done(null, chunk);
});
res.on('end', function() {
console.log('end');
done(null, null);
});
});

req.on('error', function(err) {
done(err, null);
});

// write data to request body
req.write(JSON.stringify({
text: text
}));
req.end();
};

//
// Handle the CodeBuild CloudWatch Event and post the message to slack so we know the progress of our build.
//
functions.handler = function(event, context) {

console.log(JSON.stringify(event));

const status = event.detail['build-status'];
const project = event.detail['project-name'];

// If failed. Get logs path.
var text = project+' '+status;
if (status === 'FAILED') {
text += '\n';
text += 'https://console.aws.amazon.com/cloudwatch/home?region='+event.region+'#logEventViewer:group=/aws/codebuild/'+project+';start=PT5M';
}

const SLACK_HOOK_URL = process.env.SLACK_HOOK_URL;
functions.slack(text, SLACK_HOOK_URL, function(err, results) {
if (err) return context.fail(err);
else return context.succeed(results);
});
};

module.exports = functions;

That’s it!

If you want to limit your notifications to a specific CodeBuild instance you can add that to the EventPattern using the project-name. For example:

EventPattern:
source:
- 'aws.codebuild'
detail-type:
- 'CodeBuild Build State Change'
detail:
project-name:
- '<your CodeBuild name>'
build-status:
- 'IN_PROGRESS'
- 'SUCCEEDED'
- 'FAILED'
- 'STOPPED'