├── .gitignore ├── .readme-assets ├── msteams-screenshot.png ├── slack-screenshot.png └── webhook-notification-graph.png ├── CF-PipelineNotification.yaml ├── PipelineNotification.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### OSX ### 20 | *.DS_Store 21 | .AppleDouble 22 | .LSOverride 23 | 24 | # Icon must end with two \r 25 | Icon 26 | 27 | # Thumbnails 28 | ._* 29 | 30 | # Files that might appear in the root of a volume 31 | .DocumentRevisions-V100 32 | .fseventsd 33 | .Spotlight-V100 34 | .TemporaryItems 35 | .Trashes 36 | .VolumeIcon.icns 37 | .com.apple.timemachine.donotpresent 38 | 39 | # Directories potentially created on remote AFP share 40 | .AppleDB 41 | .AppleDesktop 42 | Network Trash Folder 43 | Temporary Items 44 | .apdisk 45 | 46 | ### PyCharm ### 47 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 48 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 49 | 50 | # User-specific stuff: 51 | .idea/**/workspace.xml 52 | .idea/**/tasks.xml 53 | .idea/dictionaries 54 | 55 | # Sensitive or high-churn files: 56 | .idea/**/dataSources/ 57 | .idea/**/dataSources.ids 58 | .idea/**/dataSources.xml 59 | .idea/**/dataSources.local.xml 60 | .idea/**/sqlDataSources.xml 61 | .idea/**/dynamic.xml 62 | .idea/**/uiDesigner.xml 63 | 64 | # Gradle: 65 | .idea/**/gradle.xml 66 | .idea/**/libraries 67 | 68 | # CMake 69 | cmake-build-debug/ 70 | 71 | # Mongo Explorer plugin: 72 | .idea/**/mongoSettings.xml 73 | 74 | ## File-based project format: 75 | *.iws 76 | 77 | ## Plugin-specific files: 78 | 79 | # IntelliJ 80 | /out/ 81 | 82 | # mpeltonen/sbt-idea plugin 83 | .idea_modules/ 84 | 85 | # JIRA plugin 86 | atlassian-ide-plugin.xml 87 | 88 | # Cursive Clojure plugin 89 | .idea/replstate.xml 90 | 91 | # Ruby plugin and RubyMine 92 | /.rakeTasks 93 | 94 | # Crashlytics plugin (for Android Studio and IntelliJ) 95 | com_crashlytics_export_strings.xml 96 | crashlytics.properties 97 | crashlytics-build.properties 98 | fabric.properties 99 | 100 | ### PyCharm Patch ### 101 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 102 | 103 | # *.iml 104 | # modules.xml 105 | # .idea/misc.xml 106 | # *.ipr 107 | 108 | # Sonarlint plugin 109 | .idea/sonarlint 110 | 111 | ### Python ### 112 | # Byte-compiled / optimized / DLL files 113 | __pycache__/ 114 | *.py[cod] 115 | *$py.class 116 | 117 | # C extensions 118 | *.so 119 | 120 | # Distribution / packaging 121 | .Python 122 | build/ 123 | develop-eggs/ 124 | dist/ 125 | downloads/ 126 | eggs/ 127 | .eggs/ 128 | lib/ 129 | lib64/ 130 | parts/ 131 | sdist/ 132 | var/ 133 | wheels/ 134 | *.egg-info/ 135 | .installed.cfg 136 | *.egg 137 | 138 | # PyInstaller 139 | # Usually these files are written by a python script from a template 140 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 141 | *.manifest 142 | *.spec 143 | 144 | # Installer logs 145 | pip-log.txt 146 | pip-delete-this-directory.txt 147 | 148 | # Unit test / coverage reports 149 | htmlcov/ 150 | .tox/ 151 | .coverage 152 | .coverage.* 153 | .cache 154 | .pytest_cache/ 155 | nosetests.xml 156 | coverage.xml 157 | *.cover 158 | .hypothesis/ 159 | 160 | # Translations 161 | *.mo 162 | *.pot 163 | 164 | # Flask stuff: 165 | instance/ 166 | .webassets-cache 167 | 168 | # Scrapy stuff: 169 | .scrapy 170 | 171 | # Sphinx documentation 172 | docs/_build/ 173 | 174 | # PyBuilder 175 | target/ 176 | 177 | # Jupyter Notebook 178 | .ipynb_checkpoints 179 | 180 | # pyenv 181 | .python-version 182 | 183 | # celery beat schedule file 184 | celerybeat-schedule.* 185 | 186 | # SageMath parsed files 187 | *.sage.py 188 | 189 | # Environments 190 | .env 191 | .venv 192 | env/ 193 | venv/ 194 | ENV/ 195 | env.bak/ 196 | venv.bak/ 197 | 198 | # Spyder project settings 199 | .spyderproject 200 | .spyproject 201 | 202 | # Rope project settings 203 | .ropeproject 204 | 205 | # mkdocs documentation 206 | /site 207 | 208 | # mypy 209 | .mypy_cache/ 210 | 211 | ### VisualStudioCode ### 212 | .vscode/* 213 | !.vscode/settings.json 214 | !.vscode/tasks.json 215 | !.vscode/launch.json 216 | !.vscode/extensions.json 217 | .history 218 | 219 | ### Windows ### 220 | # Windows thumbnail cache files 221 | Thumbs.db 222 | ehthumbs.db 223 | ehthumbs_vista.db 224 | 225 | # Folder config file 226 | Desktop.ini 227 | 228 | # Recycle Bin used on file shares 229 | $RECYCLE.BIN/ 230 | 231 | # Windows Installer files 232 | *.cab 233 | *.msi 234 | *.msm 235 | *.msp 236 | 237 | # Windows shortcuts 238 | *.lnk 239 | 240 | # Build folder 241 | 242 | */build/* 243 | 244 | # End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode 245 | -------------------------------------------------------------------------------- /.readme-assets/msteams-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globaldatanet/aws-codepipeline-notification/726d7605b27945d93a46ccb0a46962c0c9b7782f/.readme-assets/msteams-screenshot.png -------------------------------------------------------------------------------- /.readme-assets/slack-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globaldatanet/aws-codepipeline-notification/726d7605b27945d93a46ccb0a46962c0c9b7782f/.readme-assets/slack-screenshot.png -------------------------------------------------------------------------------- /.readme-assets/webhook-notification-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globaldatanet/aws-codepipeline-notification/726d7605b27945d93a46ccb0a46962c0c9b7782f/.readme-assets/webhook-notification-graph.png -------------------------------------------------------------------------------- /CF-PipelineNotification.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: Pipeline Notification Lambda 3 | 4 | #----------------------------------------------------------------------------- 5 | #Parameters 6 | #----------------------------------------------------------------------------- 7 | Parameters: 8 | 9 | LambdaName: 10 | Description: Name for the PipelineNotification Lambda 11 | Type: String 12 | Default: PipelineNotification 13 | 14 | Env: 15 | Type: String 16 | Default: slack 17 | Description: Messenger to recieve Notifications 18 | AllowedValues: 19 | - slack 20 | - msteams 21 | 22 | WebhookUrl: 23 | Type: String 24 | Default: webhook_url 25 | Description: Incoming Webhook URL to send messages to 26 | 27 | #----------------------------------------------------------------------------- 28 | #Resources 29 | #----------------------------------------------------------------------------- 30 | Resources: 31 | LambdaBasicExecutionRole: 32 | Type: AWS::IAM::Role 33 | Properties: 34 | AssumeRolePolicyDocument: 35 | Version: 2012-10-17 36 | Statement: 37 | - Effect: Allow 38 | Principal: 39 | Service: 40 | - lambda.amazonaws.com 41 | Action: 42 | - sts:AssumeRole 43 | Policies: 44 | - PolicyName: LambdaLoggingRule 45 | PolicyDocument: 46 | Version: 2012-10-17 47 | Statement: 48 | - Effect: Allow 49 | Action: 50 | - 'logs:CreateLogGroup' 51 | - 'logs:CreateLogStream' 52 | - 'logs:PutLogEvents' 53 | Resource: '*' 54 | Path: / 55 | 56 | PermissionForEventsToInvokeLambdaApproval: 57 | Type: AWS::Lambda::Permission 58 | Properties: 59 | FunctionName: 60 | !Ref NotifyLambda 61 | Action: "lambda:InvokeFunction" 62 | Principal: "events.amazonaws.com" 63 | SourceArn: 64 | Fn::GetAtt: 65 | - "CloudWatchRuleApproval" 66 | - "Arn" 67 | 68 | PermissionForEventsToInvokeLambdaFailed: 69 | Type: AWS::Lambda::Permission 70 | Properties: 71 | FunctionName: 72 | !Ref NotifyLambda 73 | Action: "lambda:InvokeFunction" 74 | Principal: "events.amazonaws.com" 75 | SourceArn: 76 | Fn::GetAtt: 77 | - "CloudWatchRuleFailed" 78 | - "Arn" 79 | 80 | NotifyLambda: 81 | Type: AWS::Lambda::Function 82 | Properties: 83 | FunctionName: !Ref LambdaName 84 | Description: Sends information about the CodePipeline Status to messengers (Slack/Ms-Teams) 85 | Environment: 86 | Variables: 87 | WebhookUrl: !Ref WebhookUrl 88 | Messenger: !Ref Env 89 | Handler: index.lambda_handler 90 | Runtime: python3.6 91 | Role: !GetAtt LambdaBasicExecutionRole.Arn 92 | Code: PipelineNotification.py 93 | 94 | CloudWatchRuleFailed: 95 | Type: AWS::Events::Rule 96 | Properties: 97 | Description: CF-Pipeline Notification Rule - FAILED 98 | EventPattern: 99 | source: 100 | - aws.codepipeline 101 | detail: 102 | state: 103 | - FAILED 104 | State: ENABLED 105 | Targets: 106 | - 107 | Arn: !GetAtt NotifyLambda.Arn 108 | Id: NotifyLambda 109 | 110 | CloudWatchRuleApproval: 111 | Type: AWS::Events::Rule 112 | Properties: 113 | Description: CF-Pipeline Notification Rule - APPROVAL 114 | EventPattern: 115 | source: 116 | - aws.codepipeline 117 | detail: 118 | state: 119 | - STARTED 120 | action: 121 | - Approval 122 | State: ENABLED 123 | Targets: 124 | - 125 | Arn: !GetAtt NotifyLambda.Arn 126 | Id: NotifyLambda 127 | -------------------------------------------------------------------------------- /PipelineNotification.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import os 4 | import re 5 | import requests 6 | 7 | 8 | HOOK_URL = os.environ['WebhookUrl'] 9 | MESSENGER = os.environ['Messenger'] 10 | 11 | logger = logging.getLogger() 12 | logger.setLevel(logging.INFO) 13 | 14 | 15 | def handler(event, context): 16 | ''' 17 | main handler 18 | ''' 19 | 20 | # start logging 21 | logger.info(f'Recieved event: {event}') 22 | 23 | # use data from logs 24 | pipeline = event['detail']['pipeline'] 25 | aws_account_id = event['account'] 26 | aws_region = event['region'] 27 | event_time = event['time'] 28 | stage = event['detail']['stage'] 29 | state = event['detail']['state'] 30 | action = event['detail']['action'] 31 | # category = message['detail']['type']['category'] 32 | 33 | # set the color depending on state/category for Approval 34 | color = '#808080' 35 | if action == 'Approval': 36 | color = '#ff9000' 37 | elif state == 'SUCCEEDED': 38 | color = '#00ff00' 39 | elif state == 'STARTED': 40 | color = '#00bbff' 41 | elif state == 'FAILED': 42 | color = '#ff0000' 43 | else: 44 | color = '#000000' 45 | 46 | date = re.split('T|Z',event_time) 47 | date = f'{date[0]} {date[1]}' 48 | pipeline_url = f'''https://{aws_region}.console.aws.amazon.com/codesuite/ 49 | codepipeline/pipelines/{pipeline}/view?region={aws_region}''' 50 | 51 | # build Slack message 52 | if MESSENGER == 'slack': 53 | message_data = { 54 | 'attachments': [ 55 | { 56 | 'fallback': 'Pipeline Status', 57 | 'color': color, 58 | 'author_name': f'{pipeline} - {state} @ {stage}', 59 | 'author_icon': 'https://www.awsgeek.com/AWS-History/icons/AWS-CodePipeline.svg', 60 | 'fields': [ 61 | { 'title': 'Account', 'value': aws_account_id, 'short': 'false' }, 62 | { 'title': 'Region', 'value': aws_region, 'short': 'false' }, 63 | { 'title': 'Event time (UTC)', 'value': date, 'short': 'false' }, 64 | { 'title': 'Action', 'value': action, 'short': 'false' } 65 | ], 66 | 'footer': 'globaldatanet', 67 | 'footer_icon': '''https://pbs.twimg.com/profile_images/980056498847010816/ 68 | JZeg2oTx_400x400.jpg''', 69 | 'ts': 1639133471, # TimeStamp for last update 70 | 'actions': [ 71 | { 72 | 'type': 'button', 'text': 73 | { 'type': 'Open in AWS', 'text': 'Link Button' }, 74 | 'url': pipeline_url 75 | } 76 | ] 77 | } 78 | ] 79 | } 80 | # build MS Teams message 81 | elif MESSENGER == 'msteams': 82 | message_data = { 83 | 'summary': 'summary', 84 | '@type': 'MessageCard', 85 | '@context': 'https://schema.org/extensions', 86 | 'themeColor': color, 87 | 'title': f'{pipeline}', 88 | 'sections': [ 89 | { 90 | 'facts': [ 91 | { 'name': 'Account', 'value': aws_account_id }, 92 | { 'name': 'Region', 'value': aws_region }, 93 | { 'name': 'Event time (UTC)', 'value': date }, 94 | { 'name': 'Stage', 'value': stage }, 95 | { 'name': 'Action', 'value': action }, 96 | { 'name': 'State', 'value': state } 97 | ], 98 | 'markdown': 'true' 99 | } 100 | ], 101 | 'potentialAction': { 102 | '@type': 'OpenUri', 'name': 'Open in AWS', 'targets': [ 103 | { 'os': 'default', 'uri': pipeline_url } 104 | ] 105 | } 106 | } 107 | 108 | # send message to webhook 109 | requests.post(HOOK_URL, json.dumps(message_data)) 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS CodePipeline Notification 2 | 3 | A simple notification application sending different status of your AWS CodePipeline to Slack or MS Teams using an incoming Webhook. You will get notified for any failed state and the approval action only (additional states need new CW Event Rules). 4 | 5 | Cloudwatch Events Rule trigger a Lambda which sends out information about the state of your CodePipeline to either Slack or MS Teams in the appropriate Format using an incoming Webhook API. 6 | 7 | ![Webhook Notification Diagramm](/.readme-assets/webhook-notification-graph.png) 8 | 9 | --- 10 | 11 | ## Table of contents 12 | 13 | 1. [Preview](#preview) 14 | - [Slack](#slack) 15 | - [MS Teams](#ms-teams) 16 | 2. [AWS Lambda Function](#aws-lambda-function) 17 | 3. [Prerequisites](#prerequisites) 18 | 4. [Installation](#installation) 19 | - [AWS CLI](#aws-cli) 20 | 21 | --- 22 | 23 | ## Preview 24 | 25 | ### Slack 26 | 27 | ![Slack](/.readme-assets/slack-screenshot.png) 28 | 29 | ### MS Teams 30 | 31 | ![MS Teams](/.readme-assets/msteams-screenshot.png) 32 | 33 | --- 34 | 35 | ## AWS Lambda Function 36 | 37 | - **Runtime**: Python 3.8 38 | - **Code**: PipelineNotification.py 39 | - **Environment variables**: 40 | 41 | | KEY | VALUE | SCOPE | 42 | |-----------|-------------------------|---------| 43 | |WebhookUrl |https://your_webhook_url |Required | 44 | |Messenger |slack / msteams |Required | 45 | 46 | --- 47 | 48 | ## Prerequisites 49 | 50 | The messages will send via incoming webooks, which need to be configured on Slack or Microsoft Teams 51 | 52 | - [Slack webhook documentation](https://api.slack.com/messaging/webhooks) 53 | - [Teams webhook documentation](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook) 54 | 55 | ## Installation 56 | 57 | The installation is automated with Infrastructure as Code using CloudFormation. 58 | 59 | The stack includes: 60 | 61 | - Lambda basic execution role 62 | - Permission for Cloudwatch Events to invoke Lambda 63 | - CW Event Rules 64 | - Python Lambda 65 | 66 | ### AWS CLI 67 | 68 | Package and upload to S3: 69 | 70 | ```bash 71 | aws cloudformation package \ 72 | --template-file CF-PipelineNotification.yaml\ 73 | --s3-bucket \ 74 | --output-template-file packaged-PipelineNotification.yaml 75 | ``` 76 | 77 | Deploy as a Cloudformation stack: 78 | 79 | ```bash 80 | aws cloudformation deploy \ 81 | --template-file packaged-PipelineNotification.yaml \ 82 | --stack-name CF-PipelineNotification \ 83 | --capabilities CAPABILITY_IAM \ 84 | --parameter-overrides \ 85 | WebhookUrl= \ 86 | Env= 87 | ``` 88 | --------------------------------------------------------------------------------