tddium 1.25.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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