Receive Slack Notification of CodePipeline with SNS and Lambda
Tags: aws, lambda, codepipeline, notification
Published: 2023-03-27 22:20:58

## Motivation

I want to receive Slack notifications from CodePipeline through Amazon SNS and Lambda. By using the Lambda function, formatting and filtering of messages are available. (Therefore, not Chatbot.) In this blog, I'll share how to achieve this.

![codepipeline-notify-lambda-slack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k48h9boc00s604we6vd5.png)

Example of Slack notification:

![Slack notification](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kysepghc8viha7v59cvx.png)

(Note: This is useful for other architectures' notifications if SNS and Lambda are used.)



## Create Slack

- [Create Webhook](https://api.slack.com/messaging/webhooks)
  - Ref: [This post](https://repost.aws/knowledge-center/sns-lambda-webhooks-chime-slack-teams) describe how to use webhooks in Lambda function.
- Memo the webhook URL for Lambda environmental variables


## Create Lambda

- Create a Lambda function of Py3.9
- Set Lambda environmental variables
  - Key: `WEBHOOK_URL_SLACK`, Value: `https://hooks.slack.com/services/xxxxxxx`
- In this Python script, get an event from SNS. 
  - If the CodePipeline status is SUCCEED, do nothing.
  - If the CodePipeline status is FAIL, send a message to Slack.
- Permission: BasicExecutionRole will be OK.


```py
import json
import os

import urllib3

http = urllib3.PoolManager()

WEBHOOK_URL_SLACK = os.environ["WEBHOOK_URL_SLACK"]


def gen_message_from_codepipeline_event(event_dict):
    """
    Return message according to the CodePipeline state.
    """

    # No message when SUCCEEDED
    if event_dict["detail"]["state"] == "SUCCEEDED":
        return ""

    if event_dict["detail"]["state"] == "FAILED":
        message = "CodePipeline state: FAILED"

        failed_stage = event_dict.get("additionalAttributes", {}).get("failedStage")
        stage_info = (
            f"Failed Stage: {failed_stage}"
            if failed_stage
            else "Failed Stage: N/A"
        )
        message += f"\n{stage_info}"

        failed_actions = event_dict.get("additionalAttributes", {}).get("failedActions")
        if failed_actions:
            # Send only the last try info
            info_last_action = failed_actions[-1]['additionalInformation']
            message += f"\nInformation: {info_last_action}"

        return message


def lambda_handler(event, context):
    """
    Handle CodePipeline notifications and send messages to Slack.
    """

    try:
        event_str = event["Records"][0]["Sns"]["Message"]
    except (KeyError, IndexError):
        print("Error: Event is missing required data")
        return

    event_dict = json.loads(event_str)

    # generate message
    message = gen_message_from_codepipeline_event(event_dict)
    if not message:
        print({"statusCode": 200, "body": "No message to return."})
        return
    region = event_dict["region"]
    pipeline = event_dict["detail"]["pipeline"]
    pipeline_url = f"https://{region}.console.aws.amazon.com/codesuite/codepipeline/pipelines/{pipeline}/view?region={region}"

    # Send Slack webhook
    text = f"{message}\n<{pipeline_url}|Visit CodePipeline>"
    msg = {
        "text": text,
    }
    encoded_msg = json.dumps(msg).encode("utf-8")
    resp = http.request("POST", WEBHOOK_URL_SLACK, body=encoded_msg)
    print({"statusCode": resp.status, "body": "Send message."})

    return

```


## Create CodePipeline

You can use any CodePipeline. If you want a simple sample, you can use [a CloudFormation template](https://github.com/shimo164/demos/tree/main/cfn-templates/code-pipeline-simple-s3) I've created from the AWS document with the simple S3 and EC2 pipeline. Upload the zip file found in the section of [this AWS document](https://docs.aws.amazon.com/codepipeline/latest/userguide/tutorials-simple-s3.html). 
`If you want to deploy to Windows Server instances using CodeDeploy, download the sample application here: SampleApp_Windows.zip.`




### Create notification

AWS doc is [here](https://docs.aws.amazon.com/codepipeline/latest/userguide/notification-rule-create.html).

On the pipeline page, select "Notify".
	- Create notification rules
	- Set Notification name
	- Detail type: Full
	- Select events: Pipeline execution Failed, Succeeded
	- Targes
		- Create target: SNS Topic
		- Use existing SNS Topic --> Need to set access policy

My settings is as below. 

![Notification Setting](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/voo4im5u5k2q8h5rhf4i.png)


As a target, the SNS topic is used here. When you use an existing SNS Topic, you may need to add access policy. **The Principal is `codestar-notifications.amazonaws.com`. **

If you create a target topic here, the access policy is included.


![target creation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qfmomz13d0t67qwwwcs0.png)



```
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "codestar-notifications.amazonaws.com"
      },
      "Action": "SNS:Publish",
      "Resource": "arn:aws:sns:REGION:ACCOUNT_ID:TOPIC_NAME"
    },
```

### Subscribe
- Subscribe the Lambda function to this SNS Topic.

## Run and fail

Fail scenarios:
- Start Pipeline without the zip file in the proper S3 bucket.
- Start Deploy section with EC2 instances are stopped.


![Failed Pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r7s8znmpabo77ihx7688.png)

Then you'll receive the Slack notification.


![Slack notification](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kysepghc8viha7v59cvx.png)

Note that the `Information` part is from the `additionalInformation` in the JSON message. And it appears on the console like the below image.
![Error message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2p9qom99negz33v6jkh8.png)


## Summary

I've demonstrated how to receive Slack notifications from CodePipeline.