standard_automation_library 0.2.1.pre.temp
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/standard_automation_library/api_clients/app_center.rb +104 -0
- data/lib/standard_automation_library/api_clients/bugsnag.rb +94 -0
- data/lib/standard_automation_library/api_clients/github.rb +371 -0
- data/lib/standard_automation_library/api_clients/jira.rb +326 -0
- data/lib/standard_automation_library/api_clients/pagerduty.rb +101 -0
- data/lib/standard_automation_library/api_clients/slack.rb +499 -0
- data/lib/standard_automation_library/danger/danger_jira.rb +169 -0
- data/lib/standard_automation_library/errors/slack_api_error.rb +6 -0
- data/lib/standard_automation_library/personnel/release_management_team.rb +85 -0
- data/lib/standard_automation_library/personnel/team.rb +41 -0
- data/lib/standard_automation_library/personnel/user.rb +68 -0
- data/lib/standard_automation_library/services/bugsnag_service.rb +251 -0
- data/lib/standard_automation_library/services/jira_service.rb +64 -0
- data/lib/standard_automation_library/services/merge_driver_service.rb +48 -0
- data/lib/standard_automation_library/services/mobile_tech_debt_logging_service.rb +176 -0
- data/lib/standard_automation_library/services/monorepo_platform_service.rb +18 -0
- data/lib/standard_automation_library/services/perf_tracker_logging_service.rb +87 -0
- data/lib/standard_automation_library/services/platform_service.rb +34 -0
- data/lib/standard_automation_library/services/repo_service.rb +17 -0
- data/lib/standard_automation_library/services/slack_service.rb +383 -0
- data/lib/standard_automation_library/util/automerge_configuration.rb +134 -0
- data/lib/standard_automation_library/util/bundler.rb +18 -0
- data/lib/standard_automation_library/util/datetime_helper.rb +23 -0
- data/lib/standard_automation_library/util/file_content.rb +15 -0
- data/lib/standard_automation_library/util/git.rb +235 -0
- data/lib/standard_automation_library/util/git_merge_error_message_cleaner.rb +27 -0
- data/lib/standard_automation_library/util/network.rb +39 -0
- data/lib/standard_automation_library/util/path_container.rb +17 -0
- data/lib/standard_automation_library/util/platform_picker.rb +150 -0
- data/lib/standard_automation_library/util/shared_constants.rb +27 -0
- data/lib/standard_automation_library/util/shell_helper.rb +54 -0
- data/lib/standard_automation_library/util/slack_constants.rb +40 -0
- data/lib/standard_automation_library/util/version.rb +31 -0
- data/lib/standard_automation_library/version.rb +5 -0
- data/lib/standard_automation_library.rb +8 -0
- metadata +296 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
require_relative 'team'
|
2
|
+
require_relative 'user'
|
3
|
+
|
4
|
+
# A team which automatically determines the members via querying pagerduty.
|
5
|
+
# Users are not computed at instantiation for performance reasons.
|
6
|
+
class ReleaseManagementTeam < Team
|
7
|
+
def initialize(
|
8
|
+
github_client,
|
9
|
+
slack_service,
|
10
|
+
pagerduty_client,
|
11
|
+
pagerduty_schedule_id,
|
12
|
+
pagerduty_cuttoff_hour = 10,
|
13
|
+
pagerduty_time_zone = 'America/Los_Angeles',
|
14
|
+
slack_user_group_id = nil,
|
15
|
+
error_reporting_channels: []
|
16
|
+
)
|
17
|
+
super(slack_user_group_id: slack_user_group_id)
|
18
|
+
@github_client = github_client
|
19
|
+
@slack_service = slack_service
|
20
|
+
@pagerduty_client = pagerduty_client
|
21
|
+
@pagerduty_schedule_id = pagerduty_schedule_id
|
22
|
+
@pagerduty_cuttoff_hour = pagerduty_cuttoff_hour
|
23
|
+
@pagerduty_time_zone = pagerduty_time_zone
|
24
|
+
@users = []
|
25
|
+
@error_reporting_channels = error_reporting_channels
|
26
|
+
end
|
27
|
+
|
28
|
+
def users
|
29
|
+
return @users unless @users.empty?
|
30
|
+
|
31
|
+
begin
|
32
|
+
# get name of the release manager from pagerduty
|
33
|
+
release_manager_name = @pagerduty_client.who_is_on_duty(
|
34
|
+
@pagerduty_schedule_id,
|
35
|
+
@pagerduty_cuttoff_hour,
|
36
|
+
time_zone: @pagerduty_time_zone
|
37
|
+
)
|
38
|
+
|
39
|
+
if release_manager_name
|
40
|
+
@users = [
|
41
|
+
User.new(
|
42
|
+
release_manager_name,
|
43
|
+
github_client: @github_client,
|
44
|
+
slack_service: @slack_service,
|
45
|
+
error_reporting_channels: @error_reporting_channels
|
46
|
+
)
|
47
|
+
]
|
48
|
+
end
|
49
|
+
rescue StandardError => e
|
50
|
+
send_slack_failure(e.message)
|
51
|
+
end
|
52
|
+
@users
|
53
|
+
end
|
54
|
+
|
55
|
+
# get user of the next release manager from pagerduty (two weeks from now)
|
56
|
+
def next_release_manager(team_domain)
|
57
|
+
next_release_manager_name = @pagerduty_client.who_is_on_duty(
|
58
|
+
@pagerduty_schedule_id,
|
59
|
+
@pagerduty_cuttoff_hour,
|
60
|
+
time: Time.now + (14 * 24 * 60 * 60),
|
61
|
+
time_zone: @pagerduty_time_zone
|
62
|
+
)
|
63
|
+
|
64
|
+
return nil if next_release_manager_name.nil?
|
65
|
+
|
66
|
+
users_param = [User.new(
|
67
|
+
next_release_manager_name,
|
68
|
+
github_client: @github_client,
|
69
|
+
slack_service: @slack_service,
|
70
|
+
error_reporting_channels: @error_reporting_channels
|
71
|
+
)]
|
72
|
+
render_slack_ids(team_domain, users_param)
|
73
|
+
rescue StandardError => e
|
74
|
+
send_slack_failure(e.message)
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def send_slack_failure(error_message)
|
79
|
+
message = "There was an error fetching data from PagerDuty\n#{error_message}\n#{ENV.fetch('BUILD_URL', nil)}"
|
80
|
+
@slack_service.send_message(
|
81
|
+
channel_strings: @error_reporting_channels,
|
82
|
+
message: message
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Represents a collection of users
|
2
|
+
class Team
|
3
|
+
def initialize(users: [], slack_user_group_id: nil)
|
4
|
+
# Don't access @users. Use the users method instead because a subclass may overwrite it.
|
5
|
+
@users = users
|
6
|
+
@slack_user_group_id = slack_user_group_id
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :users
|
10
|
+
|
11
|
+
attr_accessor :slack_user_group_id
|
12
|
+
|
13
|
+
def names(users_param = nil)
|
14
|
+
users_to_use = users_param || users
|
15
|
+
users_to_use.map(&:name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def github_ids(users_param = nil)
|
19
|
+
users_to_use = users_param || users
|
20
|
+
users_to_use.map(&:github_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
def slack_user_id_hashes(users_param = nil)
|
24
|
+
users_to_use = users_param || users
|
25
|
+
users_to_use.map(&:slack_user_id_hash)
|
26
|
+
end
|
27
|
+
|
28
|
+
def slack_ids(team_domain, users_param = nil)
|
29
|
+
slack_user_id_hashes(users_param).map { |id_map| id_map[team_domain] }
|
30
|
+
end
|
31
|
+
|
32
|
+
def render_slack_ids(team_domain, users_param = nil)
|
33
|
+
slack_ids(team_domain, users_param).compact.map do |slack_id|
|
34
|
+
"<@#{slack_id}>"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def empty?
|
39
|
+
users.empty?
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Holds user id information for various 3rd party services
|
2
|
+
# If services are supplied to compute the service usernames they
|
3
|
+
# should only be used to provide just in time computation. No information
|
4
|
+
# should be computed at instantiation.
|
5
|
+
class User
|
6
|
+
def initialize(
|
7
|
+
name,
|
8
|
+
github_id: nil,
|
9
|
+
slack_user_id_hash: {},
|
10
|
+
github_client: nil,
|
11
|
+
slack_service: nil,
|
12
|
+
error_reporting_channels: []
|
13
|
+
)
|
14
|
+
@name = name
|
15
|
+
@github_id = github_id
|
16
|
+
@slack_user_id_hash = slack_user_id_hash
|
17
|
+
@github_client = github_client
|
18
|
+
@slack_service = slack_service
|
19
|
+
@error_reporting_channels = error_reporting_channels
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_accessor :name
|
23
|
+
|
24
|
+
def github_id
|
25
|
+
return @github_id if @github_client.nil?
|
26
|
+
|
27
|
+
@github_id = @github_client.find_user_id(@name)
|
28
|
+
if @github_id.nil?
|
29
|
+
message = "Unable to resolve Github id for #{@name}. Ensure their name in Github matches #{@name}. " \
|
30
|
+
'The name field can be found by going to Settings > Profile in Github.'
|
31
|
+
send_error_message(message, true)
|
32
|
+
end
|
33
|
+
@github_id
|
34
|
+
end
|
35
|
+
|
36
|
+
def slack_user_id_hash
|
37
|
+
unless @slack_service.nil?
|
38
|
+
user_hash = @slack_service.user_hash([@name])
|
39
|
+
user_hash.each do |key, value|
|
40
|
+
user_id = value[0]
|
41
|
+
@slack_user_id_hash[key] = user_id unless user_id.nil?
|
42
|
+
end
|
43
|
+
if @slack_user_id_hash.empty?
|
44
|
+
message = "Unable to resolve slack user id for User object with name #{name}. " \
|
45
|
+
'Ensure the name being searched for matches their name in slack.'
|
46
|
+
send_error_message(message, false)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@slack_user_id_hash
|
50
|
+
end
|
51
|
+
|
52
|
+
def send_error_message(message, tag_user)
|
53
|
+
return if @slack_service.nil? || @error_reporting_channels.empty?
|
54
|
+
|
55
|
+
slack_client = @slack_service.slack_client
|
56
|
+
if tag_user
|
57
|
+
user_id = slack_user_id_hash[slack_client.team_domain]
|
58
|
+
message = if user_id
|
59
|
+
"<@#{user_id}> #{message}"
|
60
|
+
else
|
61
|
+
"<!here> #{message}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
@slack_service.with_multiple_channels(@error_reporting_channels) do |channel_string|
|
65
|
+
slack_client.send_message(channel_name: channel_string, message: message)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
require_relative '../api_clients/bugsnag'
|
2
|
+
require_relative '../util/slack_constants'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
# Service for querying information from Bugsnag
|
6
|
+
class BugsnagService
|
7
|
+
include SlackConstants
|
8
|
+
|
9
|
+
STABILITY_ABOVE_TARGET = (99.9..100).freeze
|
10
|
+
STABILITY_BELOW_TARGET = (99.5...99.9).freeze
|
11
|
+
|
12
|
+
def initialize(
|
13
|
+
auth_token,
|
14
|
+
platform,
|
15
|
+
bugsnag_mapping_file_path,
|
16
|
+
slack_service,
|
17
|
+
slack_health_channels,
|
18
|
+
slack_release_channels,
|
19
|
+
release_manager_team
|
20
|
+
)
|
21
|
+
@client = BugsnagClient.new(platform, auth_token)
|
22
|
+
@slack_service = slack_service
|
23
|
+
@slack_health_channels = slack_health_channels
|
24
|
+
@slack_release_channels = slack_release_channels
|
25
|
+
@bugsnag_mapping_file_path_container = PathContainer.new([bugsnag_mapping_file_path])
|
26
|
+
@release_manager_team = release_manager_team
|
27
|
+
@platform = platform
|
28
|
+
end
|
29
|
+
|
30
|
+
## Finds the top open and unhandled errors for the provided app_version and posts them to Slack.
|
31
|
+
def crash_report(
|
32
|
+
app_version,
|
33
|
+
count,
|
34
|
+
days
|
35
|
+
)
|
36
|
+
user_mappings = YAML.load_file(@bugsnag_mapping_file_path_container&.path)
|
37
|
+
formatted_errors = []
|
38
|
+
assigned_collaborators = []
|
39
|
+
@client.top_errors(
|
40
|
+
app_version,
|
41
|
+
count: count,
|
42
|
+
events_since: "#{days}d"
|
43
|
+
).each do |error|
|
44
|
+
details = @client.error_details(error.id)
|
45
|
+
possible_team = find_team(user_mappings, details)
|
46
|
+
error_message, collaborator = make_slack_block_from_bugsnag_error(details, possible_team)
|
47
|
+
formatted_errors.append(error_message)
|
48
|
+
assigned_collaborators.append(collaborator)
|
49
|
+
end
|
50
|
+
initial_message, release_manager_slack_id = make_initial_message(app_version)
|
51
|
+
assigned_collaborators.append(release_manager_slack_id)
|
52
|
+
@slack_service.send_message_in_thread(
|
53
|
+
@slack_release_channels,
|
54
|
+
initial_message,
|
55
|
+
formatted_errors,
|
56
|
+
assigned_collaborators.compact
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def platform_emoji
|
61
|
+
case @platform
|
62
|
+
when IOS
|
63
|
+
IOS_SLACK_EMOJI
|
64
|
+
when ANDROID
|
65
|
+
ANDROID_SLACK_EMOJI
|
66
|
+
else
|
67
|
+
''
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
## Makes the initial thread message, contains the platform emojis and tags the current release manager
|
72
|
+
def make_initial_message(app_version)
|
73
|
+
release_manager_slack_id = nil
|
74
|
+
|
75
|
+
if release_manager_slack_id.nil?
|
76
|
+
begin
|
77
|
+
release_manager_slack_id = @release_manager_team.slack_ids(@slack_service.slack_client.team_domain).compact[0]
|
78
|
+
rescue RuntimeError
|
79
|
+
# Ignored
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
formatted_manager_id = release_manager_slack_id ? "<@#{release_manager_slack_id}>" : ''
|
84
|
+
["#{platform_emoji} :mega: ACC #{@platform} version " \
|
85
|
+
"*#{app_version}* top crashes :mega: #{platform_emoji}\n" \
|
86
|
+
"#{formatted_manager_id} please link Bugsnags to Jira tickets and create " \
|
87
|
+
'Jira tickets if an appropriate ticket does not exist already, per ' \
|
88
|
+
'<https://wiki.autodesk.com/display/SCFICOE/Critical+Crash+Rate+Playbook|Crash Rate Playbook>. ' \
|
89
|
+
':point_down: :bc-thread:', release_manager_slack_id]
|
90
|
+
end
|
91
|
+
|
92
|
+
## Tries to find a team from the mappings file, that has a keyword contained in the error's stack trace
|
93
|
+
def find_team(user_mappings, details)
|
94
|
+
extras = details.grouping_fields
|
95
|
+
possible_team = nil
|
96
|
+
## Here we'll have the file and line that generated the error
|
97
|
+
unless extras&.file.nil?
|
98
|
+
error_file = extras.file
|
99
|
+
## search based on the defined keywords for each team
|
100
|
+
possible_team = user_mappings.select { |t| t['keywords'].any? { |k| error_file.downcase.include? k } }[0]
|
101
|
+
end
|
102
|
+
possible_team
|
103
|
+
end
|
104
|
+
|
105
|
+
## Generates the required Slack message in blocks format to be replied on a slack thread
|
106
|
+
def make_slack_block_from_bugsnag_error(details, team)
|
107
|
+
linked_issue = details.linked_issues&.first
|
108
|
+
message_emoji = details.error_class == 'ANR' ? ':hourglass:' : ':boom:'
|
109
|
+
|
110
|
+
header_block = {
|
111
|
+
type: 'section',
|
112
|
+
text: {
|
113
|
+
type: 'mrkdwn',
|
114
|
+
text: "#{message_emoji} *#{details.error_class} on #{details.context}*\n#{details.message}"
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
button_block = {
|
119
|
+
type: 'actions',
|
120
|
+
elements: [
|
121
|
+
{
|
122
|
+
type: 'button',
|
123
|
+
text: {
|
124
|
+
type: 'plain_text',
|
125
|
+
emoji: true,
|
126
|
+
text: 'View in BugSnag'
|
127
|
+
},
|
128
|
+
url: "https://app.bugsnag.com/plangrid/#{@platform.downcase}/errors/#{details.id}"
|
129
|
+
},
|
130
|
+
{
|
131
|
+
type: 'button',
|
132
|
+
text: {
|
133
|
+
type: 'plain_text',
|
134
|
+
emoji: true,
|
135
|
+
text: linked_issue ? 'View in Jira' : 'UNLINKED'
|
136
|
+
},
|
137
|
+
url: linked_issue ? linked_issue.url : 'https://jira.autodesk.com/secure/Dashboard.jspa'
|
138
|
+
}
|
139
|
+
]
|
140
|
+
}
|
141
|
+
|
142
|
+
## If no ticket is linked, then highlight the button
|
143
|
+
button_block[:elements][1][:style] = 'danger' unless linked_issue
|
144
|
+
|
145
|
+
field_block = {
|
146
|
+
type: 'section',
|
147
|
+
fields: [
|
148
|
+
{
|
149
|
+
type: 'mrkdwn',
|
150
|
+
text: "*Occurrences:*\n#{details.events} times"
|
151
|
+
},
|
152
|
+
{
|
153
|
+
type: 'mrkdwn',
|
154
|
+
text: "*Affected users:*\n#{details.users}"
|
155
|
+
}
|
156
|
+
]
|
157
|
+
}
|
158
|
+
|
159
|
+
if details.grouping_fields
|
160
|
+
error_type = details.grouping_fields.errorClass
|
161
|
+
error_file = details.grouping_fields.file
|
162
|
+
error_place = details.grouping_fields.lineNumber
|
163
|
+
extra_info = "*Stack details:*\n #{error_type} at `#{error_file}:#{error_place}`"
|
164
|
+
field_block[:fields].append({ type: 'mrkdwn', text: extra_info })
|
165
|
+
end
|
166
|
+
|
167
|
+
collaborator = nil
|
168
|
+
if team
|
169
|
+
collaborator = team['collaborators'].sample
|
170
|
+
## Tagging the possible team associated with this error
|
171
|
+
reason = "Might it be related to the *#{team['team']}* team <@#{collaborator}>?"
|
172
|
+
field_block[:fields].append({ type: 'mrkdwn', text: reason })
|
173
|
+
end
|
174
|
+
|
175
|
+
[[header_block, button_block, field_block], collaborator]
|
176
|
+
end
|
177
|
+
|
178
|
+
def stability_report(current_version)
|
179
|
+
release_groups = @client.release_groups(current_version)
|
180
|
+
|
181
|
+
message = "#{platform_emoji} :mega: ACC #{@platform} " \
|
182
|
+
"<https://app.bugsnag.com/plangrid/#{@platform.downcase}/releases" \
|
183
|
+
"?release_stage=production&releases=top|Stability Report> :mega: #{platform_emoji}\n"
|
184
|
+
|
185
|
+
release_groups.each do |release_group|
|
186
|
+
session_stability_change = release_group[:session_stability_change] || 0.00
|
187
|
+
change_emoji = session_stability_change.negative? ? ':red_arrow_decrease:' : ':green_arrow_increase:'
|
188
|
+
change_sign = session_stability_change.negative? ? ' -' : '+'
|
189
|
+
rating_emoji = case release_group[:session_stability]
|
190
|
+
when STABILITY_ABOVE_TARGET then ':hds_up:'
|
191
|
+
when STABILITY_BELOW_TARGET then ':hds_degraded:'
|
192
|
+
else ':hds_down:'
|
193
|
+
end
|
194
|
+
|
195
|
+
message += "*#{release_group[:version]}* #{rating_emoji} " \
|
196
|
+
"#{format('%.2f%%', release_group[:session_stability].round(2))} | " \
|
197
|
+
"#{change_sign}#{format('%.2f%%', session_stability_change.round(2).abs)} #{change_emoji} | " \
|
198
|
+
"Adoption: #{format('%.2f%%', release_group[:adoption_rate].round(2))}\n"
|
199
|
+
end
|
200
|
+
|
201
|
+
@slack_service.send_message_in_thread(
|
202
|
+
@slack_release_channels,
|
203
|
+
message,
|
204
|
+
[],
|
205
|
+
[]
|
206
|
+
)
|
207
|
+
end
|
208
|
+
|
209
|
+
def check_stability_drop(current_version, release_manager_team, platform, drop_threshold, minimum_adoption,
|
210
|
+
use_test_channel)
|
211
|
+
release_group = @client.release_groups(current_version).first
|
212
|
+
session_stability_change = release_group[:session_stability_change] || 0.00
|
213
|
+
adoption_rate = release_group[:adoption_rate]
|
214
|
+
puts "#{platform} stability data - Stability Change: #{session_stability_change} Adoption Rate: #{adoption_rate}"
|
215
|
+
return if session_stability_change > drop_threshold.to_f || adoption_rate < minimum_adoption.to_f
|
216
|
+
|
217
|
+
slack_client = @slack_service.slack_client
|
218
|
+
|
219
|
+
current_manager_user_id = release_manager_team.render_slack_ids(slack_client.team_domain).join(' ').strip
|
220
|
+
bugsnag_url = "https://app.bugsnag.com/plangrid/#{platform.downcase}/errors/?filters[error.status]=open" \
|
221
|
+
"&filters[event.since]=30d&filters[release.seen_in]=#{current_version}&sort=last_seen"
|
222
|
+
|
223
|
+
message_identifier = "detected in version #{release_group[:version]}."
|
224
|
+
|
225
|
+
message = "Elevated crash rate (Crash-free: -#{format('%.2f%%', session_stability_change.round(2).abs)} " \
|
226
|
+
":red_arrow_decrease:) #{message_identifier} Has anyone shipped code to production lately " \
|
227
|
+
"that could result in crashes? :face_with_monocle:\n#{current_manager_user_id} " \
|
228
|
+
"please investigate in <#{bugsnag_url}|Bugsnag>."
|
229
|
+
|
230
|
+
channels_to_use = use_test_channel ? @slack_health_channels : @slack_release_channels
|
231
|
+
|
232
|
+
@slack_service.with_multiple_channels(channels_to_use) do |channel_name|
|
233
|
+
timestamp = slack_client.old_message_timestamp(
|
234
|
+
channel_name,
|
235
|
+
message_identifier,
|
236
|
+
options: { limit: 500 }
|
237
|
+
)
|
238
|
+
|
239
|
+
unless timestamp.nil?
|
240
|
+
puts "Message found for version #{release_group[:version]} in channel #{channel_name}. " \
|
241
|
+
'Not posting again.'
|
242
|
+
next
|
243
|
+
end
|
244
|
+
|
245
|
+
@slack_service.send_message(
|
246
|
+
channel_strings: [channel_name],
|
247
|
+
message: message
|
248
|
+
)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative '../api_clients/jira'
|
2
|
+
|
3
|
+
# Service for all things JIRA related
|
4
|
+
class JiraService
|
5
|
+
def initialize(
|
6
|
+
username,
|
7
|
+
api_key,
|
8
|
+
site: 'https://jira.autodesk.com',
|
9
|
+
is_dry_run: false,
|
10
|
+
whitelisted_project_keys: []
|
11
|
+
)
|
12
|
+
@username = username
|
13
|
+
@jira = Jira.new(@username, api_key, site: site, is_dry_run: is_dry_run)
|
14
|
+
@whitelisted_project_keys = whitelisted_project_keys
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :whitelisted_project_keys
|
18
|
+
|
19
|
+
attr_reader :username
|
20
|
+
|
21
|
+
# Get a JiraIssue representing the supplied issue key
|
22
|
+
# Will be nil if the issue does not exist or the credentials are wrong.
|
23
|
+
def issue(issue_key)
|
24
|
+
@jira.issue(issue_key)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get a JiraProject representing the supplied project key
|
28
|
+
# Will be nil if the project does not exist or the credentials are wrong.
|
29
|
+
def project(project_key)
|
30
|
+
@jira.project(project_key)
|
31
|
+
end
|
32
|
+
|
33
|
+
def all_project_keys
|
34
|
+
@jira.all_project_keys
|
35
|
+
end
|
36
|
+
|
37
|
+
def filtered_project_keys
|
38
|
+
project_keys = all_project_keys
|
39
|
+
project_keys.select! { |item| @whitelisted_project_keys.include?(item) } unless @whitelisted_project_keys.empty?
|
40
|
+
project_keys
|
41
|
+
end
|
42
|
+
|
43
|
+
def find_issue_references_in_text(text, whitelisted_project_keys = filtered_project_keys)
|
44
|
+
project_list_pattern = '[A-Z]+'
|
45
|
+
project_list_pattern = whitelisted_project_keys.to_a.join('|') unless whitelisted_project_keys.empty?
|
46
|
+
# Surround project key or statement with parens and make non-capturing group with ?:
|
47
|
+
regex_pattern = /(?:#{project_list_pattern})-\d+/
|
48
|
+
text.scan(regex_pattern).flatten.uniq
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_issue_references_in_text_list(text_list, whitelisted_project_keys = filtered_project_keys)
|
52
|
+
text_list.flat_map do |text|
|
53
|
+
find_issue_references_in_text(text, whitelisted_project_keys)
|
54
|
+
end.uniq.compact
|
55
|
+
end
|
56
|
+
|
57
|
+
def comment_on_all_unverified_tickets_for_version(fix_version, release_date)
|
58
|
+
@jira.comment_on_all_unverified_tickets_for_version(fix_version, release_date)
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_all_unverified_tickets_for_version(fix_version, platform = nil)
|
62
|
+
@jira.all_unverified_tickets(fix_version, platform)
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative '../util/git'
|
2
|
+
|
3
|
+
# Class for operating on git merge drivers.
|
4
|
+
class MergeDriverService
|
5
|
+
def initialize(merge_driver_map)
|
6
|
+
@merge_driver_map = merge_driver_map
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :merge_driver_map
|
10
|
+
|
11
|
+
# Modify the git config to "install" the merge drivers
|
12
|
+
def install_merge_drivers
|
13
|
+
@merge_driver_map.each do |key, value|
|
14
|
+
Git.config_command("#{merge_driver_name_command(key)} \"#{value['name']}\"")
|
15
|
+
Git.config_command("#{merge_driver_driver_command(key)} \"#{value['command']}\"")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Remove the merge drivers if they are configured
|
20
|
+
# Leaving these around can cause problems with the 'checkout scm'
|
21
|
+
# step in the jenkinsfile.
|
22
|
+
def uninstall_merge_drivers
|
23
|
+
@merge_driver_map.each_key do |key|
|
24
|
+
Git.config_command("--remove-section merge.#{key}") if Git.merge_driver_installed?(key)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def merge_driver_name_command(merge_driver_key)
|
29
|
+
"merge.#{merge_driver_key}.name"
|
30
|
+
end
|
31
|
+
|
32
|
+
def merge_driver_driver_command(merge_driver_key)
|
33
|
+
"merge.#{merge_driver_key}.driver"
|
34
|
+
end
|
35
|
+
|
36
|
+
def +(other)
|
37
|
+
other_driver_map = other.merge_driver_map
|
38
|
+
other_driver_map.each do |other_key, other_value|
|
39
|
+
current_value = @merge_driver_map[other_key]
|
40
|
+
if !current_value.nil? && other_value != current_value
|
41
|
+
raise "Attempting to add existing merge driver key '#{other_key}' with new value: #{other_value}"
|
42
|
+
end
|
43
|
+
|
44
|
+
@merge_driver_map[other_key] = other_value
|
45
|
+
end
|
46
|
+
self
|
47
|
+
end
|
48
|
+
end
|