WTBuildHelpers 0.1.3 → 0.2.0
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 +4 -4
- data/README.md +14 -7
- data/exe/wtbuild +57 -0
- data/lib/WTBuildHelpers.rb +3 -6
- data/lib/WTBuildHelpers/version.rb +1 -1
- data/lib/help.rb +25 -0
- data/lib/jira.rb +241 -0
- data/lib/teamcity.rb +137 -0
- metadata +7 -7
- data/exe/wtbuild_jira_update +0 -5
- data/exe/wtbuild_teamcity_fetch_git_range +0 -13
- data/lib/jira_tasks.rb +0 -245
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e6418a62d2f8b5af8789b0e040b099192d655c44
|
4
|
+
data.tar.gz: 518f10e071661fb471d5bf1350ef0d7f29dce4e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b751fd611f57471cd40ddadbbd4f74fdca88f14539bc8bc3788adf9391f5c8356b11b2d922ee86dfbaa21aa5b5280eaf4427785c8674b63e043e3bba29fa1f3b
|
7
|
+
data.tar.gz: acf144d2c25e5e1e666da093fcdc9affb720574444f3f4c86e95b8cc452ea0fc2feacc3ae910b69d572f162c2b54761d5d0c987cb5e54e98106e1e8902bb579c
|
data/README.md
CHANGED
@@ -3,9 +3,12 @@
|
|
3
3
|
This is a set of scripts for multiple platforms to help with building and testing tasks
|
4
4
|
specific to WillowTree's pipelines and integrations.
|
5
5
|
|
6
|
-
|
6
|
+
All commands are run with the wtbuild command. If you need help on a specific command, run wtbuild help [command]
|
7
7
|
|
8
|
-
|
8
|
+
JIRA
|
9
|
+
====
|
10
|
+
|
11
|
+
#### jira_update_from_build
|
9
12
|
Updates any jira tickets based on the supplied git commit range.
|
10
13
|
|
11
14
|
Environment Variables
|
@@ -17,22 +20,26 @@ Environment Variables
|
|
17
20
|
- TRAVIS_BUILD_NUMBER - build number
|
18
21
|
- Supplied by TeamCity
|
19
22
|
- BUILD_NUMBER - build number
|
20
|
-
- TRAMCITY_COMMIT_RANGE - created by
|
23
|
+
- TRAMCITY_COMMIT_RANGE - created by teamcity_fetch_git_range
|
21
24
|
|
22
25
|
Command Line Usage:
|
23
26
|
```bash
|
24
|
-
$
|
27
|
+
$ wtbuild jira_update_from_build
|
25
28
|
```
|
26
29
|
|
27
30
|
### TeamCity
|
28
|
-
####
|
31
|
+
#### teamcity_fetch_git_range
|
29
32
|
Fetches the last good build from the TeamCity server and uses that to produce the a git commit range.
|
30
33
|
|
31
|
-
Command Line Usage (
|
34
|
+
Command Line Usage (when run as its own TeamCity build step):
|
32
35
|
```bash
|
33
|
-
$
|
36
|
+
$ wtbuild teamcity_fetch_git_range --url "%teamcity.serverUrl%" --build_type "%system.teamcity.buildType.id%" --username "%system.teamcity.auth.userId%" --password "%system.teamcity.auth.password%"`
|
34
37
|
```
|
35
38
|
|
39
|
+
Command Line Usage (when run as part of a larger build step):
|
40
|
+
```bash
|
41
|
+
$ export TEAMCITY_COMMIT_RANGE=`$ wtbuild teamcity_fetch_git_range --url "%teamcity.serverUrl%" --build_type "%system.teamcity.buildType.id%" --username "%system.teamcity.auth.userId%" --password "%system.teamcity.auth.password%" --export`
|
42
|
+
```
|
36
43
|
|
37
44
|
## Installation
|
38
45
|
|
data/exe/wtbuild
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'WTBuildHelpers'
|
4
|
+
|
5
|
+
Command = Struct.new(:command, :description, :help_function, :run_function)
|
6
|
+
|
7
|
+
commands = [
|
8
|
+
|
9
|
+
# JIRA section
|
10
|
+
Command.new(
|
11
|
+
"jira_update_from_build",
|
12
|
+
"Update JIRA by transitioning any mentioned issues and settting Fixed In Build",
|
13
|
+
Proc.new { WTBuildHelpers::JIRA::print_help },
|
14
|
+
Proc.new { |args| WTBuildHelpers::JIRA::commandline_jira_update(args) }),
|
15
|
+
|
16
|
+
# TeamCity section
|
17
|
+
Command.new(
|
18
|
+
"teamcity_fetch_git_range",
|
19
|
+
"Get the git commit range for the last successful build on TeamCity to now",
|
20
|
+
Proc.new { WTBuildHelpers::TeamCity::print_help },
|
21
|
+
Proc.new { |args| WTBuildHelpers::TeamCity::commandline_fetch_commit_range(args) }),
|
22
|
+
]
|
23
|
+
|
24
|
+
commands.unshift(
|
25
|
+
# Help
|
26
|
+
Command.new(
|
27
|
+
"help",
|
28
|
+
"Get help for a specific command",
|
29
|
+
Proc.new { WTBuildHelpers::Help::print_help },
|
30
|
+
Proc.new { |args| WTBuildHelpers::Help::print_command_help(args, commands) }),
|
31
|
+
)
|
32
|
+
|
33
|
+
command_name = ARGV.shift
|
34
|
+
if command_name.nil?
|
35
|
+
puts " Usage: #{__FILE__} command [options]"
|
36
|
+
puts
|
37
|
+
printf " %-30s %s\n", "Command", "Command Description"
|
38
|
+
puts
|
39
|
+
commands.each do |command|
|
40
|
+
printf " %-30s %s\n", command.command, command.description
|
41
|
+
end
|
42
|
+
else
|
43
|
+
command = commands.find do |c|
|
44
|
+
c.command == command_name
|
45
|
+
end
|
46
|
+
|
47
|
+
if command
|
48
|
+
if ARGV.count == 0 || ARGV[0] == "--help"
|
49
|
+
command.help_function.call()
|
50
|
+
else
|
51
|
+
command.run_function.call(ARGV)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
puts "Unknown command: #{command_name}"
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
end
|
data/lib/WTBuildHelpers.rb
CHANGED
data/lib/help.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
module WTBuildHelpers
|
4
|
+
end
|
5
|
+
|
6
|
+
module WTBuildHelpers::Help
|
7
|
+
|
8
|
+
def self.print_help()
|
9
|
+
puts "Usage: wtbuild help command"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.print_command_help(args, commands)
|
13
|
+
command_name = args[0]
|
14
|
+
command = commands.find do |c|
|
15
|
+
c.command == command_name
|
16
|
+
end
|
17
|
+
|
18
|
+
if command
|
19
|
+
command.help_function.call()
|
20
|
+
else
|
21
|
+
puts "Unknown command: #{command_name}"
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/jira.rb
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'pp'
|
5
|
+
require 'set'
|
6
|
+
require 'rest_client'
|
7
|
+
require 'base64'
|
8
|
+
require 'json'
|
9
|
+
|
10
|
+
module WTBuildHelpers
|
11
|
+
end
|
12
|
+
|
13
|
+
module WTBuildHelpers::JIRA
|
14
|
+
Options = Struct.new(:commit_range, :site, :username, :password, :build)
|
15
|
+
|
16
|
+
class JiraInfo
|
17
|
+
attr_reader :fixed_in_build_field_id
|
18
|
+
attr_reader :build_complete_transition_id
|
19
|
+
|
20
|
+
attr_reader :client
|
21
|
+
|
22
|
+
def initialize(site, username, password)
|
23
|
+
@site = site
|
24
|
+
@username = username
|
25
|
+
@password = password
|
26
|
+
|
27
|
+
auth_token = Base64.strict_encode64("#{username}:#{password}")
|
28
|
+
base_url = File.join(site, '/rest/api/2')
|
29
|
+
headers = { "Authorization" => "Basic #{auth_token}",
|
30
|
+
"Content-Type" => "application/json", }
|
31
|
+
@client = RestClient::Resource.new(base_url, :headers => headers)
|
32
|
+
end
|
33
|
+
|
34
|
+
def update_fixed_in_build()
|
35
|
+
# Find the Fixed In Build field
|
36
|
+
begin
|
37
|
+
response = @client['field'].get()
|
38
|
+
|
39
|
+
fields = JSON.parse(response.body);
|
40
|
+
fixed_field = fields.find do |field|
|
41
|
+
field["name"] == "Fixed In Build"
|
42
|
+
end
|
43
|
+
|
44
|
+
if fixed_field
|
45
|
+
@fixed_in_build_field_id = fixed_field["id"]
|
46
|
+
else
|
47
|
+
puts "Could not find 'Fixed In Build' field on supplied JIRA instance. Are you using WillowTree's JIRA? Does your user have access to the project?"
|
48
|
+
end
|
49
|
+
rescue RestClient::Exception => e
|
50
|
+
puts "Error code #{e.response.code} attempting to find Fixed In Build field"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def update_build_complete_transition(issue_key)
|
55
|
+
begin
|
56
|
+
fields_resource = "issue/#{issue_key}/transitions"
|
57
|
+
|
58
|
+
response = @client[fields_resource].get
|
59
|
+
|
60
|
+
response_json = JSON.parse(response.body)
|
61
|
+
transitions = response_json["transitions"]
|
62
|
+
build_complete_transition = transitions.find do |transition|
|
63
|
+
transition["name"] == "Build Complete"
|
64
|
+
end
|
65
|
+
|
66
|
+
if build_complete_transition
|
67
|
+
@build_complete_transition_id = build_complete_transition["id"]
|
68
|
+
end
|
69
|
+
rescue RestClient::Exception => e
|
70
|
+
puts "Error code #{e.response.code} attempting to find Build Complete transition"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Parser
|
76
|
+
def self.parse(options)
|
77
|
+
args = Options.new()
|
78
|
+
|
79
|
+
opt_parser = OptionParser.new do |opts|
|
80
|
+
opts.banner = "Usage: jira_tasks.rb [options]"
|
81
|
+
|
82
|
+
opts.on("-c", "--commits COMMIT_RANGE", "Specify a commit range") do |commit_range|
|
83
|
+
args.commit_range = commit_range
|
84
|
+
end
|
85
|
+
|
86
|
+
opts.on("-u", "--username USERNAME", "Specify the user to connecto to JIRA") do |username|
|
87
|
+
args.username = username
|
88
|
+
end
|
89
|
+
|
90
|
+
opts.on("-p", "--password PASSWORD", "Specify the password to connect to JIRA") do |password|
|
91
|
+
args.password = password
|
92
|
+
end
|
93
|
+
|
94
|
+
opts.on("-s", "--site SITE", "Specify the base location of the JIRA instance") do |site|
|
95
|
+
site.slice!("http://")
|
96
|
+
site.insert(0, "https://") if not site.start_with?("https://")
|
97
|
+
args.site = site
|
98
|
+
end
|
99
|
+
|
100
|
+
opts.on("-b", "--build BUILD", "The build number for which this script is executing") do |build|
|
101
|
+
args.build = build
|
102
|
+
end
|
103
|
+
|
104
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
105
|
+
puts opts
|
106
|
+
exit
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
opt_parser.parse!(options)
|
111
|
+
return args
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.find_jira_issues(logs)
|
116
|
+
issue_set = Set.new
|
117
|
+
logs.each do |log|
|
118
|
+
issues = log.scan(/[A-Z][A-Z]+-[1-9][0-9]*/)
|
119
|
+
issue_set.merge(issues)
|
120
|
+
end
|
121
|
+
|
122
|
+
return issue_set
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.get_git_history(commit_range)
|
126
|
+
happyface = "\u263A".encode('utf-8')
|
127
|
+
git_log = `git --no-pager log --format="%B#{happyface}" #{commit_range} `
|
128
|
+
|
129
|
+
#parse out the log
|
130
|
+
logs = git_log.split(happyface)
|
131
|
+
|
132
|
+
return logs
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.jira_update(jira_info, issues, build)
|
136
|
+
if !jira_info.fixed_in_build_field_id
|
137
|
+
jira_info.update_fixed_in_build
|
138
|
+
end
|
139
|
+
|
140
|
+
# Something's wrong. We're not on our JIRA
|
141
|
+
return unless jira_info.fixed_in_build_field_id
|
142
|
+
|
143
|
+
issues.each do |issue_key|
|
144
|
+
#try to perform the transition
|
145
|
+
if !jira_info.build_complete_transition_id
|
146
|
+
jira_info.update_build_complete_transition(issue_key)
|
147
|
+
end
|
148
|
+
|
149
|
+
#still nil? Can't perform this transition
|
150
|
+
if !jira_info.build_complete_transition_id
|
151
|
+
puts "Could no update issue #{issue_key}: could not find transition"
|
152
|
+
next
|
153
|
+
end
|
154
|
+
|
155
|
+
params = {
|
156
|
+
transition: {
|
157
|
+
id: jira_info.build_complete_transition_id
|
158
|
+
},
|
159
|
+
fields: {
|
160
|
+
jira_info.fixed_in_build_field_id => build
|
161
|
+
}
|
162
|
+
}
|
163
|
+
|
164
|
+
puts "Updating issue #{issue_key}"
|
165
|
+
begin
|
166
|
+
issue_response = jira_info.client["issue/#{issue_key}/transitions"].post(JSON.generate(params))
|
167
|
+
rescue RestClient::Exception => e
|
168
|
+
puts "Error updating issue #{issue_key}: Error code #{e.response.code}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.pull_travis_options(options)
|
174
|
+
puts "Pulling parameters from Travis-CI environment variables where needed...."
|
175
|
+
options.commit_range = ENV["TRAVIS_COMMIT_RANGE"] if options.commit_range == nil
|
176
|
+
options.build = ENV["TRAVIS_BUILD_NUMBER"] if options.build == nil
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.pull_team_city_options(options)
|
180
|
+
puts "Pulling parameters from TeamCity environment variables where needed...."
|
181
|
+
options.commit_range = ENV["TEAMCITY_COMMIT_RANGE"] if options.commit_range == nil
|
182
|
+
options.build = ENV["BUILD_NUMBER"] if options.build == nil
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.pull_common_options(options)
|
186
|
+
options.site = ENV["JIRA_URL"] if options.site == nil
|
187
|
+
options.username = ENV["JIRA_USERNAME"] if options.username == nil
|
188
|
+
options.password = ENV["JIRA_PASSWORD"] if options.password == nil
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.validate_options(options)
|
192
|
+
valid_options = true
|
193
|
+
if options.commit_range == nil
|
194
|
+
puts "Commit range is required. None specified."
|
195
|
+
puts "You can specify this in TeamCity by running 'teamcity_git_range_fetch.rb' before this step." if ENV["TEAMCITY_VERSION"]
|
196
|
+
valid_options = false
|
197
|
+
end
|
198
|
+
if options.site == nil
|
199
|
+
puts "Site parameter is required, or use JIRA_URL environment variable on build servers"
|
200
|
+
valid_options = false
|
201
|
+
end
|
202
|
+
if options.username == nil
|
203
|
+
puts "No username for JIRA provided. Use JIRA_USERNAME environment variable on build servers"
|
204
|
+
valid_options = false
|
205
|
+
end
|
206
|
+
if options.password == nil
|
207
|
+
puts "No password for JIRA provided. Use JIRA_PASSWORD environment variable on build servers"
|
208
|
+
valid_options = false
|
209
|
+
end
|
210
|
+
|
211
|
+
valid_options
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.print_help()
|
215
|
+
Parser.parse(["--help"])
|
216
|
+
end
|
217
|
+
|
218
|
+
def self.commandline_jira_update(args)
|
219
|
+
options = args ? Parser.parse(ARGV) : Options.new
|
220
|
+
|
221
|
+
if ENV["TRAVIS"]
|
222
|
+
pull_travis_options(options)
|
223
|
+
# Detect running on TeamCity for common prarams
|
224
|
+
elsif ENV["TEAMCITY_VERSION"]
|
225
|
+
pull_team_city_options(options)
|
226
|
+
end
|
227
|
+
pull_common_options(options)
|
228
|
+
|
229
|
+
if validate_options(options)
|
230
|
+
logs = get_git_history(options.commit_range)
|
231
|
+
issue_set = find_jira_issues(logs)
|
232
|
+
|
233
|
+
jira_info = JiraInfo.new options.site, options.username, options.password
|
234
|
+
jira_update(jira_info, issue_set, options.build)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
if __FILE__ == $0
|
240
|
+
WTBuildHelpers::JIRA::commandline_jira_update(ARGV)
|
241
|
+
end
|
data/lib/teamcity.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'base64'
|
5
|
+
require 'json'
|
6
|
+
require 'rest_client'
|
7
|
+
require 'pp'
|
8
|
+
require 'uri'
|
9
|
+
|
10
|
+
module WTBuildHelpers
|
11
|
+
end
|
12
|
+
|
13
|
+
module WTBuildHelpers::TeamCity
|
14
|
+
Options = Struct.new(:url, :build_type_id, :username, :password, :export)
|
15
|
+
|
16
|
+
class OptionsParser
|
17
|
+
def self.parse(options)
|
18
|
+
args = Options.new()
|
19
|
+
args.export = false
|
20
|
+
|
21
|
+
opt_parser = OptionParser.new do |opts|
|
22
|
+
opts.banner = "Usage: #{__FILE__} [options]"
|
23
|
+
|
24
|
+
opts.on("-s", "--url URL", "Specify the URL for TeamCity") do |url|
|
25
|
+
url.slice!("http://")
|
26
|
+
url.insert(0, "http://")
|
27
|
+
args.url = url
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on("-u", "--username USERNAME", "Specify the user to connecto to TeamCity") do |username|
|
31
|
+
args.username = username
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on("-p", "--password PASSWORD", "Specify the password to connect to TeamCity") do |password|
|
35
|
+
args.password = password
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on("-t", "--build_type BUILD_TYPE", "The build_type_id for the running build from TeamCity") do |build_type_id|
|
39
|
+
args.build_type_id = build_type_id
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on("-e", "--export", "Output range for use as a parameter to the 'export' bash command") do
|
43
|
+
args.export = true
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
47
|
+
puts opts
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
opt_parser.parse!(options)
|
53
|
+
return args
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.put_range(options, start_revision, end_revision)
|
58
|
+
value = ""
|
59
|
+
if end_revision
|
60
|
+
value = "#{end_revision}..#{start_revision}"
|
61
|
+
else
|
62
|
+
value = "#{start_revision}"
|
63
|
+
end
|
64
|
+
|
65
|
+
if options.export
|
66
|
+
puts value
|
67
|
+
else
|
68
|
+
puts "##teamcity[setParameter name='env.TEAMCITY_COMMIT_RANGE' value='#{value}']"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.get_last_successful_rev(options, branch)
|
73
|
+
auth = "#{options.username}:#{options.password}"
|
74
|
+
auth_token = Base64.strict_encode64(auth)
|
75
|
+
|
76
|
+
headers = { "Authorization" => "Basic #{auth_token}",
|
77
|
+
"Accept" => "application/json"
|
78
|
+
}
|
79
|
+
|
80
|
+
end_revision = nil
|
81
|
+
escaped_branch = URI.escape(branch)
|
82
|
+
|
83
|
+
full_url = "#{options.url}/app/rest/buildTypes/id:#{options.build_type_id}/builds/?locator=status:SUCCESS,branch:name:#{escaped_branch}"
|
84
|
+
|
85
|
+
response = RestClient.get(full_url, headers=headers)
|
86
|
+
json_response = JSON.parse(response.body)
|
87
|
+
|
88
|
+
if json_response["build"] && json_response["build"].count > 0
|
89
|
+
last_build = json_response["build"].first
|
90
|
+
last_build_id = last_build["id"]
|
91
|
+
|
92
|
+
build_full_url = "#{options.url}/app/rest/builds/id:#{last_build["id"]}"
|
93
|
+
build_response = RestClient.get(build_full_url, headers=headers)
|
94
|
+
build_json_response = JSON.parse(build_response.body)
|
95
|
+
|
96
|
+
end_revision = build_json_response["revisions"]["revision"][0]["version"]
|
97
|
+
end
|
98
|
+
|
99
|
+
return end_revision
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.fetch_commit_range(options)
|
103
|
+
branch = `git rev-parse --abbrev-ref HEAD`
|
104
|
+
branch.strip!
|
105
|
+
|
106
|
+
start_revision = ENV["BUILD_VCS_NUMBER"]
|
107
|
+
|
108
|
+
begin
|
109
|
+
end_revision = get_last_successful_rev(options, branch)
|
110
|
+
if !end_revision
|
111
|
+
# Check refs/heads instead
|
112
|
+
branch = "refs/heads/#{branch}"
|
113
|
+
end_revision = get_last_successful_rev(options, branch)
|
114
|
+
end
|
115
|
+
|
116
|
+
put_range(options, start_revision, end_revision)
|
117
|
+
rescue RestClient::Exception => e
|
118
|
+
puts "ERROR getting last successful build from TeamCity:"
|
119
|
+
pp e
|
120
|
+
exit 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.print_help()
|
125
|
+
OptionsParser.parse(["--help"])
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.commandline_fetch_commit_range(args = nil)
|
129
|
+
options = OptionsParser.parse(args)
|
130
|
+
|
131
|
+
fetch_commit_range(options)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
if __FILE__ == $0
|
136
|
+
WTBuildHelpers::TeamCity.commandline_fetch_commit_range(ARGV)
|
137
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: WTBuildHelpers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- WillowTreeApps
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: OptionParser
|
@@ -70,8 +70,7 @@ description:
|
|
70
70
|
email:
|
71
71
|
- jeff.ward@willowtreeapps.com
|
72
72
|
executables:
|
73
|
-
-
|
74
|
-
- wtbuild_teamcity_fetch_git_range
|
73
|
+
- wtbuild
|
75
74
|
extensions: []
|
76
75
|
extra_rdoc_files: []
|
77
76
|
files:
|
@@ -83,11 +82,12 @@ files:
|
|
83
82
|
- WTBuildHelpers.gemspec
|
84
83
|
- bin/console
|
85
84
|
- bin/setup
|
86
|
-
- exe/
|
87
|
-
- exe/wtbuild_teamcity_fetch_git_range
|
85
|
+
- exe/wtbuild
|
88
86
|
- lib/WTBuildHelpers.rb
|
89
87
|
- lib/WTBuildHelpers/version.rb
|
90
|
-
- lib/
|
88
|
+
- lib/help.rb
|
89
|
+
- lib/jira.rb
|
90
|
+
- lib/teamcity.rb
|
91
91
|
- lib/teamcity_git_range_fetch.rb
|
92
92
|
homepage:
|
93
93
|
licenses:
|
data/exe/wtbuild_jira_update
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
#! /usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'WTBuildHelpers'
|
4
|
-
|
5
|
-
if ARGV.length != 4
|
6
|
-
puts "Usage: teamcity_git_range_fetch.rb [TeamCity Url] [TeamCity Build Type Id] [TeamCity User Name] [TeamCity Password]"
|
7
|
-
puts "These parameters can be passed from TeamCity with the following line: "
|
8
|
-
puts " teamcity_git_range_fetch.rb \"%teamcity.serverUrl%\" \"%system.teamcity.buildType.id%\" \"%system.teamcity.auth.userId%\" \"%system.teamcity.auth.password%\""
|
9
|
-
|
10
|
-
exit
|
11
|
-
end
|
12
|
-
|
13
|
-
WTBuildHelpers::TeamCity.fetch_commit_range(ARGV[0], ARGV[1], ARGV[2], ARGV[3])
|
data/lib/jira_tasks.rb
DELETED
@@ -1,245 +0,0 @@
|
|
1
|
-
#! /usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'optparse'
|
4
|
-
require 'pp'
|
5
|
-
require 'set'
|
6
|
-
require 'rest_client'
|
7
|
-
require 'base64'
|
8
|
-
require 'json'
|
9
|
-
|
10
|
-
module WTBuildHelpers
|
11
|
-
module Jira
|
12
|
-
Options = Struct.new(:commit_range, :site, :username, :password, :build)
|
13
|
-
|
14
|
-
class JiraInfo
|
15
|
-
attr_reader :fixed_in_build_field_id
|
16
|
-
attr_reader :build_complete_transition_id
|
17
|
-
|
18
|
-
attr_reader :site
|
19
|
-
attr_reader :base_url
|
20
|
-
attr_reader :auth_token
|
21
|
-
|
22
|
-
def initialize(site, username, password)
|
23
|
-
@site = site
|
24
|
-
@username = username
|
25
|
-
@password = password
|
26
|
-
|
27
|
-
@auth_token = Base64.encode64("#{username}:#{password}")
|
28
|
-
@base_url = File.join(site, '/rest/api/2')
|
29
|
-
end
|
30
|
-
|
31
|
-
def update_fixed_in_build()
|
32
|
-
headers = { "Authorization" => "Basic #{@auth_token}" }
|
33
|
-
|
34
|
-
# Find the Fixed In Build field
|
35
|
-
begin
|
36
|
-
fields_url = File.join(@base_url, 'field')
|
37
|
-
response = RestClient.get(fields_url, headers=headers)
|
38
|
-
|
39
|
-
fields = JSON.parse(response.body);
|
40
|
-
fixed_field = fields.find do |field|
|
41
|
-
field["name"] == "Fixed In Build"
|
42
|
-
end
|
43
|
-
|
44
|
-
if not fixed_field.nil?
|
45
|
-
@fixed_in_build_field_id = fixed_field["id"]
|
46
|
-
else
|
47
|
-
puts "Could not find 'Fixed In Build' field on supplied JIRA instance. Are you using WillowTree's JIRA? Does your user have access to the project?"
|
48
|
-
end
|
49
|
-
rescue RestClient::Exception => e
|
50
|
-
puts "Error code #{e.response.code} attempting to find Fixed In Build field"
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def update_build_complete_transition(issue_key)
|
55
|
-
headers = { "Authorization" => "Basic #{@auth_token}"}
|
56
|
-
|
57
|
-
fields_url = File.join(@base_url, 'issue', issue_key, 'transitions')
|
58
|
-
begin
|
59
|
-
response = RestClient.get(fields_url, headers=headers)
|
60
|
-
|
61
|
-
response_json = JSON.parse(response.body)
|
62
|
-
transitions = response_json["transitions"]
|
63
|
-
build_complete_transition = transitions.find do |transition|
|
64
|
-
transition["name"] == "Build Complete"
|
65
|
-
end
|
66
|
-
|
67
|
-
if not build_complete_transition.nil?
|
68
|
-
@build_complete_transition_id = build_complete_transition["id"]
|
69
|
-
end
|
70
|
-
rescue RestClient::Exception => e
|
71
|
-
puts "Error code #{e.response.code} attempting to find Build Complete transition"
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
class Parser
|
77
|
-
def self.parse(options)
|
78
|
-
args = Options.new()
|
79
|
-
|
80
|
-
opt_parser = OptionParser.new do |opts|
|
81
|
-
opts.banner = "Usage: jira_tasks.rb [options]"
|
82
|
-
|
83
|
-
opts.on("-c", "--commits COMMIT_RANGE", "Specify a commit range") do |commit_range|
|
84
|
-
args.commit_range = commit_range
|
85
|
-
end
|
86
|
-
|
87
|
-
opts.on("-u", "--username USERNAME", "Specify the user to connecto to JIRA") do |username|
|
88
|
-
args.username = username
|
89
|
-
end
|
90
|
-
|
91
|
-
opts.on("-p", "--password PASSWORD", "Specify the password to connect to JIRA") do |password|
|
92
|
-
args.password = password
|
93
|
-
end
|
94
|
-
|
95
|
-
opts.on("-s", "--site SITE", "Specify the base location of the JIRA instance") do |site|
|
96
|
-
site.slice!("http://")
|
97
|
-
site.insert(0, "https://")
|
98
|
-
args.site = site
|
99
|
-
end
|
100
|
-
|
101
|
-
opts.on("-b", "--build BUILD", "The build number for which this script is executing") do |build|
|
102
|
-
args.build = build
|
103
|
-
end
|
104
|
-
|
105
|
-
opts.on_tail("-h", "--help", "Show this message") do
|
106
|
-
puts opts
|
107
|
-
exit
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
opt_parser.parse!(options)
|
112
|
-
return args
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def self.find_jira_issues(logs)
|
117
|
-
issue_set = Set.new
|
118
|
-
logs.each do |log|
|
119
|
-
issues = log.scan(/[A-Z][A-Z]+-[1-9][0-9]*/)
|
120
|
-
issue_set.merge(issues)
|
121
|
-
end
|
122
|
-
|
123
|
-
return issue_set
|
124
|
-
end
|
125
|
-
|
126
|
-
def self.get_git_history(commit_range)
|
127
|
-
happyface = "\u263A".encode('utf-8')
|
128
|
-
git_log = `git --no-pager log --format="%B#{happyface}" #{commit_range} `
|
129
|
-
|
130
|
-
#parse out the log
|
131
|
-
logs = git_log.split(happyface)
|
132
|
-
|
133
|
-
return logs
|
134
|
-
end
|
135
|
-
|
136
|
-
def self.jira_update(jira_info, issues, build)
|
137
|
-
jira_url = File.join(jira_info.base_url, 'issue')
|
138
|
-
|
139
|
-
if jira_info.fixed_in_build_field_id.nil?
|
140
|
-
jira_info.update_fixed_in_build
|
141
|
-
end
|
142
|
-
|
143
|
-
# Something's wrong. We're not on our JIRA
|
144
|
-
return if jira_info.fixed_in_build_field_id.nil?
|
145
|
-
|
146
|
-
headers = { "Authorization" => "Basic #{jira_info.auth_token}",
|
147
|
-
"Content-Type" => "application/json" }
|
148
|
-
|
149
|
-
issues.each do |issue_key|
|
150
|
-
#try to perform the transition
|
151
|
-
if jira_info.build_complete_transition_id.nil?
|
152
|
-
jira_info.update_build_complete_transition(issue_key)
|
153
|
-
end
|
154
|
-
|
155
|
-
#still nil? Can't perform this transition
|
156
|
-
if jira_info.build_complete_transition_id.nil?
|
157
|
-
puts "Could no update issue #{issue_key}: could not find transition"
|
158
|
-
next
|
159
|
-
end
|
160
|
-
|
161
|
-
params = {
|
162
|
-
transition: {
|
163
|
-
id: jira_info.build_complete_transition_id
|
164
|
-
},
|
165
|
-
fields: {
|
166
|
-
jira_info.fixed_in_build_field_id => build
|
167
|
-
}
|
168
|
-
}
|
169
|
-
transition_url = File.join(jira_url, issue_key, 'transitions')
|
170
|
-
|
171
|
-
puts "Updating issue #{issue_key}"
|
172
|
-
begin
|
173
|
-
issue_response = RestClient.post(transition_url, JSON.generate(params), headers=headers)
|
174
|
-
rescue RestClient::Exception => e
|
175
|
-
puts "Error updating issue #{issue_key}: Error code #{e.response.code}"
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
def self.pull_travis_options(options)
|
181
|
-
puts "Pulling parameters from Travis-CI environment variables where needed...."
|
182
|
-
options.commit_range = ENV["TRAVIS_COMMIT_RANGE"] if options.commit_range == nil
|
183
|
-
options.build = ENV["TRAVIS_BUILD_NUMBER"] if options.build == nil
|
184
|
-
end
|
185
|
-
|
186
|
-
def self.pull_team_city_options(options)
|
187
|
-
puts "Pulling parameters from TeamCity environment variables where needed...."
|
188
|
-
options.commit_range = ENV["TEAMCITY_COMMIT_RANGE"] if options.commit_range == nil
|
189
|
-
options.build = ENV["BUILD_NUMBER"] if options.build == nil
|
190
|
-
end
|
191
|
-
|
192
|
-
def self.pull_common_options(options)
|
193
|
-
options.site = ENV["JIRA_URL"] if options.site == nil
|
194
|
-
options.username = ENV["JIRA_USERNAME"] if options.username == nil
|
195
|
-
options.password = ENV["JIRA_PASSWORD"] if options.password == nil
|
196
|
-
end
|
197
|
-
|
198
|
-
def self.validate_options(options)
|
199
|
-
valid_options = true
|
200
|
-
if options.commit_range == nil
|
201
|
-
puts "Commit range is required. None specified."
|
202
|
-
puts "You can specify this in TeamCity by running 'teamcity_git_range_fetch.rb' before this step." if ENV["TEAMCITY_VERSION"]
|
203
|
-
valid_options = false
|
204
|
-
end
|
205
|
-
if options.site == nil
|
206
|
-
puts "Site parameter is required, or use JIRA_URL environment variable on build servers"
|
207
|
-
valid_options = false
|
208
|
-
end
|
209
|
-
if options.username == nil
|
210
|
-
puts "No username for JIRA provided. Use JIRA_USERNAME environment variable on build servers"
|
211
|
-
valid_options = false
|
212
|
-
end
|
213
|
-
if options.password == nil
|
214
|
-
puts "No password for JIRA provided. Use JIRA_PASSWORD environment variable on build servers"
|
215
|
-
valid_options = false
|
216
|
-
end
|
217
|
-
|
218
|
-
valid_options
|
219
|
-
end
|
220
|
-
|
221
|
-
def self.perform_jira_update(options = nil)
|
222
|
-
options = Options.new if options == nil
|
223
|
-
if ENV["TRAVIS"]
|
224
|
-
pull_travis_options(options)
|
225
|
-
# Detect running on TeamCity for common prarams
|
226
|
-
elsif ENV["TEAMCITY_VERSION"]
|
227
|
-
pull_team_city_options(options)
|
228
|
-
end
|
229
|
-
pull_common_options(options)
|
230
|
-
|
231
|
-
if validate_options(options)
|
232
|
-
logs = get_git_history(options.commit_range)
|
233
|
-
issue_set = find_jira_issues(logs)
|
234
|
-
|
235
|
-
jira_info = JiraInfo.new options.site, options.username, options.password
|
236
|
-
jira_update(jira_info, issue_set, options.build)
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
if __FILE__ == $0
|
243
|
-
options = WTBuildHelpers::Jira::Parser.parse(ARGV)
|
244
|
-
WTBuildHelpers::Jira.perform_jira_update(options)
|
245
|
-
end
|