codebuild-notifier 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,261 @@
1
+ # codebuild-notifier
2
+ Reports status of AWS CodeBuild CI jobs to slack.
3
+
4
+ # Infrastructure Requirements
5
+
6
+ ### Slack App or Bot in your workspace
7
+
8
+ Notifications will be sent as slack Direct Messages to users from the default
9
+ Slack bot in your workspace (e.g. @slackbot)
10
+ - Go to <a href="https://api.slack.com/apps">https://api.slack.com/apps</a>
11
+ - Create a New App, e.g. App Name: CodeBuild Notifier
12
+ - Under *Add features and functionality* select "Permissions" and grant these scopes:
13
+ - chat:write:bot
14
+ - users:read
15
+ - users:read.email
16
+ - Click *Install App To Workspace* and store the OAuth token generated in a new
17
+ secret in AWS Secrets Manager (<a href="#secret-in-aws-secrets-manager">see below</a>)
18
+
19
+ *Optional* Add a Bot User to your app - instead of the default Slack bot, messages
20
+ will come from a user with a name you choose, e.g. CodeBuildBot
21
+ - Under Features / Bot Users, click *Add a Bot User*
22
+ - Select a name and display name; the always show as online option does not matter
23
+ - After adding the Bot User, re-install the app to your workspace
24
+ - A new OAuth token will be generated specific to the Bot User. Store this in
25
+ AWS Secrets Manager instead of the App token.
26
+
27
+ ### DynamoDB table
28
+ - expected to be named 'branch-build-status', but can be configured
29
+ - the following definition:
30
+
31
+ ```ruby
32
+ AttributeDefinitions [
33
+ { AttributeName: 'source_id', AttributeType: 'S' },
34
+ { AttributeName: 'commit_hash', AttributeType: 'S' }
35
+ ]
36
+ GlobalSecondaryIndexes [
37
+ {
38
+ IndexName: 'commit_hash_index',
39
+ KeySchema: [
40
+ { AttributeName: 'commit_hash', KeyType: 'HASH' }
41
+ ],
42
+ Projection: { ProjectionType: 'ALL' },
43
+ }
44
+ ]
45
+ KeySchema [
46
+ { AttributeName: 'source_id', KeyType: 'HASH' }
47
+ ]
48
+ ```
49
+
50
+ ### Secret in AWS Secrets Manager
51
+ - expected to be named 'slack/codebuild', but can be configured
52
+ - contents should be:
53
+ ```json
54
+ { "token": "xoxo-your-slack-app-token" }
55
+ ```
56
+
57
+ ### IAM Service Role for CodeBuild projects
58
+
59
+ You will likely already have a service role granting CloudWatch access, to
60
+ which you will want to add the following, substituing your region,
61
+ account id, and if different, dynamo table name and secrets-manager secret
62
+ name:
63
+ ```json
64
+ {
65
+ "Action": [
66
+ "dynamodb:BatchGetItem",
67
+ "dynamodb:GetItem",
68
+ "dynamodb:PutItem",
69
+ "dynamodb:Query",
70
+ "dynamodb:Scan",
71
+ "dynamodb:UpdateItem"
72
+ ],
73
+ "Effect": "Allow",
74
+ "Resource": [
75
+ "arn:aws:dynamodb:<your-region>:<your-account-id>:table/branch-build-status",
76
+ "arn:aws:dynamodb:<your-region>:<your-account-id>:table/branch-build-status/*"
77
+ ]
78
+ },
79
+ {
80
+ "Action": "secretsmanager:GetSecretValue",
81
+ "Effect": "Allow",
82
+ "Resource": [
83
+ "arn:aws:secretsmanager:<your-region>:<your-account-id>:secret:slack/codebuild*"
84
+ ]
85
+ }
86
+ ```
87
+
88
+ # Configuration
89
+
90
+ ## Installation
91
+
92
+ ### Pre-requisites
93
+
94
+ The base docker image used for your CodeBuild project must include ruby, or
95
+ you must install it using the project's buildspec.yml file.
96
+ Any ruby from 2.3.x to 2.5.x will work.
97
+
98
+ ### Using buildspec
99
+
100
+ Add to the `install:` phase of your buildspec.yml
101
+
102
+ ```yml
103
+ phases:
104
+ install:
105
+ commands:
106
+ - gem install codebuild-notifier
107
+ ```
108
+
109
+ ### Using custom Docker image
110
+
111
+ Add to your Dockerfile
112
+ ```
113
+ RUN gem install codebuild-notifier
114
+ ```
115
+
116
+ ## Usage
117
+
118
+ Add to the `post_build:` phase of your buildspec.yml file
119
+
120
+ ```yml
121
+ phases:
122
+ post_build:
123
+ commands:
124
+ - update-build-status
125
+ ```
126
+
127
+ ## Configuration
128
+
129
+ ### ENV vars
130
+
131
+ ENV vars can either be set in Dockerfile e.g.
132
+ ```
133
+ ENV CBN_SLACK_ADMIN_USERNAMES scooby,shaggy
134
+ ```
135
+
136
+ Or in buildspec.yml
137
+ ```
138
+ env:
139
+ variables:
140
+ CBN_SLACK_ADMIN_USERNAMES: 'fred,velma'
141
+ ```
142
+
143
+ ### command-line
144
+
145
+ In buildspec.yml
146
+
147
+ ```yml
148
+ phases:
149
+ post_build:
150
+ commands:
151
+ - update-build-status --slack-admin-usernames="fred,velma"
152
+ ```
153
+
154
+ ### Options
155
+
156
+ <table>
157
+ <tr>
158
+ <th>ENV var</th>
159
+ <th>command-line</th>
160
+ <th>Default value</th>
161
+ <th>Notes</th>
162
+ </tr>
163
+ <tr>
164
+ <th>
165
+ CBN_ADDITIONAL_CHANNEL
166
+ </th>
167
+ <td>
168
+ <nobr>--additional-channel</nobr>
169
+ </td>
170
+ <td>
171
+ not set
172
+ </td>
173
+ <td>
174
+ If whitelist branches are set, status notifications for these
175
+ branches can be sent to this channel, as well as direct messages
176
+ to the author/committer of the commit triggering the build.
177
+ </td>
178
+ </tr>
179
+ <tr>
180
+ <th>
181
+ CBN_AWS_REGION
182
+ </th>
183
+ <td>
184
+ <nobr>--region</nobr>
185
+ </td>
186
+ <td>
187
+ value of AWS_REGION env var in CodeBuild container
188
+ </td>
189
+ <td>
190
+ If for some reason the dynamo table and secrets-manager live in a
191
+ different region than where CodeBuild is executing, you can specify
192
+ that region.
193
+ </td>
194
+ </tr>
195
+ <tr>
196
+ <th>
197
+ CBN_DYNAMO_TABLE
198
+ </th>
199
+ <td>
200
+ <nobr>--dynamo-table</nobr>
201
+ </td>
202
+ <td>
203
+ branch-build-status
204
+ </td>
205
+ <td>
206
+ This table must be created and permissions granted to it as described
207
+ in <a href="#infrastructure-requirements">Infrastructure Requirements</a>
208
+ </td>
209
+ </tr>
210
+ <tr>
211
+ <th>
212
+ CBN_SLACK_ADMIN_USERNAMES
213
+ </th>
214
+ <td>
215
+ <nobr>--slack-admin-usernames</nobr>
216
+ </td>
217
+ <td>
218
+ not set
219
+ </td>
220
+ <td>
221
+ If no slack user can be found in your workspace with the email
222
+ address of the author or committer of a commit, a message will be
223
+ sent to the slack usernames specified.<br />
224
+ Separate multiple values with commas, with no spaces.<br />
225
+ e.g. fred,velma
226
+ </td>
227
+ </tr>
228
+ <tr>
229
+ <th>
230
+ CBN_SLACK_SECRET_NAME
231
+ </th>
232
+ <td>
233
+ <nobr>--slack-secret-name</nobr>
234
+ </td>
235
+ <td>
236
+ slack/codebuild
237
+ </td>
238
+ <td>
239
+ The name of a secret in AWS Secrets Manager with the app or bot auth token.
240
+ </td>
241
+ </tr>
242
+ <tr>
243
+ <th>
244
+ CBN_WHITELIST_BRANCHES
245
+ </th>
246
+ <td>
247
+ <nobr>--whitelist-branches</nobr>
248
+ </td>
249
+ <td>
250
+ master,release
251
+ </td>
252
+ <td>
253
+ Normally statuses will be stored and notifications sent only for builds
254
+ triggered by commits to branches with open Pull Requests. However, it
255
+ can be useful to get notifications for all commits to certain branches,
256
+ regardless of Pull Request status.<br />
257
+ Separate multiple values with commas, without spaces.<br />
258
+ e.g. 'master,nightly,jira-50012'
259
+ </td>
260
+ </tr>
261
+ </table>
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # codebuild-notifier
4
+ # Copyright © 2018 Adam Alboyadjian <adam@cassia.tech>
5
+ # Copyright © 2018 Vista Higher Learning, Inc.
6
+ #
7
+ # codebuild-notifier is free software: you can redistribute it
8
+ # and/or modify it under the terms of the GNU General Public
9
+ # License as published by the Free Software Foundation, either
10
+ # version 3 of the License, or (at your option) any later version.
11
+ #
12
+ # codebuild-notifier is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with codebuild-notifier. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ require 'codebuild-notifier'
21
+ require 'optparse'
22
+ # This script updates a DynamoDb table with the last codebuild build status
23
+ # for the current project and branch or pr. If the current build status is
24
+ # different from the previous status, the email address of the author of the
25
+ # last commit is extracted from the git commit, and a notification is sent
26
+ # via slack.
27
+
28
+ def quit(message)
29
+ cb_puts(message)
30
+ exit
31
+ end
32
+
33
+ def cb_puts(message)
34
+ puts "CODEBUILD NOTIFIER: #{message}"
35
+ end
36
+
37
+ def not_latest_commit_in_branch_message(build, config)
38
+ "Commit #{build.commit_hash} in project #{build.project_code} did " \
39
+ 'not match the most recent commit for any Pull Requests or the ' \
40
+ "whitelisted branches: #{config.whitelist}. Skipping status updates."
41
+ end
42
+
43
+ def not_pr_or_whitelisted_branch_message(config)
44
+ 'Build is neither for a Pull Request nor for one of whitelisted ' \
45
+ "branches: #{config.whitelist}. Skipping status updates."
46
+ end
47
+
48
+ def no_status_diff_message(build)
49
+ "Current status #{build.status} is same as last status. " \
50
+ 'Skipping slack notifications.'
51
+ end
52
+
53
+ command_line_opts = {}
54
+
55
+ # rubocop:disable Metrics/BlockLength
56
+ OptionParser.new do |opts|
57
+ opts.banner = 'Usage: update-build-status [OPTIONS]'
58
+
59
+ opts.on(
60
+ '--additional-channel=CHANNEL',
61
+ 'status notifications for whitelisted branches will be sent here ' \
62
+ 'as well as to author/committer'
63
+ ) { |usernames| command_line_opts[:slack_admin_users] = usernames }
64
+
65
+ opts.on(
66
+ '--slack-admin-usernames=USERS',
67
+ 'comma-separated list of slack users to be notified if build status ' \
68
+ 'notifications fail to send'
69
+ ) { |usernames| command_line_opts[:slack_admin_users] = usernames }
70
+
71
+ opts.on(
72
+ '--dynamo-table=TABLE',
73
+ 'table for storing build statuses'
74
+ ) { |table| command_line_opts[:dynamo_table] = table }
75
+
76
+ opts.on(
77
+ '--slack-secret-name=SECRET',
78
+ 'name of Secrets Manager secret with slack app/bot auth token'
79
+ ) { |slack_secret| command_line_opts[:slack_secret_name] = slack_secret }
80
+
81
+ opts.on(
82
+ '--whitelist-branches=BRANCHES',
83
+ 'comma-separated list of branches that will have build notifications ' \
84
+ 'sent even if there is no open Pull Request'
85
+ ) { |whitelist| command_line_opts[:whitelist_branches] = whitelist }
86
+
87
+ opts.on('--region=REGION', 'AWS region') do |region|
88
+ command_line_opts[:region] = region
89
+ end
90
+ end.parse!
91
+ # rubocop:enable Metrics/BlockLength
92
+
93
+ config = CodeBuildNotifier::Config.new(command_line_opts)
94
+ build = CodeBuildNotifier::CurrentBuild.new
95
+ history = CodeBuildNotifier::BuildHistory.new(config, build)
96
+
97
+ last_build = history.last_entry
98
+
99
+ if build.launched_by_retry?
100
+ # Whenever a build is triggered by a PR or whitelisted branch, we update
101
+ # the record for that trigger with the commit hash. If a build is then
102
+ # launched using Retry, the status is updated and are notifications sent
103
+ # only if the re-tried build was for the latest commit. Otherwise re-trying
104
+ # an older commit could result in inaccurate notifications.
105
+ quit(not_latest_commit_in_branch_message(build, config)) unless last_build
106
+
107
+ source_id = last_build.source_id
108
+ source_ref = last_build.source_ref
109
+ else
110
+ # We only want to track information for whitelisted branches and branches
111
+ # with open Pull Requests.
112
+ unless config.non_pr_branch_ids.include?(build.trigger) || build.for_pr?
113
+ quit(not_pr_or_whitelisted_branch_message(config))
114
+ end
115
+ source_id = build.source_id
116
+ source_ref = build.trigger
117
+ end
118
+
119
+ # Update record for this project + branch/pr in DynamoDb even if the
120
+ # status hasn't changed, so the latest commit hash is stored.
121
+ history.write_entry(source_id) do |new_item|
122
+ cb_puts "Updating dynamo table #{config.dynamo_table} with: #{new_item}"
123
+ end
124
+
125
+ quit(no_status_diff_message(build)) if last_build&.status == build.status
126
+
127
+ slack_message = CodeBuildNotifier::SlackMessage.new(build, config, source_ref)
128
+ sender = CodeBuildNotifier::SlackSender.new(config)
129
+ sender.send(slack_message)
@@ -0,0 +1,50 @@
1
+ # codebuild-notifier
2
+ # Copyright © 2018 Adam Alboyadjian <adam@cassia.tech>
3
+ # Copyright © 2018 Vista Higher Learning, Inc.
4
+ #
5
+ # codebuild-notifier is free software: you can redistribute it
6
+ # and/or modify it under the terms of the GNU General Public
7
+ # License as published by the Free Software Foundation, either
8
+ # version 3 of the License, or (at your option) any later version.
9
+ #
10
+ # codebuild-notifier is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with codebuild-notifier. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ lib = File.expand_path('lib', __dir__)
19
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
20
+
21
+ require 'codebuild-notifier/version'
22
+
23
+ Gem::Specification.new do |spec|
24
+ spec.name = 'codebuild-notifier'
25
+ spec.version = CodeBuildNotifier::VERSION
26
+ spec.authors = ['VHL Ops Team']
27
+ spec.email = ['ops@vistahigherlearning.com']
28
+ spec.summary = 'Slack notifications for CodeBuild jobs.'
29
+ spec.description = 'CodeBuild Notifier tracks the past status of CI jobs ' \
30
+ 'run on AWS CodeBuild for each pr or whitelisted branch' \
31
+ 'for a given project, and sends slack notifications ' \
32
+ 'when a branch/pr changes build status.'
33
+ spec.homepage = 'https://github.com/vhl/codebuild-notifier'
34
+ spec.executables = ['update-build-status']
35
+
36
+ spec.files = `git ls-files -z`.split("\x0").reject do |file|
37
+ file.match(%r{^(test|spec|features)/})
38
+ end
39
+
40
+ spec.add_dependency 'activesupport', '> 2.0', '< 6.0'
41
+ spec.add_dependency 'aws-sdk-dynamodb', '~> 1.16'
42
+ spec.add_dependency 'aws-sdk-secretsmanager', '~> 1.19'
43
+ spec.add_dependency 'hashie', '> 1.0', '< 4.0'
44
+ spec.add_dependency 'slack-ruby-client', '~> 0.13'
45
+
46
+ spec.add_development_dependency 'rspec', '~> 3.8'
47
+ spec.add_development_dependency 'rubocop', '0.58.2'
48
+ spec.add_development_dependency 'rubocop-rspec', '1.30.0'
49
+ spec.add_development_dependency 'simplecov', '~> 0.16'
50
+ end
@@ -0,0 +1,24 @@
1
+ # codebuild-notifier
2
+ # Copyright © 2018 Adam Alboyadjian <adam@cassia.tech>
3
+ # Copyright © 2018 Vista Higher Learning, Inc.
4
+ #
5
+ # codebuild-notifier is free software: you can redistribute it
6
+ # and/or modify it under the terms of the GNU General Public
7
+ # License as published by the Free Software Foundation, either
8
+ # version 3 of the License, or (at your option) any later version.
9
+ #
10
+ # codebuild-notifier is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with codebuild-notifier. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'codebuild-notifier/config'
19
+ require 'codebuild-notifier/build_history'
20
+ require 'codebuild-notifier/current_build'
21
+ require 'codebuild-notifier/git'
22
+ require 'codebuild-notifier/slack_message'
23
+ require 'codebuild-notifier/slack_sender'
24
+ require 'codebuild-notifier/version'