tddium 1.25.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +15 -0
  2. data/bin/tddium +29 -0
  3. data/lib/tddium.rb +19 -0
  4. data/lib/tddium/agent.rb +3 -0
  5. data/lib/tddium/agent/tddium.rb +122 -0
  6. data/lib/tddium/cli.rb +26 -0
  7. data/lib/tddium/cli/api.rb +319 -0
  8. data/lib/tddium/cli/commands/account.rb +49 -0
  9. data/lib/tddium/cli/commands/activate.rb +14 -0
  10. data/lib/tddium/cli/commands/config.rb +55 -0
  11. data/lib/tddium/cli/commands/describe.rb +96 -0
  12. data/lib/tddium/cli/commands/find_failing.rb +62 -0
  13. data/lib/tddium/cli/commands/github.rb +53 -0
  14. data/lib/tddium/cli/commands/heroku.rb +15 -0
  15. data/lib/tddium/cli/commands/hg.rb +48 -0
  16. data/lib/tddium/cli/commands/keys.rb +83 -0
  17. data/lib/tddium/cli/commands/login.rb +37 -0
  18. data/lib/tddium/cli/commands/logout.rb +14 -0
  19. data/lib/tddium/cli/commands/password.rb +26 -0
  20. data/lib/tddium/cli/commands/rerun.rb +50 -0
  21. data/lib/tddium/cli/commands/server.rb +22 -0
  22. data/lib/tddium/cli/commands/spec.rb +306 -0
  23. data/lib/tddium/cli/commands/status.rb +107 -0
  24. data/lib/tddium/cli/commands/stop.rb +19 -0
  25. data/lib/tddium/cli/commands/suite.rb +110 -0
  26. data/lib/tddium/cli/commands/web.rb +22 -0
  27. data/lib/tddium/cli/config.rb +245 -0
  28. data/lib/tddium/cli/params_helper.rb +36 -0
  29. data/lib/tddium/cli/prompt.rb +128 -0
  30. data/lib/tddium/cli/show.rb +122 -0
  31. data/lib/tddium/cli/suite.rb +179 -0
  32. data/lib/tddium/cli/tddium.rb +153 -0
  33. data/lib/tddium/cli/text_helper.rb +16 -0
  34. data/lib/tddium/cli/timeformat.rb +21 -0
  35. data/lib/tddium/cli/util.rb +132 -0
  36. data/lib/tddium/constant.rb +509 -0
  37. data/lib/tddium/scm.rb +8 -0
  38. data/lib/tddium/scm/git.rb +188 -0
  39. data/lib/tddium/scm/git_log_parser.rb +67 -0
  40. data/lib/tddium/scm/hg.rb +160 -0
  41. data/lib/tddium/scm/hg_log_parser.rb +66 -0
  42. data/lib/tddium/scm/scm.rb +20 -0
  43. data/lib/tddium/script.rb +12 -0
  44. data/lib/tddium/script/git-remote-hg +1258 -0
  45. data/lib/tddium/ssh.rb +66 -0
  46. data/lib/tddium/util.rb +35 -0
  47. data/lib/tddium/version.rb +5 -0
  48. metadata +394 -0
@@ -0,0 +1,107 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ require 'stringio'
4
+
5
+ module Tddium
6
+ class TddiumCli < Thor
7
+ desc "status", "Display information about this suite, and any open dev sessions"
8
+ method_option :json, :type => :boolean, :default => false
9
+ def status
10
+ tddium_setup
11
+
12
+ begin
13
+ # tddium_setup asserts that we're in a git repo
14
+ origin_url = @scm.origin_url
15
+ repo_params = {
16
+ :active => true,
17
+ :repo_url => origin_url
18
+ }
19
+
20
+ if suite_for_current_branch? then
21
+ status_branch = @tddium_api.current_branch
22
+ suite_params = {
23
+ :suite_id => @tddium_api.current_suite_id,
24
+ :active => false,
25
+ :limit => 10
26
+ }
27
+ elsif suite_for_default_branch? then
28
+ status_branch = @tddium_api.default_branch
29
+ say Text::Error::TRY_DEFAULT_BRANCH % status_branch
30
+ suite_params = {
31
+ :suite_id => @tddium_api.default_suite_id,
32
+ :active => false,
33
+ :limit => 10
34
+ }
35
+ end
36
+
37
+ if options[:json]
38
+ res = {}
39
+ res[:running] = { origin_url => @tddium_api.get_sessions(repo_params) }
40
+ res[:history] = {
41
+ status_branch => @tddium_api.get_sessions(suite_params)
42
+ } if suite_params
43
+ puts JSON.pretty_generate(res)
44
+ else
45
+ show_session_details(
46
+ status_branch,
47
+ repo_params,
48
+ Text::Status::NO_ACTIVE_SESSION,
49
+ Text::Status::ACTIVE_SESSIONS,
50
+ true
51
+ )
52
+ show_session_details(
53
+ status_branch,
54
+ suite_params,
55
+ Text::Status::NO_INACTIVE_SESSION,
56
+ Text::Status::INACTIVE_SESSIONS,
57
+ false
58
+ ) if suite_params
59
+ say Text::Process::RERUN_SESSION
60
+ end
61
+
62
+ rescue TddiumClient::Error::Base => e
63
+ exit_failure e.message
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def show_session_details(status_branch, params, no_session_prompt, all_session_prompt, include_branch)
70
+ current_sessions = @tddium_api.get_sessions(params)
71
+
72
+ say ""
73
+ if current_sessions.empty? then
74
+ say no_session_prompt
75
+ else
76
+ commit_size = 0...7
77
+ head = @scm.current_commit[commit_size]
78
+
79
+ say all_session_prompt % (params[:suite_id] ? status_branch : "")
80
+ say ""
81
+ header = ["Session #", "Commit", ("Branch" if include_branch), "Status", "Duration", "Started"].compact
82
+ table = [header, header.map { |t| "-" * t.size }] + current_sessions.map do |session|
83
+ duration = "%ds" % session['duration']
84
+ start_timeago = "%s ago" % Tddium::TimeFormat.seconds_to_human_time(Time.now - Time.parse(session["start_time"]))
85
+
86
+ [
87
+ session["id"].to_s,
88
+ session["commit"] ? session['commit'][commit_size] : '- ',
89
+ (session["branch"] if include_branch),
90
+ session["status"],
91
+ duration,
92
+ start_timeago
93
+ ].compact
94
+ end
95
+ say(capture_stdout { print_table table }.gsub(head, "\e[7m#{head}\e[0m"))
96
+ end
97
+ end
98
+
99
+ def capture_stdout
100
+ old, $stdout = $stdout, StringIO.new
101
+ yield
102
+ $stdout.string
103
+ ensure
104
+ $stdout = old
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2011-2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc "stop [SESSION]", "Stop session by id"
6
+ def stop(ls_id=nil)
7
+ tddium_setup({:scm => false})
8
+ if ls_id
9
+ begin
10
+ say "Stoping session #{ls_id} ..."
11
+ say @tddium_api.stop_session(ls_id)['notice']
12
+ rescue
13
+ end
14
+ else
15
+ say 'Stop requires a session id -- e.g. `tddium stop 7869764`'
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,110 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc "suite", "Register the current repo/branch, view/edit CI repos & deploy keys"
6
+ method_option :edit, :type => :boolean, :default => false
7
+ method_option :account, :type => :string, :default => nil,
8
+ :aliases => %w(--org --organization)
9
+ method_option :name, :type => :string, :default => nil
10
+ method_option :repo_url, :type => :string, :default => nil
11
+ method_option :ci_pull_url, :type => :string, :default => nil
12
+ method_option :ci_push_url, :type => :string, :default => nil
13
+ method_option :test_pattern, :type => :string, :default => nil
14
+ method_option :campfire_room, :type => :string, :default => nil
15
+ method_option :hipchat_room, :type => :string, :default => nil
16
+ method_option :non_interactive, :type => :boolean, :default => false
17
+ method_option :tool, :type => :hash, :default => {}
18
+ method_option :delete, :type => :boolean, :default => false
19
+ def suite(*argv)
20
+ tddium_setup({:repo => true})
21
+
22
+ params = {}
23
+
24
+ # Load tool options into params
25
+ tool_cli_populate(options, params)
26
+
27
+ begin
28
+
29
+ if options[:delete]
30
+ # Deleting works differently (for now) because api_config can't handle
31
+ # multiple suites with the same branch name in two different accounts.
32
+
33
+ repo_url = @scm.origin_url
34
+
35
+ if argv.is_a?(Array) && argv.size > 0
36
+ branch = argv[0]
37
+ else
38
+ branch = @tddium_api.current_branch
39
+ end
40
+
41
+ suites = @tddium_api.get_suites(:repo_url => repo_url, :branch => branch)
42
+ if suites.count == 0
43
+ exit_failure Text::Error::CANT_FIND_SUITE % [repo_url, branch]
44
+ elsif suites.count > 1
45
+ say Text::Process::SUITE_IN_MULTIPLE_ACCOUNTS % [repo_url, branch]
46
+ suites.each { |s| say ' ' + s['account'] }
47
+ account = ask Text::Process::SUITE_IN_MULTIPLE_ACCOUNTS_PROMPT
48
+ suites = suites.select { |s| s['account'] == account }
49
+ if suites.count == 0
50
+ exit_failure Text::Error::INVALID_ACCOUNT_NAME
51
+ end
52
+ end
53
+
54
+ suite = suites.first
55
+
56
+ unless options[:non_interactive]
57
+ yn = ask Text::Process::CONFIRM_DELETE_SUITE % [suite['repo_url'], suite['branch'], suite['account']]
58
+ unless yn.downcase == Text::Prompt::Response::YES
59
+ exit_failure Text::Process::ABORTING
60
+ end
61
+ end
62
+
63
+ @tddium_api.permanent_destroy_suite(suite['id'])
64
+ @api_config.delete_suite(suite['branch'])
65
+ @api_config.write_config
66
+
67
+ elsif @tddium_api.current_suite_id then
68
+ # Suite ID set in tddium config file
69
+ current_suite = @tddium_api.get_suite_by_id(@tddium_api.current_suite_id)
70
+ if options[:edit] then
71
+ update_suite(current_suite, options)
72
+ else
73
+ say Text::Process::EXISTING_SUITE, :bold
74
+ say format_suite_details(current_suite)
75
+ end
76
+
77
+ @api_config.set_suite(current_suite)
78
+ @api_config.write_config
79
+ else
80
+ # Need to find or construct the suite (and repo)
81
+ params[:branch] = @scm.current_branch
82
+ params[:repo_url] = @scm.origin_url
83
+ params[:repo_name] = @scm.repo_name
84
+ params[:scm] = @scm.scm_name
85
+
86
+ say Text::Process::NO_CONFIGURED_SUITE % [params[:repo_name], params[:branch]]
87
+
88
+ prompt_suite_params(options, params)
89
+
90
+ params.each do |k,v|
91
+ params.delete(k) if v == 'disable'
92
+ end
93
+
94
+ # Create new suite if it does not exist yet
95
+ say Text::Process::CREATING_SUITE % [params[:repo_name], params[:branch]]
96
+ new_suite = @tddium_api.create_suite(params)
97
+
98
+ # Save the created suite
99
+ @api_config.set_suite(new_suite)
100
+ @api_config.write_config
101
+
102
+ say Text::Process::CREATED_SUITE, :bold
103
+ say format_suite_details(new_suite)
104
+ end
105
+ rescue TddiumClient::Error::Base => e
106
+ exit_failure(e.explanation)
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,22 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc "web [SESSION_ID]", "Open build report in web browser"
6
+ def web(*args)
7
+ tddium_setup({:login => false, :scm => false})
8
+
9
+ session_id = args.first
10
+ if session_id then
11
+ fragment = "1/reports/#{session_id.to_i}" if session_id =~ /^[0-9]+$/
12
+ end
13
+ fragment ||= 'latest'
14
+
15
+ begin
16
+ Launchy.open("#{options[:proto]}://#{options[:host]}/#{fragment}")
17
+ rescue Launchy::Error => e
18
+ exit_failure e.message
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,245 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ require 'yaml'
4
+
5
+ module ConfigHelper
6
+ def hash_stringify_keys(h)
7
+ case h
8
+ when Hash
9
+ Hash[ h.map { |k, v| [ k.to_s, hash_stringify_keys(v) ] } ]
10
+ when Enumerable
11
+ h.map { |v| hash_stringify_keys(v) }
12
+ else
13
+ h
14
+ end
15
+ end
16
+ end
17
+
18
+ module Tddium
19
+ class RepoConfig
20
+ include TddiumConstant
21
+ include ConfigHelper
22
+
23
+ def initialize
24
+ @scm = Tddium::SCM.configure
25
+ @config = load_config
26
+ end
27
+
28
+ def [](key)
29
+ return @config[key.to_s]
30
+ end
31
+
32
+ def config_filename
33
+ @config_filename
34
+ end
35
+
36
+ def load_config
37
+ config = nil
38
+
39
+ root = @scm.root
40
+ cfgfile_pair = pick_config_pair(root, Config::CONFIG_PATHS)
41
+ cfgfile_pair_depr = pick_config_pair(root, Config::CONFIG_PATHS_DEPRECATED)
42
+
43
+ if cfgfile_pair && cfgfile_pair_depr then
44
+ abort Text::Error::CONFIG_PATHS_COLLISION % [cfgfile_pair, cfgfile_pair_depr]
45
+ end
46
+
47
+ cfgfile_pair = cfgfile_pair_depr if cfgfile_pair.nil?
48
+
49
+ if cfgfile_pair && cfgfile_pair.first then
50
+ cfgfile = cfgfile_pair.first
51
+ @config_filename = cfgfile_pair[1]
52
+ begin
53
+ rawconfig = File.read(cfgfile)
54
+ if rawconfig && rawconfig !~ /\A\s*\z/ then
55
+ config = YAML.load(rawconfig)
56
+ config = hash_stringify_keys(config)
57
+ config = config['solano'] || config['tddium'] || config
58
+ end
59
+ rescue Exception => e
60
+ warn(Text::Warning::YAML_PARSE_FAILED % cfgfile)
61
+ end
62
+ end
63
+
64
+ config ||= Hash.new
65
+ return config
66
+ end
67
+
68
+ private
69
+
70
+ def pick_config_pair(root, config_paths)
71
+ files = config_paths.map { |fn| [ File.join(root, fn), fn ] }
72
+ files.select { |p| File.exists?(p.first) }.first
73
+ end
74
+ end
75
+
76
+ class ApiConfig
77
+ include TddiumConstant
78
+
79
+ # BOTCH: should be a state object rather than entire CLI object
80
+ def initialize(tddium_client, host, cli_options)
81
+ @scm = Tddium::SCM.configure
82
+ @tddium_client = tddium_client
83
+ @config = Hash.new
84
+ @host = host
85
+ @cli_options = cli_options
86
+ end
87
+
88
+ # BOTCH: fugly
89
+ def set_api(tddium_api)
90
+ @tddium_api = tddium_api
91
+ end
92
+
93
+ def logout
94
+ remove_tddium_files
95
+ end
96
+
97
+ def populate_branches(branch)
98
+ suites = @tddium_api.get_suites(:repo_url => @scm.origin_url, :branch=>branch)
99
+ suites.each do |ste|
100
+ set_suite(ste)
101
+ end
102
+ end
103
+
104
+ def get_branch(branch, var, options={})
105
+ if options['account'].nil? && @cli_options[:account] then
106
+ options['account'] = @cli_options[:account]
107
+ end
108
+
109
+ val = fetch_branch(branch, var, options)
110
+ return val unless val.nil?
111
+
112
+ populate_branches(branch)
113
+
114
+ return fetch_branch(branch, var, options)
115
+ end
116
+
117
+ def get_api_key(options = {})
118
+ options.any? ? load_config(options)['api_key'] : @config['api_key']
119
+ end
120
+
121
+ def set_api_key(api_key, user)
122
+ @config['api_key'] = api_key
123
+ end
124
+
125
+ def scm_ready_sleep
126
+ s = ENV["TDDIUM_SCM_READY_SLEEP"] || Default::SCM_READY_SLEEP
127
+ s.to_f
128
+ end
129
+
130
+ def set_suite(suite)
131
+ id = suite['id']
132
+ branch = suite['branch']
133
+ return if id.nil? || branch.nil? || branch.empty?
134
+
135
+ keys = %w(id branch account repo_id ci_ssh_pubkey)
136
+ metadata = keys.inject({}) { |h, v| h[v] = suite[v]; h }
137
+
138
+ branches = @config["branches"] || {}
139
+ branches.merge!({id => metadata})
140
+ @config.merge!({"branches" => branches})
141
+ end
142
+
143
+ def delete_suite(branch, account=nil)
144
+ branches = @config["branches"] || {}
145
+ branches.delete_if do |k, v|
146
+ v['branch'] == branch && (account.nil? || v['account'] == account)
147
+ end
148
+ end
149
+
150
+ def load_config(options = {})
151
+ global_config = load_config_from_file(:global)
152
+ return global_config if options[:global]
153
+
154
+ repo_config = load_config_from_file
155
+ return repo_config if options[:repo]
156
+
157
+ @config = global_config.merge(repo_config)
158
+ end
159
+
160
+ def write_config
161
+ path = tddium_file_name(:global)
162
+ File.open(path, File::CREAT|File::TRUNC|File::RDWR, 0600) do |file|
163
+ config = Hash.new
164
+ config['api_key'] = @config['api_key'] if @config.member?('api_key')
165
+ file.write(config.to_json)
166
+ end
167
+
168
+ path = tddium_file_name(:repo)
169
+ File.open(path, File::CREAT|File::TRUNC|File::RDWR, 0600) do |file|
170
+ file.write(@config.to_json)
171
+ end
172
+
173
+ if @scm.repo? then
174
+ branch = @scm.current_branch
175
+ id = get_branch(branch, 'id', {})
176
+ suite = @config['branches'][id] rescue nil
177
+
178
+ if suite then
179
+ path = tddium_deploy_key_file_name
180
+ File.open(path, File::CREAT|File::TRUNC|File::RDWR, 0644) do |file|
181
+ file.write(suite["ci_ssh_pubkey"])
182
+ end
183
+ end
184
+ write_scm_ignore # BOTCH: no need to write every time
185
+ end
186
+ end
187
+
188
+ def write_scm_ignore
189
+ path = @scm.ignore_path
190
+ content = File.exists?(path) ? File.read(path) : ''
191
+ unless content.include?(".tddium*\n")
192
+ File.open(path, File::CREAT|File::APPEND|File::RDWR, 0644) do |file|
193
+ file.write(".tddium*\n")
194
+ end
195
+ end
196
+ end
197
+
198
+ def tddium_file_name(scope=:repo, kind='', root=nil)
199
+ ext = (@host == 'api.tddium.com' || @host == 'ci.solanolabs.com') ? '' : ".#{@host}"
200
+
201
+ case scope
202
+ when :repo
203
+ root ||= @scm.repo? ? @scm.root : Dir.pwd
204
+
205
+ when :global
206
+ root = ENV['HOME']
207
+ end
208
+
209
+ return File.join(root, ".tddium#{kind}#{ext}")
210
+ end
211
+
212
+ def tddium_deploy_key_file_name
213
+ return tddium_file_name(:repo, '-deploy-key')
214
+ end
215
+
216
+ protected
217
+
218
+ def fetch_branch(branch, var, options)
219
+ h = @config['branches']
220
+ return nil unless h.is_a?(Hash)
221
+ h.each_pair do |id, data|
222
+ next unless data.is_a?(Hash)
223
+ branch_name = data['branch']
224
+ next unless branch_name == branch
225
+ if options.keys.all? { |k| data.member?(k) && data[k] == options[k] }
226
+ return data[var]
227
+ end
228
+ end
229
+ return nil
230
+ end
231
+
232
+ private
233
+
234
+ def remove_tddium_files
235
+ [tddium_file_name, tddium_file_name(:global)].each do |tddium_file_path|
236
+ File.delete(tddium_file_path) if File.exists?(tddium_file_path)
237
+ end
238
+ end
239
+
240
+ def load_config_from_file(tddium_file_type = :repo)
241
+ path = tddium_file_name(tddium_file_type)
242
+ File.exists?(path) ? (JSON.parse(File.read(path)) rescue {}) : {}
243
+ end
244
+ end
245
+ end