Setting up Slack Notifications For CodePipeline
When you're working with AWS CodePipeline(/CodeBuild/StepFunctions) daily, it can be very useful to receive notifications to slack (or other messaging[...]

Table of Contents
When you're working with AWS CodePipeline(/CodeBuild/StepFunctions) daily, it can be very useful to receive notifications to slack (or other messaging services) of when the pipeline has finished, instead of watching it or periodically coming back to it.
In this post, I'll walk through how I've previously setup Slack Notifications using AWS EventBridge, Lambda and SecretsManager.
EventBridge
AWS EventBridge is the service that allows you to trigger actions when specific events occur. The below CloudFormation allowed me to create 2 EventBridge Rules. One for CodePipeline and the other for StepFunctions.
PipelineRunNotification:
Type: AWS::Events::Rule
Properties:
Description: Pipeline Run Notification
Name: PipelineRunNotification
State: ENABLED
EventPattern:
source:
- aws.codepipeline
detail-type:
- CodePipeline Pipeline Execution State Change
detail:
state:
- FAILED
- STOPPED
- SUCCEEDED
- CANCELED
- STARTED
- RESUMED
resources:
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Infrastructure-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Application-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Frontend-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Mobile-App-IOS-Develop-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Mobile-App-Android-Develop-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Mobile-App-IOS-Release-Candidate-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Mobile-App-Android-Release-Candidate-Pipeline
Targets:
- Arn: !GetAtt SlackNotificationLambda.Arn
Id: PipelineRunNotification
StepFunctionsExecutionNotification:
Type: AWS::Events::Rule
Properties:
Description: Step Functions Execution Notification
Name: StepFunctionsExecutionNotification
State: ENABLED
EventPattern:
source:
- aws.states
detail-type:
- Step Functions Execution Status Change
detail:
status:
- RUNNING
- SUCCEEDED
- FAILED
- TIMED_OUT
- ABORTED
stateMachineArn:
- !Sub arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:ReleaseStepFunction
Targets:
- Arn: !GetAtt SlackNotificationLambda.Arn
SecretsManager
I stored the slack web url in secrets manager to avoid misuse of it.
Slack
In slack I created a specific channel, then created an app which resulted in a url that I could post to which would result in slack messages.
Lambda
The below code shows the jist of how the notifications can be sent
import http.client
import json
import boto3
import logging
import os
logger = logging.getLogger()
# Set the log level from an environment variable or default to ERROR
log_level = os.getenv('LOG_LEVEL', 'ERROR').upper()
logger.setLevel(log_level)
# Create a console handler
handler = logging.StreamHandler()
# Create a formatter and set it for the handler
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
# Add the handler to the logger
logger.addHandler(handler)
def send_slack_message(webhook_url :str, message :str, user :str, icon_emoji :str, fields :list, color :str) -> None:
def get_codepipeline_execution_information(event :dict) -> dict:
def get_slack_notification_url(json_key :str = 'default') -> str:
def handle_codepipeline_event(event :dict) -> str:
def handle_eventbridge_event(event :dict) -> str:
def handle_cloudformation_event(event :dict) -> str:
def handle_cli_event(event :dict) -> str:
def handle_step_function_event(event: dict) -> str:
def handler(event, context):
logger.info(f"Event: {event} \n Context: {context}")
# Determine the source of the event and extract the message
if 'source' in event and event['source'] == 'aws.codepipeline':
handle_codepipeline_event(event)
elif 'source' in event and event['source'] == 'aws.events':
handle_eventbridge_event(event)
elif 'RequestType' in event: # CloudFormation Custom Resource
handle_cloudformation_event(event)
elif 'detail' in event and 'aws-cli' in event['detail']:
handle_cli_event(event)
elif 'source' in event and event['source'] == "aws.states":
handle_step_function_event(event)
if __name__ == '__main__':
handler(None, None)send_slack_message
def send_slack_message(webhook_url :str, message :str, user :str, icon_emoji :str, fields :list, color :str) -> None:
# Parse the webhook URL to get the host and path
logger.info(f"Sending message to Slack: {message}")
url_parts = webhook_url.split("/", 3)
host = url_parts[2]
path = "/" + url_parts[3]
# Create a connection object
conn = http.client.HTTPSConnection(host)
# Headers for the request
headers = {
'Content-type': 'application/json'
}
# Data to be sent in the POST request
payload = json.dumps({
"text": message,
"username": user,
"icon_emoji": icon_emoji,
"color": color,
"fields": fields
})
# Send a POST request
conn.request("POST", path, body=payload, headers=headers)
# Get the response
response = conn.getresponse()
# Read and decode the response
data = response.read().decode()
# Print the status and response
logger.info(f"Status: {response.status}")
logger.info(f"Response: {data}")
# Close the connection
conn.close()get_slack_notification_url
def get_slack_notification_url(json_key :str = 'default') -> str:
"""
Gets the Slack notification URL from AWS Secrets Manager
Pass in the key (channel type) to get the URL
"""
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId='slack-notification-urls')
raw_secret = response['SecretString']
secret_json = json.loads(raw_secret)
return secret_json[json_key] handle_codepipeline_event
def handle_codepipeline_event(event :dict) -> str:
logger.info(f"Handling CodePipeline event: {event}")
url = get_slack_notification_url('default')
codepipeline_detail = get_codepipeline_execution_information(event)
logger.info(f"Lambda Triggered by {codepipeline_detail.get('trigger')}")
pipeline = codepipeline_detail.get("pipeline")
env = os.environ['Environment'].upper()
state = event.get('detail').get('state')
if 'build-orchestrator' in codepipeline_detail.get('trigger'):
user = "Build Orchestrator"
elif '/Administrator/' in codepipeline_detail.get('trigger'):
user = f"{env} Administrator"
else:
user = codepipeline_detail.get('trigger').split('/')[-1]
pipeline = codepipeline_detail.get("pipeline")
env = os.environ['Environment'].upper()
state = event.get('detail').get('state')
message = f"{pipeline} in {env} triggered by {user} is in a {state} state"
fields = [
{"title": "Pipeline", "value": pipeline, "short": True},
{"title": "State", "value": state, "short": True},
{"title": "ExecutionId", "value": codepipeline_detail.get("execution_id"), "short": True},
{"title": "Environment", "value": env, "short": True}
]
if event.get('detail').get('state') in ("FAILED", "CANCELLED"):
color = "bad"
else:
color = "good"
icon_dict = {
"Application-Pipeline": ":rocket:",
"Infrastructure-Pipeline": ":factory:",
"Frontend-Pipeline": ":globe_with_meridians:",
"Mobile-App-IOS-Develop-Pipeline": ":iphone:",
"Mobile-App-Android-Develop-Pipeline": ":phone:",
"Mobile-App-IOS-Release-Candidate-Pipeline": ":iphone:",
"Mobile-App-Android-Release-Candidate-Pipeline": ":phone:"
}
send_slack_message(url, str(message), user, icon_dict.get(pipeline), fields, color)handle_step_function_event
def handle_step_function_event(event: dict) -> str:
logger.info(f"Handling Step Function Event")
logger.info(f"Event Info: {event}")
url = get_slack_notification_url('default')
env = os.environ['Environment'].upper()
state = event.get('detail').get('status')
release_name = event.get('detail').get('name')
message = f"Release Step Function execution of {release_name} in {env} is in a {state} state"
input_value = json.loads(event.get('detail').get('input'))
fields = [
{"title": "Release", "value": input_value.get('release_version'), "short": True},
{"title": "State", "value": state, "short": True},
{"title": "Hotfix", "value": input_value.get('hotfix'), "short": True},
{"title": "MobileRelease", "value": input_value.get('run_app_pipelines'), "short": True},
{"title": "Environment", "value": env, "short": True}
]
if event.get('detail').get('status') in ("FAILED", "TIMED_OUT", "ABORTED"):
color = "bad"
else:
color = "good"
if event.get('detail').get('error') is not None:
fields.append({"title": "Error", "value": event.get('detail').get('error'), "short": False})
if event.get('detail').get('cause') is not None:
fields.append({"title": "Cause", "value": event.get('detail').get('cause'), "short": False})
send_slack_message(webhook_url=url, message=message, user="Release Step Function", icon_emoji=":cool-doge:", fields=fields, color=color)
CloudFormation
Lambda
SlackNotificationLambda:
Type: AWS::Lambda::Function
Properties:
Handler: !Sub ${FileName}.handler
Role: !GetAtt SlackNotificationLambdaRole.Arn
Code:
S3Bucket: !Ref DevOpsBucketName
S3Key: !Sub app-infra/zipped_lambdas/${FileName}.zip
Runtime: "python3.11"
Timeout: 30
MemorySize: 128
Description: "Lambda function to send slack notification"
Environment:
Variables:
AccountId: !Sub ${AWS::AccountId}
Environment: !Ref Environment
DevOpsBucketName: !Ref DevOpsBucketName
Tags:
- Key: "CostCenter"
Value: !Ref CostCenter
- Key: "Environment"
Value: !Ref EnvironmentIAM
SlackNotificationLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: SlackNotificationLambdaPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource:
- !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:slack-notification-urls*
- PolicyName: CodePipelinePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- codepipeline:GetPipeline
- codepipeline:GetPipelineState
- codepipeline:GetPipelineExecution
Resource:
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Infrastructure-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Application-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Frontend-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Mobile-App-IOS-Develop-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Mobile-App-Android-Develop-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Mobile-App-IOS-Release-Candidate-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Mobile-App-Android-Release-Candidate-Pipeline
- PolicyName: StepFunctionsPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- states:DescribeExecution
- states:DescribeStateMachine
Resource:
- !Sub arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:ReleaseStepFunction-ZrbGymeGn9iR
Permissions
LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref SlackNotificationLambda
Action: "lambda:InvokeFunction"
Principal: "events.amazonaws.com"
SourceArn: !GetAtt PipelineRunNotification.Arn
LambdaInvokePermissionStepFunction:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref SlackNotificationLambda
Action: "lambda:InvokeFunction"
Principal: "events.amazonaws.com"
SourceArn: !GetAtt StepFunctionsExecutionNotification.Arn
EventBridge
PipelineRunNotification:
Type: AWS::Events::Rule
Properties:
Description: Pipeline Run Notification
Name: PipelineRunNotification
State: ENABLED
EventPattern:
source:
- aws.codepipeline
detail-type:
- CodePipeline Pipeline Execution State Change
detail:
state:
- FAILED
- STOPPED
- SUCCEEDED
- CANCELED
- STARTED
- RESUMED
resources:
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Infrastructure-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Application-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Frontend-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Mobile-App-IOS-Develop-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Mobile-App-Android-Develop-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Mobile-App-IOS-Release-Candidate-Pipeline
- !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:Mobile-App-Android-Release-Candidate-Pipeline
Targets:
- Arn: !GetAtt SlackNotificationLambda.Arn
Id: PipelineRunNotification
StepFunctionsExecutionNotification:
Type: AWS::Events::Rule
Properties:
Description: Step Functions Execution Notification
Name: StepFunctionsExecutionNotification
State: ENABLED
EventPattern:
source:
- aws.states
detail-type:
- Step Functions Execution Status Change
detail:
status:
- RUNNING
- SUCCEEDED
- FAILED
- TIMED_OUT
- ABORTED
stateMachineArn:
- !Sub arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:ReleaseStepFunction
Targets:
- Arn: !GetAtt SlackNotificationLambda.Arn
Id: StepFunctionsExecutionNotification