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.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/lib/standard_automation_library/api_clients/app_center.rb +104 -0
  3. data/lib/standard_automation_library/api_clients/bugsnag.rb +94 -0
  4. data/lib/standard_automation_library/api_clients/github.rb +371 -0
  5. data/lib/standard_automation_library/api_clients/jira.rb +326 -0
  6. data/lib/standard_automation_library/api_clients/pagerduty.rb +101 -0
  7. data/lib/standard_automation_library/api_clients/slack.rb +499 -0
  8. data/lib/standard_automation_library/danger/danger_jira.rb +169 -0
  9. data/lib/standard_automation_library/errors/slack_api_error.rb +6 -0
  10. data/lib/standard_automation_library/personnel/release_management_team.rb +85 -0
  11. data/lib/standard_automation_library/personnel/team.rb +41 -0
  12. data/lib/standard_automation_library/personnel/user.rb +68 -0
  13. data/lib/standard_automation_library/services/bugsnag_service.rb +251 -0
  14. data/lib/standard_automation_library/services/jira_service.rb +64 -0
  15. data/lib/standard_automation_library/services/merge_driver_service.rb +48 -0
  16. data/lib/standard_automation_library/services/mobile_tech_debt_logging_service.rb +176 -0
  17. data/lib/standard_automation_library/services/monorepo_platform_service.rb +18 -0
  18. data/lib/standard_automation_library/services/perf_tracker_logging_service.rb +87 -0
  19. data/lib/standard_automation_library/services/platform_service.rb +34 -0
  20. data/lib/standard_automation_library/services/repo_service.rb +17 -0
  21. data/lib/standard_automation_library/services/slack_service.rb +383 -0
  22. data/lib/standard_automation_library/util/automerge_configuration.rb +134 -0
  23. data/lib/standard_automation_library/util/bundler.rb +18 -0
  24. data/lib/standard_automation_library/util/datetime_helper.rb +23 -0
  25. data/lib/standard_automation_library/util/file_content.rb +15 -0
  26. data/lib/standard_automation_library/util/git.rb +235 -0
  27. data/lib/standard_automation_library/util/git_merge_error_message_cleaner.rb +27 -0
  28. data/lib/standard_automation_library/util/network.rb +39 -0
  29. data/lib/standard_automation_library/util/path_container.rb +17 -0
  30. data/lib/standard_automation_library/util/platform_picker.rb +150 -0
  31. data/lib/standard_automation_library/util/shared_constants.rb +27 -0
  32. data/lib/standard_automation_library/util/shell_helper.rb +54 -0
  33. data/lib/standard_automation_library/util/slack_constants.rb +40 -0
  34. data/lib/standard_automation_library/util/version.rb +31 -0
  35. data/lib/standard_automation_library/version.rb +5 -0
  36. data/lib/standard_automation_library.rb +8 -0
  37. metadata +296 -0
@@ -0,0 +1,235 @@
1
+ require_relative 'shell_helper'
2
+
3
+ # A collection of git commands
4
+ # Keep the method list alphabetized
5
+ module Git
6
+ @repo_root_path = nil
7
+
8
+ def self.ancestor?(ancestor_git_ref, child_git_ref)
9
+ git("merge-base --is-ancestor #{ancestor_git_ref} #{child_git_ref}")
10
+ true # exit status is 0 if ancestor_git_ref is an ancestor of child_git_ref
11
+ rescue StandardError
12
+ false
13
+ end
14
+
15
+ def self.branch_exists?(branch_name)
16
+ rev_parse("--quiet --verify #{branch_name}")
17
+ true # exit status of zero means branch exists
18
+ rescue StandardError
19
+ false
20
+ end
21
+
22
+ def self.checkout(arg_string)
23
+ git("checkout #{arg_string}")
24
+ end
25
+
26
+ def self.checkout_new_branch(branch_name)
27
+ checkout("-b #{branch_name}")
28
+ end
29
+
30
+ def self.clean(arg_string = '')
31
+ git("clean #{arg_string}")
32
+ end
33
+
34
+ def self.clean_without_vendor_bundle(arg_string = '')
35
+ clean("-ffdx -e vendor -e .bundle #{arg_string}")
36
+ end
37
+
38
+ # TODO: Calling this method `config` results in a name conflict with the Git module in ruby-git.
39
+ def self.config_command(arg_string)
40
+ git("config #{arg_string}")
41
+ end
42
+
43
+ def self.commit(arg_string)
44
+ git("commit #{arg_string}")
45
+ end
46
+
47
+ def self.commit_author_email(git_ref = 'HEAD')
48
+ log("-1 --format=\"%ae\" #{git_ref}")
49
+ end
50
+
51
+ def self.commit_author_name(git_ref = 'HEAD')
52
+ log("-1 --format=\"%an\" #{git_ref}")
53
+ end
54
+
55
+ def self.commit_message(git_ref = 'HEAD')
56
+ log("-1 --format=\"%B\" #{git_ref}")
57
+ end
58
+
59
+ def self.commit_title(git_ref = 'HEAD')
60
+ log("-1 --format=\"%s\" #{git_ref}")
61
+ end
62
+
63
+ def self.commit_sha?(git_ref)
64
+ !git_ref.match(/^[a-f0-9]{9,40}$/).nil?
65
+ end
66
+
67
+ def self.commits_between(exclude_commit, include_commit)
68
+ rev_list("#{include_commit} ^#{exclude_commit}").split("\n").map(&:strip)
69
+ end
70
+
71
+ # Create a slack message payload containing all git related information
72
+ def self.create_message_payload(git_ref, remote_name = 'origin')
73
+ commit_or_branch_label = commit_sha?(git_ref) ? 'Commit' : 'Branch'
74
+ message_payload = { commit_or_branch_label => git_ref }
75
+ commit_hash = first_non_jenkins_commit(git_ref, remote_name)
76
+ message_payload['Git Author'] = commit_author_name(commit_hash)
77
+ message_payload['Commit Message'] = commit_message(commit_hash)
78
+ message_payload
79
+ end
80
+
81
+ def self.current_branch_name
82
+ rev_parse('--abbrev-ref HEAD')
83
+ end
84
+
85
+ # Returns the name of the current branch if there is one,
86
+ # otherwise return the current commit sha.
87
+ def self.current_branch_or_commit
88
+ git('symbolic-ref --short -q HEAD')
89
+ rescue StandardError
90
+ rev_parse('HEAD')
91
+ end
92
+
93
+ def self.current_commit_sha
94
+ log('-1 --format="%H"')
95
+ end
96
+
97
+ def self.current_commit_sha_short
98
+ log('-1 --format="%h"')
99
+ end
100
+
101
+ def self.delete_branch(branch_name)
102
+ git("branch -D #{branch_name}")
103
+ end
104
+
105
+ def self.diff?(arg_string = '')
106
+ git("diff --quiet #{arg_string}")
107
+ false # exit status 0 means that git diff --quiet reports no diff
108
+ rescue StandardError
109
+ true
110
+ end
111
+
112
+ def self.fetch(arg_string = '')
113
+ git("fetch #{arg_string}")
114
+ end
115
+
116
+ # Get the contents of a file from an arbitrary git ref.
117
+ # file_path must be specified from the top of the repository
118
+ # If no git ref is specified then just read the contents of
119
+ # the file on disk
120
+ def self.file_content(file_path, git_ref = nil)
121
+ if git_ref.nil?
122
+ File.read(File.join(repo_root_path, file_path))
123
+ else
124
+ git("show #{git_ref}:#{file_path}")
125
+ end
126
+ end
127
+
128
+ # When jenkins does a PR build it merges the target branch INTO the source branch so
129
+ # using git_ref~# will eventually get us the commit of the source branch of the PR.
130
+ def self.first_non_jenkins_commit(git_ref, remote_name = 'origin')
131
+ begin
132
+ commit_hash = rev_parse(git_ref)
133
+ rescue StandardError => e
134
+ puts e.message
135
+ commit_hash = rev_parse("#{remote_name}/#{git_ref}")
136
+ end
137
+
138
+ parent_count = 0
139
+ loop do
140
+ git_author = commit_author_name("#{commit_hash}~#{parent_count}")
141
+ break if git_author != 'Jenkins'
142
+
143
+ parent_count += 1
144
+ end
145
+ rev_parse("#{commit_hash}~#{parent_count}")
146
+ end
147
+
148
+ def self.git(arg_string)
149
+ ShellHelper.run("git #{arg_string}")
150
+ end
151
+
152
+ def self.local_branch_is_behind_remote?(local_branch_name, remote_name = 'origin')
153
+ rev_parse(local_branch_name) != rev_parse("#{remote_name}/#{local_branch_name}")
154
+ end
155
+
156
+ def self.log(arg_string)
157
+ git("log #{arg_string}")
158
+ end
159
+
160
+ def self.merge(arg_string)
161
+ git("merge #{arg_string}")
162
+ end
163
+
164
+ def self.merge_abort(fail_silently: true)
165
+ merge('--abort')
166
+ rescue StandardError => e
167
+ raise e unless fail_silently
168
+ end
169
+
170
+ def self.merge_driver_installed?(key)
171
+ config_command("--get-regexp \"^merge.#{key}\"")
172
+ true # exit status 0 means merge driver is installed
173
+ rescue StandardError
174
+ false
175
+ end
176
+
177
+ # We prefer our own implementation rather than the fastlane one so that we can dry run.
178
+ def self.push_to_remote(local_branch, is_dry_run, remote_branch: nil, force: false, tags: false, remote: 'origin')
179
+ remote_branch ||= local_branch
180
+
181
+ command = [
182
+ 'push',
183
+ remote,
184
+ "#{local_branch.shellescape}:#{remote_branch.shellescape}"
185
+ ]
186
+
187
+ # optionally add the tags component
188
+ command << '--tags' if tags
189
+
190
+ # optionally add the force component
191
+ command << '--force' if force
192
+
193
+ if is_dry_run
194
+ puts "Would have pushed branch: #{local_branch} to remote branch: #{remote}/#{remote_branch} " \
195
+ "with force: #{force} and tags: #{tags}"
196
+ else
197
+ git(command.join(' '))
198
+ end
199
+ end
200
+
201
+ def self.return_to_current_ref(git_clean_args = '', &_blk)
202
+ current_ref = current_branch_or_commit
203
+ begin
204
+ yield
205
+ ensure
206
+ clean_without_vendor_bundle(git_clean_args)
207
+ checkout("#{current_ref} -f")
208
+ end
209
+ end
210
+
211
+ def self.reset(arg_string)
212
+ git("reset #{arg_string}")
213
+ end
214
+
215
+ def self.reset_hard(arg_string = '')
216
+ reset("--hard #{arg_string}")
217
+ end
218
+
219
+ def self.reset_hard_if_refs_not_equal(current_ref, reset_ref)
220
+ reset_hard(reset_ref) if rev_parse(current_ref) != rev_parse(reset_ref)
221
+ end
222
+
223
+ def self.repo_root_path
224
+ @repo_root_path = rev_parse('--show-toplevel') if @repo_root_path.nil?
225
+ @repo_root_path
226
+ end
227
+
228
+ def self.rev_list(arg_string)
229
+ git("rev-list #{arg_string}")
230
+ end
231
+
232
+ def self.rev_parse(arg_string)
233
+ git("rev-parse #{arg_string}")
234
+ end
235
+ end
@@ -0,0 +1,27 @@
1
+ # Class for cleaning up git merge error messages and turning them into
2
+ # a message suitable for sending via slack. If different message processing
3
+ # is desired then subclass this and override the clean_message method.
4
+ class GitMergeErrorMessageCleaner
5
+ MERGE_CONFLICT_PREFIX = 'CONFLICT (content): Merge conflict in'.freeze
6
+
7
+ def clean_message(error_message)
8
+ merge_conflicts = []
9
+ other_messages = []
10
+ error_message.split("\n").each do |line|
11
+ merge_conflicts << line.gsub(MERGE_CONFLICT_PREFIX, '').strip if line.include?(MERGE_CONFLICT_PREFIX)
12
+ end
13
+
14
+ return error_message if merge_conflicts.empty? && other_messages.empty?
15
+
16
+ new_message = ''
17
+ unless merge_conflicts.empty?
18
+ new_message = "*Merge Conflicts*\n#{merge_conflicts.sort.map { |conflict_path| "• #{conflict_path}\n" }.join}"
19
+ end
20
+
21
+ unless other_messages.empty?
22
+ new_message += "\n*Other Messages*\n#{other_messages.map { |other_message| "• #{other_message}\n" }.join}"
23
+ end
24
+
25
+ new_message.strip
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ require 'net/http'
2
+ require 'json'
3
+ require 'open-uri'
4
+
5
+ # Functions for performing network requests
6
+ module Network
7
+ def self.get(url, headers)
8
+ uri = URI(url)
9
+ http = Net::HTTP.new(uri.host, uri.port)
10
+ http.use_ssl = true
11
+ request = Net::HTTP::Get.new(uri.path, headers)
12
+ http.request(request)
13
+ end
14
+
15
+ def self.post(url, headers, request_body)
16
+ uri = URI(url)
17
+ http = Net::HTTP.new(uri.host, uri.port)
18
+ http.use_ssl = true
19
+ request = Net::HTTP::Post.new(
20
+ uri.path,
21
+ headers
22
+ )
23
+
24
+ request_body = request_body.to_json if request_body.is_a?(Hash)
25
+
26
+ request.body = request_body
27
+ http.request(request)
28
+ end
29
+
30
+ def self.json_response(url, headers = {})
31
+ uri = URI.parse(url)
32
+ JSON.parse(uri.open(headers).read)
33
+ end
34
+
35
+ def self.download_file(url, output_path, headers = {})
36
+ uri = URI.parse(url)
37
+ File.write(output_path, uri.open(headers).read)
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ # An object to contain a collections of file paths.
2
+ # This could be useful to specify paths to a file
3
+ # that is known to switch places based on checking
4
+ # out different git branches. Calling the path function
5
+ # will return the first path that exists on the file system.
6
+ class PathContainer
7
+ def initialize(paths)
8
+ @paths = paths
9
+ end
10
+
11
+ def path
12
+ @paths.each do |path|
13
+ return path if File.exist?(path)
14
+ end
15
+ nil
16
+ end
17
+ end
@@ -0,0 +1,150 @@
1
+ require_relative '../api_clients/github'
2
+ require_relative 'shared_constants'
3
+
4
+ # Functionality for filtering a list of path prefixes based on which
5
+ # paths have been modified between two git commits. Allows us to choose
6
+ # which platforms to build
7
+ module PlatformPicker
8
+ COMMON_BUILD_FILES = %w[
9
+ AndroidCommon.gradle
10
+ build.gradle.kts
11
+ Gemfile
12
+ Gemfile.lock
13
+ gradle.properties
14
+ gradlew
15
+ gradlew.bat
16
+ Jenkinsfile.forge
17
+ settings.gradle.kts
18
+ ].freeze
19
+
20
+ COMMON_BUILD_DIRECTORIES = %w[
21
+ buildTools/
22
+ common/
23
+ deps/
24
+ gradle/
25
+ ].freeze
26
+
27
+ # Determines the list of platforms that require building.
28
+ #
29
+ class PlatformDetector
30
+ # @param [String] recent_commit - A required commit hash for comparing the base branch against to.
31
+ # @param [String] older_commit - An optional commit hash of an older commit.
32
+ # @param [String] pr_number - An optional PR# for label checking.
33
+ # @param [String] repo_name - A required git repo name path.
34
+ # @param [Array] platform - A required platforms array.
35
+ # @param [TrueClass] dry_run - A boolean indicating if a dry_run should be performed.
36
+ # @param [TrueClass] verbose - A boolean indicating if verbose logging should be enabled. Helps to determine if
37
+ # certain labels were matched.
38
+ def initialize(recent_commit:, older_commit:, pr_number:, repo_name:, platforms:, dry_run:, verbose:)
39
+ @recent_commit = recent_commit
40
+ @older_commit = older_commit
41
+ @pr_number = pr_number
42
+ @platforms = platforms
43
+ @verbose = verbose || false
44
+ @github_client = GitHub.new(
45
+ repo_name,
46
+ ENV.fetch('IOS_GITHUB_API_TOKEN', nil),
47
+ api_server_base_url: SharedConstants::GITHUB_API_BASE_URL,
48
+ is_dry_run: dry_run || false
49
+ )
50
+ end
51
+
52
+ def detect
53
+ log 'Determining platforms to build...'
54
+ if @pr_number
55
+ labels_to_skip = [SharedConstants::VERSION_BUMP_LABEL, SharedConstants::AUTOGENERATED_LABEL]
56
+ log "Fetching git labels for for PR ##{@pr_number}..."
57
+ has_valid_labels = @github_client.pull_request_has_labels?(pr_number: @pr_number, labels: labels_to_skip)
58
+ if has_valid_labels
59
+ log "Detected #{labels_to_skip.join(', ')} labels.
60
+ \Skipping git revision checking as only version file(s) were changed."
61
+ log 'Platforms requiring build: []'
62
+ return []
63
+ else
64
+ log 'No labels matched'
65
+ end
66
+ end
67
+
68
+ platforms = self.class.platforms_to_build(@older_commit, @recent_commit, @platforms)
69
+ log "Platforms requiring build: #{platforms}"
70
+ platforms
71
+ end
72
+
73
+ def log(string)
74
+ puts string if @verbose
75
+ end
76
+
77
+ def self.check_gradle_build_files(earlier_commit, later_commit, platforms)
78
+ file_paths = `git diff --name-only #{earlier_commit} #{later_commit}`.strip.split("\n")
79
+ does_modify_common_build_files = false
80
+ common_platforms_to_build = []
81
+ file_paths.each do |file_path|
82
+ if COMMON_BUILD_FILES.include? file_path
83
+ does_modify_common_build_files = true
84
+ break
85
+ end
86
+
87
+ next unless COMMON_BUILD_DIRECTORIES.any? { |dir| file_path.start_with?(dir) }
88
+
89
+ common_platforms_to_build = if deps_contains_one_analytics_lib_change(earlier_commit, later_commit)
90
+ %w[android]
91
+ else
92
+ platforms
93
+ end
94
+ break
95
+ end
96
+ does_modify_common_build_files ? platforms : common_platforms_to_build
97
+ end
98
+
99
+ def self.platforms_to_build(earlier_commit, later_commit, platforms)
100
+ platform_path_prefixes = platforms.map { |platform| "#{platform}/" }
101
+ prefixes_to_build = filter_path_prefixes(platform_path_prefixes, earlier_commit, later_commit)
102
+ platforms_to_build_by_prefix = prefixes_to_build.map { |prefix| prefix.delete_suffix('/') }
103
+ platforms_to_build_by_gradle_build_files = check_gradle_build_files(earlier_commit, later_commit, platforms)
104
+ (platforms_to_build_by_prefix + platforms_to_build_by_gradle_build_files).uniq
105
+ end
106
+
107
+ def self.filter_path_prefixes(path_prefixes, earlier_commit, later_commit)
108
+ file_paths = `git diff --name-only #{earlier_commit} #{later_commit}`.strip.split("\n")
109
+ path_prefixes.select do |prefix|
110
+ should_select = false
111
+ file_paths.each do |file_path|
112
+ if file_path.start_with?(prefix)
113
+ should_select = true
114
+ break
115
+ end
116
+ end
117
+ should_select
118
+ end
119
+ end
120
+
121
+ # Ensure that the only change is in the file V.kt and specifically related to analytics, the following conditions
122
+ # must be met:
123
+
124
+ # First Condition: Verify that the total count of modified files within the /deps directory is exactly one.
125
+ # Second Condition: Ensure that V.kt is the only file that has been altered in the /deps directory.
126
+ # Third Condition: Confirm that there is precisely one modification in the V.kt file, and this modification
127
+ # includes the term analyticsLib.
128
+
129
+ # This method will help to avoid running the build for all platforms when only the analytics lib is changed.
130
+ def self.deps_contains_one_analytics_lib_change(earlier_commit, later_commit)
131
+ files_in_deps = `git diff --name-only #{earlier_commit} #{later_commit} | \
132
+ grep '^deps/'`.strip.split("\n")
133
+ if files_in_deps.length == 1
134
+ v_is_changed = files_in_deps[0].include?('/V.kt')
135
+ if v_is_changed
136
+ changes_in_v_file = `
137
+ git diff --unified=0 #{earlier_commit} #{later_commit} deps/src/main/kotlin/com/plangrid/deps/V.kt | \
138
+ grep '^[+]' | \
139
+ grep -Ev '^(--- a/|\\+\\+\\+ b/)'`.strip.split("\n")
140
+ if changes_in_v_file.length == 1
141
+ changes_in_v_file[0].include?('analyticsLib')
142
+ else
143
+ false end
144
+ else
145
+ false end
146
+ else
147
+ false end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,27 @@
1
+ module SharedConstants
2
+ AUTOMERGE_BRANCH_PREFIX = 'automerge/'.freeze
3
+ AUTOMERGE_LABEL = 'Automerge'.freeze
4
+ AUTOGENERATED_LABEL = 'Autogenerated'.freeze
5
+ VERSION_BUMP_LABEL = 'Version bump'.freeze
6
+
7
+ DEV = 'dev'.freeze
8
+ RC = 'rc'.freeze
9
+ HOTFIX = 'hotfix'.freeze
10
+
11
+ GITHUB_API_BASE_URL = 'https://api.github.com'.freeze
12
+
13
+ SLACK_USERNAME_MAPPING_FILE_PATH = 'common/fastlane/slack_username_mapping.yml'.freeze
14
+
15
+ INITIATION_TYPE_AUTOMATIC = 'automatic'.freeze
16
+ INITIATION_TYPE_MANUAL = 'manual'.freeze
17
+ INITIATION_TYPES = [INITIATION_TYPE_AUTOMATIC, INITIATION_TYPE_MANUAL].freeze
18
+
19
+ MERGE_METHOD_SQUASH = 'squash'.freeze
20
+ MERGE_METHOD_MERGE = 'merge'.freeze
21
+ MERGE_METHOD_REBASE = 'rebase'.freeze
22
+ VALID_MERGE_METHODS = [MERGE_METHOD_MERGE, MERGE_METHOD_SQUASH, MERGE_METHOD_REBASE].freeze
23
+
24
+ FAILURE_REPORTING_INSTRUCTIONS = 'Be a good citizen! Investigate and report this build failure using ' \
25
+ '<https://wiki.autodesk.com/display/ACSMOB/Reporting+Build+Flakes|' \
26
+ 'these reporting instructions>.'.freeze
27
+ end
@@ -0,0 +1,54 @@
1
+ require 'open3'
2
+
3
+ # TODO: consider refactor now that Sorbet has been removed from the codebase
4
+ # The shell module handles running shell commands in both a Fastlane and non Fastlane context
5
+ # The sh method only works when run in a Fastlane context but some of the library methods
6
+ # need to be run outside of a Fastlane context and we want to use the same interface
7
+ # for everything. This implementation removes the ability to use an error callback but
8
+ # that could be added in the future if necessary.
9
+ module ShellHelper
10
+ # Attempt to run the command with Fastlane's sh action. If we're not in a Fastlane context
11
+ # a NameError will be thrown in which case we'll use our own implementation.
12
+ # This implementation is a slightly simplified version of fastlane's sh command
13
+ # https://github.com/fastlane/fastlane/blob/master/fastlane/lib/fastlane/helper/sh_helper.rb
14
+ # Because we're not using Fastlane::Action.sh (Action without an S) sorbet will complain
15
+ # if this file is in strict mode without the use of T.unsafe
16
+ def self.run(command, log: true)
17
+ Fastlane::Actions.sh(command.strip, log: log).strip
18
+ rescue NameError => e
19
+ raise e unless e.message.include?('uninitialized constant') && e.message.include?('Fastlane')
20
+
21
+ previous_encoding = [Encoding.default_external, Encoding.default_internal]
22
+ Encoding.default_external = Encoding::UTF_8
23
+ Encoding.default_internal = Encoding::UTF_8
24
+
25
+ begin
26
+ puts "$ #{command.strip}" if log
27
+ result = ''
28
+ exit_status = 0
29
+ Open3.popen2e(command.strip) do |_stdin, io, thread|
30
+ io.sync = true
31
+ io.each do |line|
32
+ puts "▸ #{line.strip}" if log
33
+ result << line
34
+ end
35
+ process_status = thread.value
36
+ exit_status = process_status.exitstatus # Should not be nil because io.sync = true
37
+ end
38
+
39
+ if exit_status != 0
40
+ message = if log
41
+ "Exit status of command '#{command.strip}' was #{exit_status} instead of 0."
42
+ else
43
+ "Shell command exited with exit status #{exit_status} instead of 0."
44
+ end
45
+ message += "\n#{result}" if log
46
+ raise message
47
+ end
48
+ result.strip
49
+ ensure
50
+ Encoding.default_external = previous_encoding.first
51
+ Encoding.default_internal = previous_encoding.last
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,40 @@
1
+ module SlackConstants
2
+ IOS_SLACK_EMOJI = ':apple-co:'.freeze
3
+ ANDROID_SLACK_EMOJI = ':android-7248:'.freeze
4
+ WINDOWS_SLACK_EMOJI = ':windows:'.freeze
5
+
6
+ HEADSUP = 'Headsup'.freeze
7
+ RELEASE = 'Release'.freeze
8
+
9
+ IOS = 'iOS'.freeze
10
+ ANDROID = 'Android'.freeze
11
+ WINDOWS = 'Windows'.freeze
12
+
13
+ ATTACHMENT_COLOR_GOOD = 'good'.freeze
14
+ ATTACHMENT_COLOR_YELLOW = '#ffe7c6'.freeze
15
+ ATTACHMENT_COLOR_GREEN = '#08a115'.freeze
16
+ ATTACHMENT_COLOR_RED = '#de0910'.freeze
17
+ ATTACHMENT_COLOR_DANGER = 'danger'.freeze
18
+
19
+ VALID_EMAIL_SUFFIXES = %w[autodesk.com].freeze
20
+
21
+ WARNING = 'warning'.freeze
22
+
23
+ # channels-ids
24
+ ACS_MOBILE_COMMON_CI = 'C01EZ7Q7ZV1'.freeze
25
+ ACS_MOBILE_RELEASE_ANNOUNCEMENT = 'CSM2T5RQQ'.freeze
26
+ ACS_MOBILE_CODE_HEALTH = 'C04LVDLDE8M'.freeze
27
+
28
+ ACS_IOS = 'CU8CK7MN2'.freeze
29
+ ACS_IOS_CI = 'CSLJSAZQC'.freeze
30
+ ACS_IOS_CODE = 'CU820SFMM'.freeze
31
+ ACS_IOS_TEST_FLAKE = 'C021WSYAMM3'.freeze
32
+ ACS_IOS_RELEASE_COMMUNICATION = 'C01RJBTG76U'.freeze
33
+ ACS_IOS_AUTOMATION_TESTING_AB = 'C026XEUCZD0'.freeze
34
+
35
+ ACS_ANDROID = 'CU66Z5X6H'.freeze
36
+ ACS_ANDROID_CI = 'C016HBNFD6G'.freeze
37
+ ACS_ANDROID_CODE = 'CU58QK1C0'.freeze
38
+ ACS_ANDROID_RELEASE_COMMUNICATION = 'C012JKW6EUR'.freeze
39
+ ACS_AN_AUTOMATION_TESTING_AB = 'C033N3891T2'.freeze
40
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'file_content'
2
+
3
+ # Generic functions for dealing with versions.
4
+ module Version
5
+ def self.app_version(version_file_path, regex, git_ref = nil)
6
+ match_list = FileContent.find_content_in_file(version_file_path, regex, git_ref)
7
+ raise "Cannot find version in #{version_file_path} at ref #{git_ref} with regex #{regex}" if match_list.empty?
8
+
9
+ match_list[0]
10
+ end
11
+
12
+ def self.update_app_version(version_file_path, regex, replacement_string)
13
+ FileContent.find_and_replace_content_in_file(version_file_path, regex, replacement_string)
14
+ end
15
+
16
+ def self.increment_minor_version(version, increment_value)
17
+ match = version.match(/^\d+\.\d+\.\d+$/)
18
+ raise "Version #{version} is not in the correct format." unless match
19
+
20
+ version_pieces = version.split('.').map(&:to_i)
21
+ [version_pieces[0], version_pieces[1] + increment_value, 0].map(&:to_s).join('.')
22
+ end
23
+
24
+ def self.increment_patch_version(version, increment_value)
25
+ match = version.match(/^\d+\.\d+\.\d+$/)
26
+ raise "Version #{version} is not in the correct format." unless match
27
+
28
+ version_pieces = version.split('.').map(&:to_i)
29
+ [version_pieces[0], version_pieces[1], version_pieces[2] + increment_value].map(&:to_s).join('.')
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StandardAutomationLibrary
4
+ VERSION = '0.2.1-temp'
5
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir["#{__dir__}/standard_automation_library/**/*.rb"].sort.each(&method(:require))
4
+
5
+ module StandardAutomationLibrary
6
+ class Error < StandardError; end
7
+ # Your code goes here...
8
+ end