fuel-cli 0.0.2

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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZDBlMjU0MmNiZjIyOTc2NWYzZjQwYjNkNDBiNzBlYjgyMDMxN2MzNQ==
5
+ data.tar.gz: !binary |-
6
+ ZWZkMDVmMjZkOTRiZjRiMTk1MmViY2YxZjRhZDNiMjIwNThjMjUxZA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NDFjYWRjYzIyMjlhOGJmOGRmZDlmZGY1M2M1M2E0MzI1MDNkOGIwYzQxYjNl
10
+ YTY2MmZmMzcyOWFkNTY1YWQ1NzgxZTYwNmQ0NmY0NjY5ZDkwMGIwZDVmNDIz
11
+ MGZkN2VhM2IyNGQ0ZGRmNTJlZDU3MWViNTE2ZjE2YzAzOWY2ODM=
12
+ data.tar.gz: !binary |-
13
+ Mjg2YWIyYjVmZDMxOTRmMzRmZDUxNmQyMWI2MTA1MzZhNGNlMzNkMzgzMjgz
14
+ M2QwNThlMzkwMTNkNGJhZTM5OWQ1M2UxNGNjZjE3MDIwN2I0Y2Y3NzQ4OTY0
15
+ MWRkOTIxNDFiNjY2YzlmOTA5MDE5NWY0OTZmNjI4Y2ZiNGEzZjQ=
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fuel-cli.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Alex Zherdev
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Fuel::Cli
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'fuel-cli'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install fuel-cli
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/fuel-cli/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc "Open an irb session preloaded with this library"
4
+ task :console do
5
+ sh "irb -rubygems -I lib -r fuel/cli.rb"
6
+ end
data/bin/fuel ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'fuel/cli'
4
+ Fuel::CLI::Main.start
data/bin/gerrit ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'fuel/cli'
4
+ Fuel::CLI::Gerrit.start
data/bin/jenkins ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'fuel/cli'
4
+ Fuel::CLI::Jenkins.start
data/bin/jira ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'fuel/cli'
4
+ Fuel::CLI::Jira.start
data/fuel-cli.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fuel/cli/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "fuel-cli"
8
+ spec.version = Fuel::CLI::VERSION
9
+ spec.authors = ["Alex Zherdev"]
10
+ spec.email = ["azherdev@rocketfuel.com"]
11
+ spec.summary = %q{Simple set of command-line tools to aid in Fuel dev process.}
12
+ spec.description = %q{}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.post_install_message = "\e[36m\e[1mWelcome, Fuel developer! Type \"fuel setup\" to get started.\e[0m"
22
+
23
+ spec.add_dependency "thor"
24
+ spec.add_dependency "httparty"
25
+ spec.add_dependency "jenkins_api_client"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.6"
28
+ spec.add_development_dependency "rake"
29
+ spec.add_development_dependency "debugger"
30
+ end
@@ -0,0 +1,27 @@
1
+ module Fuel
2
+ module CLI
3
+ class Base < Thor
4
+ include Fuel::Util
5
+
6
+ include HTTParty
7
+
8
+ protected
9
+
10
+ def commit_message
11
+ `git log -1 --pretty=%B`
12
+ end
13
+
14
+ def fetch_change_id_or_fail
15
+ change_id = commit_message =~ /Change-Id: (.*)/ && $1
16
+ change_id or raise Thor::Error, set_color("No appropriate commit found at HEAD", :red)
17
+ end
18
+
19
+ def set_credentials(user, user_keys, password_keys)
20
+ password = ask("Password:", echo: false)
21
+ Config.set(*user_keys, user)
22
+ Config.set(*password_keys, password)
23
+ puts
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,328 @@
1
+ require 'time'
2
+ require 'cgi'
3
+ require 'fileutils'
4
+ require 'base64'
5
+
6
+ module Fuel
7
+ module CLI
8
+ class Gerrit < Base
9
+ include GerritCommon
10
+
11
+ package_name "gerrit"
12
+
13
+ headers "Content-Type" => "application/json"
14
+ digest_auth Config.get('gerrit', 'user'), Config.get('gerrit', 'password')
15
+ parser GerritJsonParser
16
+
17
+ desc "status", "Show a brief overview of the current gerrit status"
18
+ def status
19
+ change_id = 'Ia8b172edcedcef7856b4d5fa7aee9e58649abe75'#fetch_change_id_or_fail
20
+
21
+ result = self.class.get(changes_endpoint(change_id, "?o=CURRENT_REVISION")).parsed_response
22
+ current_rev = result['current_revision']
23
+
24
+ say result["subject"], :bold
25
+ table = [["Owner", result["owner"]["name"]]]
26
+ table << ["Updated", format_time(result["updated"])]
27
+ table << ["Status", result["status"]]
28
+ table << ["Ref", gerrit_ref(result)]
29
+ print_table table
30
+
31
+ result = fetch_comments(change_id, current_rev)
32
+ print_comments_info(result)
33
+ puts
34
+ result = self.class.get(reviewers_endpoint(change_id)).parsed_response
35
+ table = result.map { |row| format_row(row) }
36
+ table.unshift(["Reviewer", "CR", "QA", "V"])
37
+ print_table table
38
+ end
39
+
40
+ desc "open", "Open Gerrit in your browser"
41
+ def open
42
+ `open #{gerrit_change_url}`
43
+ end
44
+
45
+ desc "review-add USER[, USER, ...]", "Add reviewer(s) to the gerrit"
46
+ def review_add(*emails_or_aliases)
47
+ change_id = fetch_change_id_or_fail
48
+ emails_or_aliases.each do |value|
49
+ emails = Array(to_email(value))
50
+ if emails.empty?
51
+ say "No email found for alias #{value}; reviewer not added", :yellow
52
+ next
53
+ end
54
+ emails.each do |email|
55
+ result = self.class.post(reviewers_endpoint(change_id), body: { reviewer: email }.to_json).parsed_response
56
+ end
57
+ end
58
+ end
59
+
60
+ desc "alias EMAIL [ALIAS]", "Add an alias or show all existing aliases for the given email"
61
+ def alias(email, alias_name = nil)
62
+ if alias_name
63
+ Config.set('gerrit', 'aliases', alias_name, email)
64
+ else
65
+ aliases = Config.get('gerrit-aliases') || {}
66
+ aliases_found = aliases.map { |k, v| v == email ? k : nil }.compact
67
+ if aliases_found.empty?
68
+ say "No aliases created for #{email}", :yellow
69
+ else
70
+ say "Aliases for #{email}: #{aliases_found.join("; ")}"
71
+ end
72
+ end
73
+ end
74
+
75
+ desc "auth USER", "Store authentication information for Gerrit"
76
+ def auth(user)
77
+ set_credentials(user, ['gerrit', 'user'], ['gerrit', 'password'])
78
+ end
79
+
80
+ desc "submit", "Submit the current change"
81
+ def submit
82
+ change_id = fetch_change_id_or_fail
83
+ result = self.class.post(changes_endpoint(change_id, 'submit'), body: { wait_for_merge: true }.to_json)
84
+ puts result.code
85
+ puts result.parsed_response.inspect
86
+ end
87
+
88
+ option :message, :aliases => '-m'
89
+ desc "abandon", "Abandon the current change"
90
+ def abandon
91
+ change_id = fetch_change_id_or_fail
92
+ params = options['message'] ? { body: { message: options['message'] }.to_json } : {}
93
+ result = self.class.post(changes_endpoint(change_id, 'abandon'), params).parsed_response
94
+ puts result.inspect
95
+ end
96
+
97
+ option :message, :aliases => '-m'
98
+ desc "restore", "Restore the current change"
99
+ def restore
100
+ change_id = fetch_change_id_or_fail
101
+ params = options['message'] ? { body: { message: options['message'] }.to_json } : {}
102
+ result = self.class.post(changes_endpoint(change_id, 'restore'), params).parsed_response
103
+ puts result.inspect
104
+ end
105
+
106
+ desc "rebase", "Rebase the current change"
107
+ def rebase
108
+ change_id = fetch_change_id_or_fail
109
+ result = self.class.post(changes_endpoint(change_id, "rebase"))
110
+ if result.response.code == "409"
111
+ say "Rebase failed, conflicts found", :red
112
+ else
113
+ say "Rebased successfully", :green
114
+ end
115
+ end
116
+
117
+ desc "checkout USER", "Pulls the latest patchset by the given user for local QA"
118
+ def checkout(email_or_alias)
119
+ email = to_email(email_or_alias)
120
+ say "Group aliases are not supported", :red and return if email.is_a?(Array)
121
+ result = self.class.get(changes_endpoint(nil, "?q=status:open+owner:#{email}&n=1&o=CURRENT_REVISION&o=DOWNLOAD_COMMANDS")).parsed_response
122
+ say "No open changes found for #{email_or_alias}", :yellow and return if result.empty?
123
+ current_rev = result[0]['current_revision']
124
+ command = result[0]['revisions'][current_rev]['fetch']['ssh']['commands']['Checkout']
125
+ `#{command}`
126
+ end
127
+
128
+ option :message, :aliases => '-m'
129
+ desc "qa VOTE", "Put a QA mark on the patch currently checked out"
130
+ def qa(vote)
131
+ submit_review('QA-Test-Passed', vote)
132
+ end
133
+
134
+ option :message, :aliases => '-m'
135
+ desc "cr VOTE", "Put a Code Review mark on the patch currently checked out"
136
+ def cr(vote)
137
+ submit_review('Code-Review', vote)
138
+ end
139
+
140
+ desc "comments FILE", "View the file with comments"
141
+ def comments(file)
142
+ comments, lines = comments_for_file(file)
143
+ extra_lines = 0
144
+ comments.each do |c|
145
+ line = c['line'] + extra_lines
146
+ comment_lines = format_comment(c)
147
+ lines.insert(line, *comment_lines)
148
+ extra_lines += comment_lines.size
149
+ end
150
+ exec("printf #{lines.join('\n').inspect} | less -F -X -R")
151
+ end
152
+
153
+ desc "respond FILE", "Look through the comments and respond to them"
154
+ def respond(file)
155
+ comments, lines, change_id, revision_id = comments_for_file(file)
156
+ commented_on = {}
157
+ comments_for_lines = comments.inject({}) do |h, c|
158
+ h[c['line']] ||= []
159
+ h[c['line']] << c
160
+ h
161
+ end
162
+ comments_to_post = comments_for_lines.map do |line, cc|
163
+ next if cc.all? { |c| c['author']['username'] != gerrit_user }
164
+ system 'tput smcup'
165
+
166
+ line_in_array = line - 1
167
+ first_line = [0, line_in_array - 4].max
168
+ if first_line == line_in_array
169
+ say "Line #{line}:", :bold
170
+ else
171
+ say "Lines #{first_line + 1}-#{line}:", :bold
172
+ end
173
+ say lines[first_line..line_in_array].join("\n")
174
+ cc.each do |c|
175
+ say format_comment(c).join("\n")
176
+ end
177
+ reply = ask "Reply on this line (press enter to skip):"
178
+ system 'tput rmcup'
179
+ next if reply.strip.empty?
180
+ { line: line, message: reply }
181
+ end.compact
182
+ say 'No comments to respond to!', :green and return if comments_to_post.empty?
183
+ puts comments_to_post.inspect
184
+ result = self.class.post(revisions_endpoint(change_id, revision_id, 'review'), body: { comments: { file => comments_to_post } }.to_json).parsed_response
185
+ puts result.inspect
186
+ end
187
+
188
+ no_commands do
189
+ def gerrit_change_url
190
+ change_id = fetch_change_id_or_fail
191
+ result = self.class.get(changes_endpoint(change_id)).parsed_response
192
+ "#{gerrit_url}/#{result['_number']}"
193
+ end
194
+ end
195
+
196
+ private
197
+
198
+ def format_comment(c)
199
+ msg = c['message'].gsub("\n\n", "\n")
200
+ date = format_time(c['updated'])
201
+ author = set_color(c['author']['name'], :yellow)
202
+ prefix = "[#{date}] #{author}: "
203
+ comment_lines = msg.split("\n")
204
+ comment_lines[0].prepend prefix
205
+ comment_lines[1..-1].each do |cline|
206
+ cline.prepend " " * prefix.size
207
+ end
208
+ comment_lines
209
+ end
210
+
211
+ def submit_review(label, vote)
212
+ change_id = fetch_change_id_or_fail
213
+ result = self.class.get(changes_endpoint(change_id, "?o=CURRENT_REVISION")).parsed_response
214
+ current_rev = result['current_revision']
215
+ body = { labels: { label => vote.to_i } }
216
+ message = options['message'] || run_editor.gsub("\n", "\n\n")
217
+
218
+ body[:message] = message
219
+ result = self.class.post(revisions_endpoint(change_id, current_rev, 'review'), body: body.to_json).parsed_response
220
+ end
221
+
222
+ def to_email(value)
223
+ is_email?(value) ? value : emails_for_alias(value)
224
+ end
225
+
226
+ def is_email?(value)
227
+ !!value.index('@')
228
+ end
229
+
230
+ def emails_for_alias(alias_name)
231
+ aliases = Config.get('gerrit-aliases', alias_name)
232
+ end
233
+
234
+ def format_row(row)
235
+ result = [row["name"]]
236
+ approvals = row["approvals"]
237
+ result << format_review(approvals && approvals["Code-Review"])
238
+ result << format_verified(approvals && approvals["QA-Test-Passed"])
239
+ result << format_verified(approvals && approvals["Verified"])
240
+ result
241
+ end
242
+
243
+ def format_review(cell)
244
+ characters = { "0" => " ", "-1" => set_color("-1", :red), "+1" => set_color("+1", :green), "+2" => tick }
245
+ characters[(cell || "").strip]
246
+ end
247
+
248
+ def format_verified(cell)
249
+ characters = { "0" => " ", "-1" => cross, "+1" => tick }
250
+ characters[(cell || "").strip]
251
+ end
252
+
253
+ def format_time(time)
254
+ Time.parse(time + " UTC").getlocal.strftime("%b %-d %l:%M %p")
255
+ end
256
+
257
+ def tick
258
+ set_color([10003].pack("U*"), :green)
259
+ end
260
+
261
+ def cross
262
+ set_color([10008].pack("U*"), :red)
263
+ end
264
+
265
+ def revisions_endpoint(change_id, revision_id = nil, path = nil)
266
+ url = "#{changes_endpoint(change_id)}revisions/"
267
+ url += "#{revision_id}/" if revision_id
268
+ url += path if path
269
+ url
270
+ end
271
+
272
+ def reviewers_endpoint(change_id)
273
+ changes_endpoint(change_id, "reviewers")
274
+ end
275
+
276
+ def run_editor
277
+ path = File.expand_path('~/.fuel/MSG')
278
+ FileUtils.touch(path)
279
+ system((ENV['EDITOR'] || 'vi') + ' ' + path)
280
+ content = File.read(path)
281
+ FileUtils.rm(path)
282
+ content
283
+ end
284
+
285
+ def fetch_comments(change_id, revision_id)
286
+ return self.class.get(revisions_endpoint(change_id, revision_id, 'comments')).parsed_response
287
+ end
288
+
289
+ def comments_for_file(file)
290
+ change_id = fetch_change_id_or_fail
291
+ result = self.class.get(changes_endpoint(change_id, "detail?o=CURRENT_REVISION&o=CURRENT_FILES")).parsed_response
292
+ current_rev = result['current_revision']
293
+
294
+ content = self.class.get(revisions_endpoint(change_id, current_rev, "files/#{CGI::escape(file)}/content")).parsed_response
295
+ content = Base64::decode64(content)
296
+ lines = content.split("\n")
297
+ comments = fetch_comments(change_id, current_rev)[file]
298
+ comments.sort_by! { |c| [c['line'], Time.parse(c['updated'])] }
299
+ [comments, lines, change_id, current_rev]
300
+ end
301
+
302
+ def gerrit_user
303
+ Config.get('gerrit', 'user')
304
+ end
305
+
306
+ def print_comments_info(data)
307
+ table = data.inject([]) do |memo, (file, comments)|
308
+ memo << [file, set_color(plural(comments.count, 'comment'), :yellow)] if comments.count > 0
309
+ memo
310
+ end
311
+ unless table.empty?
312
+ puts
313
+ say "Files with comments:"
314
+ say " (use \"gerrit comments FILE\" to see the comments)"
315
+ print_table table, :indent => 8
316
+ end
317
+ end
318
+
319
+ def plural(n, singular)
320
+ if n == 1
321
+ "1 #{singular}"
322
+ else
323
+ "#{n} #{singular}s"
324
+ end
325
+ end
326
+ end
327
+ end
328
+ end
@@ -0,0 +1,21 @@
1
+ module Fuel
2
+ module CLI
3
+ module GerritCommon
4
+ def gerrit_url
5
+ Fuel::Util::Config.get('gerrit', 'url').chomp('/')
6
+ end
7
+
8
+ def gerrit_ref(result)
9
+ current_rev = result['current_revision']
10
+ result['revisions'][current_rev]['fetch']['ssh']['ref']
11
+ end
12
+
13
+ def changes_endpoint(change_id = nil, path = nil)
14
+ url = "#{gerrit_url}/a/changes/"
15
+ url += "#{change_id}/" if change_id
16
+ url += path if path
17
+ url
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,89 @@
1
+ module Fuel
2
+ module CLI
3
+ class Jenkins < Base
4
+ include GerritCommon
5
+
6
+ headers "Content-Type" => "application/json"
7
+ digest_auth Config.get('gerrit', 'user'), Config.get('gerrit', 'password')
8
+ parser GerritJsonParser
9
+
10
+ package_name "jenkins"
11
+
12
+ desc "open", "Open the most recent Jenkins build for the current change"
13
+ def open
14
+ build_link = build_link_or_fail
15
+
16
+ `open #{build_link}`
17
+ end
18
+
19
+ desc "deploy", "Deploy the latest patchset to your QA server"
20
+ def deploy
21
+ change_id = 'Ia8b172edcedcef7856b4d5fa7aee9e58649abe75'#fetch_change_id_or_fail
22
+ result = self.class.get(changes_endpoint(change_id, "?o=CURRENT_REVISION")).parsed_response
23
+ current_rev = result['current_revision']
24
+
25
+ client = create_client
26
+ ref = gerrit_ref(result).sub('refs/', '')
27
+ client.job.build("ui.fuel-deploy.gerrit", { "Environment" => "qa", "GerritRef" => ref })
28
+ end
29
+
30
+ desc "status", "View the status of the current build"
31
+ def status
32
+ details = build_details
33
+ result = details['result']
34
+ say 'Build is still running', :yellow and return unless result
35
+
36
+ color = case result
37
+ when "FAILURE", "ABORTED"
38
+ :red
39
+ when "SUCCESS"
40
+ :green
41
+ else
42
+ :default
43
+ end
44
+
45
+ say result, color
46
+ say 'Build artifacts are available via "jenkins artifacts"'
47
+ end
48
+
49
+ desc "artifacts", "Open build artifacts in the browser"
50
+ def artifacts
51
+ build_link = build_link_or_fail
52
+
53
+ details = build_details
54
+ say 'No artifacts available, build might be still running', :yellow and return if details['artifacts'].empty?
55
+
56
+ `open #{build_link}artifact/`
57
+ end
58
+
59
+ desc "auth USER", "Store authentication information for Jenkins"
60
+ def auth(user)
61
+ set_credentials(user, ['jenkins', :username], ['jenkins', :password])
62
+ end
63
+
64
+ private
65
+
66
+ def build_details
67
+ build_link = build_link_or_fail
68
+ build_number = build_link =~ /\/(\d+)\// && $1
69
+ client = create_client
70
+
71
+ client.job.get_build_details("ui.fuel-gerrit", build_number)
72
+ end
73
+
74
+ def create_client
75
+ @client ||= JenkinsApi::Client.new(Config.get('jenkins'))
76
+ end
77
+
78
+ def build_link_or_fail
79
+ change_id = 'I89fe9b251904fb929adf61605438fcc6fdbf534d'#fetch_change_id_or_fail
80
+ result = self.class.get(changes_endpoint(change_id, "detail")).parsed_response
81
+ build_message = result['messages'].reverse.find { |m| m['author']['username'] == 'hbuilduser' }
82
+ build_message &&= build_message['message']
83
+ build_link = build_message && URI.extract(build_message)[0]
84
+ raise Thor::Error, set_color('No Jenkins build found for this change', :red) unless build_link
85
+ build_link
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,86 @@
1
+ module Fuel
2
+ module CLI
3
+ class Jira < Base
4
+ package_name "jira"
5
+
6
+ headers "Content-Type" => "application/json"
7
+ basic_auth Config.get('jira', 'user'), Config.get('jira', 'password')
8
+
9
+ desc "status [JIRA]", "View a brief summary of the jira"
10
+ def status(jira = nil)
11
+ jira_id = jira || fetch_jira_or_fail
12
+ result = self.class.get(issue_endpoint(jira_id)).parsed_response
13
+ fields = result["fields"]
14
+ say "#{jira_id}: #{fields['summary']}", :bold
15
+ table = [['Type', fields['issuetype']['name']]]
16
+ table << ['Status', fields['status']['name']]
17
+ table << ['Assignee', fields['assignee']['displayName']]
18
+ print_table table
19
+ end
20
+
21
+ desc "start [JIRA]", "Start progress on the jira"
22
+ def start(jira = nil)
23
+ transition('start progress', jira)
24
+ end
25
+
26
+ desc "resolve", "Resolve the current jira"
27
+ def resolve
28
+ transition('resolve issue')
29
+ end
30
+
31
+ desc "submit", "Put the current jira in review"
32
+ def submit
33
+ transition('submit', nil, fields: { get_field_id(fetch_jira_or_fail, 'review id') => Gerrit.new.gerrit_change_url })
34
+ end
35
+
36
+ desc "auth USER", "Store authentication information for JIRA"
37
+ def auth(user)
38
+ set_credentials(user, ['jira', 'user'], ['jira', 'password'])
39
+ end
40
+
41
+ private
42
+
43
+ def transition(name, jira = nil, extra_body = {})
44
+ jira_id = jira || fetch_jira_or_fail
45
+ body = { transition: { id: get_transition_id(jira_id, name) } }.merge(extra_body)
46
+ result = self.class.post(issue_endpoint(jira_id, "transitions"),
47
+ body: body.to_json).parsed_response
48
+ puts result.inspect
49
+ end
50
+
51
+ def jira_url
52
+ Config.get('jira', 'url').chomp('/')
53
+ end
54
+
55
+ def get_field_id(jira_id, name)
56
+ result = self.class.get(jira_endpoint("field")).parsed_response
57
+ field = result.find { |r| r['name'].downcase == name.downcase }
58
+ field && field['id']
59
+ end
60
+
61
+ def get_transition_id(jira_id, name)
62
+ get_allowed_transitions(jira_id).each do |transition|
63
+ return transition["id"] if transition["name"].downcase == name.downcase
64
+ end
65
+ nil
66
+ end
67
+
68
+ def get_allowed_transitions(jira_id)
69
+ result = self.class.get(issue_endpoint(jira_id, "transitions")).parsed_response
70
+ result["transitions"]
71
+ end
72
+
73
+ def fetch_jira_or_fail
74
+ commit_message.split("\n")[2] or raise Thor::Error, set_color("No JIRA found at HEAD", :red)
75
+ end
76
+
77
+ def jira_endpoint(path = "")
78
+ "#{jira_url}/rest/api/2/#{path}"
79
+ end
80
+
81
+ def issue_endpoint(id, path = "")
82
+ jira_endpoint("issue/#{id}/#{path}")
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,86 @@
1
+ module Fuel
2
+ module CLI
3
+ class Main < Thor
4
+ include Fuel::Util
5
+
6
+ package_name "fuel"
7
+
8
+ desc "setup", "Set up the CLI interactively"
9
+ def setup
10
+ say flame, :cyan
11
+ say "\nWelcome to Fuel-CLI!\n\n", :bold
12
+
13
+ setup_gerrit
14
+ setup_jira
15
+ setup_jenkins
16
+
17
+ say "You are good to go!", [:green, :bold]
18
+ say 'You have "fuel", "gerrit", "jira" and "jenkins" executables in your PATH.', :green
19
+ say 'Use the "help" command (e.g. "gerrit help") to get started.', :green
20
+ end
21
+
22
+ option :gerrit_url
23
+ option :jira_url
24
+ option :jenkins_url
25
+ desc "config", "Set configuration values"
26
+ def config
27
+ raise Thor::Error, set_color("Provide at least one key-value pair!", :red) if options.empty?
28
+ if options.has_key?('gerrit_url')
29
+ Config.set('gerrit', 'url', options['gerrit_url'])
30
+ end
31
+ if options.has_key?('jira_url')
32
+ Config.set('jira', 'url', options['jira_url'])
33
+ end
34
+ if options.has_key?('jenkins_url')
35
+ Config.set('jenkins', :server_url, options['jenkins_url'])
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def setup_gerrit
42
+ say "[Setting up gerrit]", :yellow
43
+ gerrit_url = ask "URL:"
44
+ self.class.new.invoke :config, [], gerrit_url: gerrit_url
45
+ gerrit_user = ask "User name:"
46
+ say "Please go to your Gerrit user settings now, and generate an HTTP password there."
47
+ Gerrit.new.invoke :auth, [gerrit_user]
48
+
49
+ say "gerrit configured successfully!\n\n", :green
50
+ end
51
+
52
+ def setup_jira
53
+ say "[Setting up jira]", :yellow
54
+ jira_url = ask "URL:"
55
+ self.class.new.invoke :config, [], jira_url: jira_url
56
+ jira_user = ask "User name:"
57
+ Jira.new.invoke :auth, [jira_user]
58
+
59
+ say "jira configured successfully!\n\n", :green
60
+ end
61
+
62
+ def setup_jenkins
63
+ say "[Setting up jenkins]", :yellow
64
+ jenkins_url = ask "URL:"
65
+ self.class.new.invoke :config, [], jenkins_url: jenkins_url
66
+ jenkins_user = ask "User name:"
67
+ Jenkins.new.invoke :auth, [jenkins_user]
68
+
69
+ say "jenkins configured successfully!\n\n", :green
70
+ end
71
+
72
+ def flame
73
+ <<-EOT
74
+ `.--..`
75
+ `-/+syyso/. .sdddddddhs+:.
76
+ -+ydddddddddddh/` .dddh:.`.:+sdddho:`
77
+ :yhsso+oshddddddddds/yddy` `+ddddo`
78
+ `. ``` .+ddds/-:/+o+: `-+ydds/.
79
+ .:odddh+ `sdddho:` .:+ydds/.
80
+ `:ohdddddddddy` /ddddddhhhddhs/.
81
+ `./oyhddhs:` .ohhdhys+:`
82
+ EOT
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,13 @@
1
+ class Thor::Shell::Color
2
+ DEFAULT = "\e[39m"
3
+ ON_DEFAULT = "\e[49m"
4
+ end
5
+
6
+ class String
7
+ alias_method :old_size, :size
8
+
9
+ def size
10
+ str = self.dup
11
+ str.gsub(/\e[^m]*m/, '').old_size
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ module Fuel
2
+ module CLI
3
+ VERSION = "0.0.2"
4
+ end
5
+ end
data/lib/fuel/cli.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'thor'
2
+ require 'jenkins_api_client'
3
+
4
+ require 'fuel/cli/version'
5
+ require 'fuel/util/config'
6
+ require 'fuel/util/json_parser'
7
+ require 'fuel/cli/gerrit_common'
8
+ require 'fuel/cli/patches'
9
+ require 'fuel/cli/base'
10
+ require 'fuel/cli/gerrit'
11
+ require 'fuel/cli/jira'
12
+ require 'fuel/cli/jenkins'
13
+ require 'fuel/cli/main'
@@ -0,0 +1,54 @@
1
+ require 'fileutils'
2
+ require 'yaml'
3
+
4
+ module Fuel
5
+ module Util
6
+ class Config
7
+ CONFIG_PATH = File.expand_path("~/.fuel")
8
+ CONFIG_FILENAME = "#{CONFIG_PATH}/cliconfig"
9
+
10
+ def self.set(*keys, value)
11
+ ensure_config_exists
12
+
13
+ config = load
14
+ key = keys.pop
15
+ h = keys.inject(config) { |memo, k| memo[k] ||= {}; memo[k] }
16
+ h[key] = value
17
+ save(config)
18
+ end
19
+
20
+ def self.get(*keys)
21
+ ensure_config_exists
22
+
23
+ config = load
24
+ keys.inject(config, :fetch)
25
+ rescue KeyError
26
+ nil
27
+ end
28
+
29
+ def self.add(key, value)
30
+ ensure_config_exists
31
+
32
+ config = load
33
+ config[key] ||= []
34
+ config[key] << value
35
+ save(config)
36
+ end
37
+
38
+ private
39
+
40
+ def self.load
41
+ YAML.load_file(CONFIG_FILENAME) || {}
42
+ end
43
+
44
+ def self.save(config)
45
+ File.open(CONFIG_FILENAME, "w") { |f| f.write(YAML.dump(config)) }
46
+ end
47
+
48
+ def self.ensure_config_exists
49
+ FileUtils.mkdir_p(CONFIG_PATH)
50
+ FileUtils.touch(CONFIG_FILENAME)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,15 @@
1
+ require 'httparty'
2
+
3
+ module Fuel
4
+ module Util
5
+ class GerritJsonParser < HTTParty::Parser
6
+ XSSI_PREFIX = ")]}'"
7
+ SupportedFormats.merge!("application/json" => :json, "text/json" => :json)
8
+
9
+ def json
10
+ body.sub!(XSSI_PREFIX, "") if body.start_with?(XSSI_PREFIX)
11
+ JSON.load(body, nil)
12
+ end
13
+ end
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fuel-cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Alex Zherdev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: httparty
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: jenkins_api_client
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: debugger
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: ''
98
+ email:
99
+ - azherdev@rocketfuel.com
100
+ executables:
101
+ - fuel
102
+ - gerrit
103
+ - jenkins
104
+ - jira
105
+ extensions: []
106
+ extra_rdoc_files: []
107
+ files:
108
+ - .gitignore
109
+ - Gemfile
110
+ - LICENSE.txt
111
+ - README.md
112
+ - Rakefile
113
+ - bin/fuel
114
+ - bin/gerrit
115
+ - bin/jenkins
116
+ - bin/jira
117
+ - fuel-cli.gemspec
118
+ - lib/fuel/cli.rb
119
+ - lib/fuel/cli/base.rb
120
+ - lib/fuel/cli/gerrit.rb
121
+ - lib/fuel/cli/gerrit_common.rb
122
+ - lib/fuel/cli/jenkins.rb
123
+ - lib/fuel/cli/jira.rb
124
+ - lib/fuel/cli/main.rb
125
+ - lib/fuel/cli/patches.rb
126
+ - lib/fuel/cli/version.rb
127
+ - lib/fuel/util/config.rb
128
+ - lib/fuel/util/json_parser.rb
129
+ homepage: ''
130
+ licenses:
131
+ - MIT
132
+ metadata: {}
133
+ post_install_message: ! "\e[36m\e[1mWelcome, Fuel developer! Type \"fuel setup\" to
134
+ get started.\e[0m"
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ! '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubyforge_project:
150
+ rubygems_version: 2.3.0
151
+ signing_key:
152
+ specification_version: 4
153
+ summary: Simple set of command-line tools to aid in Fuel dev process.
154
+ test_files: []