fuel-cli 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: []