はじめに
CodePipeline で CI/CD を組んでいると各ステージの進捗状況や結果を CloudWatch と SNS を使って Slack 等に通知されている方もいるかと思いますが、これを CodeStar Notifications
と言うサービスの通知機能を使えば簡単に代替することができます。
CloudWatch は イベント 100 万件あたり 1.00USD 掛かりますが、Notifications は無料になります。
まぁ、個人利用で 100 万イベントなんて私にはあり得ないので、イベントだろうが Notifications だろうが関係ありませんが。
今回は CodeStar Notifications + SNS + Lambda を使って CodePipeline の状態通知を Slack に送信する仕組みを作ってみます。
イメージ
今までは CloudWatch Events だったところが CodeStar Notifications に変わっています。
手順
Notifications と SNS を CloudFormation で作成
CodeStar Notifications
Code 兄弟(Commit、Build、Deploy、 Pipeline)のイベント通知ルールを作成する CloudFormation 例が下記になります。各 Code 兄弟の通知イベントは EventTypeIds から指定できます。各イベントの一覧はこちらの公式から確認できます。今回は CodePipeline の各ステージの実行イベントを通知しています。
Resource
で対象となる Code 兄弟の ARN を指定EventTypeIds
で通知したいイベントを指定Targets
で Publish する SNS の ARN を指定
1NotificationRule:
2 Type: AWS::CodeStarNotifications::NotificationRule
3 Properties:
4 Name: sample-codepipeline-notification-rule
5 Resource: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:SamplePipelineName
6 DetailType: FULL
7 EventTypeIds:
8 - codepipeline-pipeline-stage-execution-started
9 - codepipeline-pipeline-stage-execution-succeeded
10 - codepipeline-pipeline-stage-execution-resumed
11 - codepipeline-pipeline-stage-execution-canceled
12 - codepipeline-pipeline-stage-execution-failed
13 Targets:
14 - TargetType: SNS
15 TargetAddress: !Ref SNSTopic
また、地味にハマるのが TargetType
の書き方でした。公式の YAML の例が下記になるのですが、SNS,
と " , " 区切りで SNS のトピックを指定していますが、" , " があると問答無用で CloudFormation のデプロイが止まりました。CloudFormation あるあるでエラー内容がよく分からないためハマりましたが、", " を消すことでエラーが除かれました。
1Targets:
2 - TargetType: SNS,
3 TargetAddress: 'Fn::Sub': 'arn:aws:sns:us-east-2:123456789012:MyNotificationTopic'
SNS Topic Policy
CodeStar Notifications が SNS トピックを発行できるように codestar-notifications.amazonaws.com
を Principal
に追加して上げます。
1SNSTopicPolicy:
2 Type: AWS::SNS::TopicPolicy
3 Properties:
4 Topics:
5 - !Ref SNSTopic
6 PolicyDocument:
7 Id: !Ref SNSTopic
8 Version: 2008-10-17
9 Statement:
10 - Sid: "__default_statement_ID"
11 Effect: Allow
12 Principal:
13 AWS: "*"
14 Action:
15 - SNS:GetTopicAttributes
16 - SNS:SetTopicAttributes
17 - SNS:AddPermission
18 - SNS:RemovePermission
19 - SNS:DeleteTopic
20 - SNS:Subscribe
21 - SNS:ListSubscriptionsByTopic
22 - SNS:Publish
23 - SNS:Receive
24 Resource: !Ref SNSTopic
25 Condition:
26 StringEquals:
27 AWS:SourceOwner: !Ref AWS::AccountId
28 - Sid: "AWSCodeStarNotifications_publish"
29 Effect: Allow
30 Principal:
31 Service:
32 - codestar-notifications.amazonaws.com
33 Action: SNS:Publish
34 Resource: !Ref SNSTopic
CloudFormation 全体
あくまでサンプル程度です。Lambda や CodePipeline の ARN を指定する必要があるので注意下さい。
1AWSTemplateFormatVersion: 2010-09-09
2
3Parameters:
4 SamplePipelineName:
5 Type: String
6 Default: "SamplePipeline"
7 LambdaArn:
8 Type: String
9
10Resources:
11 NotificationRule:
12 Type: AWS::CodeStarNotifications::NotificationRule
13 Properties:
14 Name: sample-codepipeline-notification-rule
15 Resource: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${SamplePipelineName}
16 DetailType: FULL
17 EventTypeIds:
18 - codepipeline-pipeline-stage-execution-started
19 - codepipeline-pipeline-stage-execution-succeeded
20 - codepipeline-pipeline-stage-execution-resumed
21 - codepipeline-pipeline-stage-execution-canceled
22 - codepipeline-pipeline-stage-execution-failed
23 Targets:
24 - TargetType: SNS
25 TargetAddress: !Ref SNSTopic
26
27 SNSTopic:
28 Type: AWS::SNS::Topic
29 Properties:
30 TopicName: sample-sns-topic
31 Subscription:
32 - Endpoint: !Ref LambdaArn
33 Protocol: lambda
34
35 SNSTopicPolicy:
36 Type: AWS::SNS::TopicPolicy
37 Properties:
38 Topics:
39 - !Ref SNSTopic
40 PolicyDocument:
41 Id: !Ref SNSTopic
42 Version: 2008-10-17
43 Statement:
44 - Sid: "__default_statement_ID"
45 Effect: Allow
46 Principal:
47 AWS: "*"
48 Action:
49 - SNS:GetTopicAttributes
50 - SNS:SetTopicAttributes
51 - SNS:AddPermission
52 - SNS:RemovePermission
53 - SNS:DeleteTopic
54 - SNS:Subscribe
55 - SNS:ListSubscriptionsByTopic
56 - SNS:Publish
57 - SNS:Receive
58 Resource: !Ref SNSTopic
59 Condition:
60 StringEquals:
61 AWS:SourceOwner: !Ref AWS::AccountId
62 - Sid: "AWSCodeStarNotifications_publish"
63 Effect: Allow
64 Principal:
65 Service:
66 - codestar-notifications.amazonaws.com
67 Action: SNS:Publish
68 Resource: !Ref SNSTopic
Slack 通知用の Lambda
Slack API の Incoming Webhooks を使って Python で通知する Lambda になります。通知内容を分かりやすくするためステージの状態に応じて色や emoji を変更しています。
1import json
2import urllib.request
3
4WEBHOOK_URL = "https://hooks.slack.com/services/***/***/******"
5
6
7def lambda_handler(event, context):
8
9 try:
10
11 message = trim_event_message(event)
12
13 state_color = get_stage_state_color(message["detail"]["state"])
14 state_emoji = get_stage_state_emoji(message["detail"]["state"])
15
16 tldr_message = f'@channel\n *{message["detail"]["stage"]}* stage is *{message["detail"]["state"]}*. {state_emoji}'
17
18 blocks = [
19 {
20 "type": "section",
21 "text": {
22 "type": "mrkdwn",
23 "text": tldr_message
24 },
25 },
26 ]
27
28 attachments = [
29 {
30 "color": state_color,
31 "blocks": [
32 {
33 "type": "section",
34 "text": {
35 "type": "mrkdwn",
36 "text": message["detailType"]
37 },
38 },
39 {
40 "type": "section",
41 "fields": [
42 {
43 "type": "mrkdwn",
44 "text": message["time"]
45 },
46 {
47 "type": "mrkdwn",
48 "text": message["region"]
49 },
50 ],
51 },
52 ],
53 }
54 ]
55
56 send_data = {
57 "text": tldr_message,
58 "blocks": blocks,
59 "attachments": attachments
60 }
61
62 send_text = "payload=" + json.dumps(send_data)
63
64 request = urllib.request.Request(
65 WEBHOOK_URL,
66 data=send_text.encode('utf-8'),
67 method="POST"
68 )
69
70 with urllib.request.urlopen(request) as response:
71 response_body = response.read().decode('utf-8')
72
73 except Exception as e:
74
75 print(e)
76 raise e
77
78
79 return {
80 "statusCode": 200,
81 "body": json.dumps({
82 "message": "success",
83 }),
84 }
85
86
87def trim_event_message(event):
88
89 # eventにNoneが含まれる可能性あり、
90 # そのため一旦全てをstringに変換
91 # そして、dictに再変換
92 event_dict = json.loads(json.dumps(event))
93
94 sns_message = event_dict["Records"][0]["Sns"]["Message"]
95
96 return json.loads(sns_message)
97
98
99def get_stage_state_color(state):
100
101 if state == "CANCELED":
102 # yellow
103 return "#DAA038"
104
105 if state == "FAILED":
106 # red
107 return "#CF0100"
108
109 if state == "STARTED":
110 # blue
111 return "#439FE0"
112
113 if state == "SUCCEEDED":
114 # green
115 return "#34A64F"
116
117 if state == "RESUMED":
118 # grey
119 return ""
120
121def get_stage_state_emoji(state):
122
123 if state == "CANCELED":
124 return ":double_vertical_bar:"
125
126 if state == "FAILED":
127 return ":ng:"
128
129 if state == "STARTED":
130 return ":arrow_forward:"
131
132 if state == "SUCCEEDED":
133 return ":ok:"
134
135 if state == "RESUMED":
136 return ":repeat_one:"
通知結果
もろもろをデプロイすると下記のような結果が Slack に返ってくると思います。工夫する点は多々あると思いますが、個人利用であればこれで十分かなと思います。
おわりに
あると便利なこれらの通知を作りまくってると意外に費用が掛かったりするので、今回は無料の Notifications を使ってみました。
前々から思ってましたが Slack って便利ですね。
API も用意されていて、AWS Chat と連携すれば本当に簡単にですが Slack 上で管理画面が作れるのではと思ったり思わなかったり、ちょとした夢が膨らんでいます。