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,36 @@
1
+ # Copyright (c) 2014 Solano Labs All Rights Reserved
2
+
3
+ module ParamsHelper
4
+ include TddiumConstant
5
+
6
+ def load_params(defaults=true)
7
+ params = {}
8
+ if File.exists?(Default::PARAMS_PATH) then
9
+ File.open(Default::PARAMS_PATH, 'r') do |file|
10
+ params = JSON.parse file.read
11
+ end
12
+ elsif !defaults then
13
+ abort Text::Process::NOT_SAVED_OPTIONS
14
+ end
15
+ return params
16
+ end
17
+
18
+ def write_params options
19
+ begin
20
+ File.open(Default::PARAMS_PATH, File::CREAT|File::TRUNC|File::RDWR, 0600) do |file|
21
+ file.write options.to_json
22
+ end
23
+ say Text::Process::OPTIONS_SAVED
24
+ rescue Exception => e
25
+ say Text::Error::OPTIONS_NOT_SAVED
26
+ end
27
+ end
28
+
29
+ def display
30
+ store_params = load_params(false)
31
+ say 'Options:'
32
+ store_params.each do |k, v|
33
+ say " #{k.capitalize}:\t#{v}"
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,128 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ protected
6
+
7
+ def prompt(text, current_value, default_value, dont_prompt=false)
8
+ value = current_value || (dont_prompt ? nil : ask(text % default_value, :bold))
9
+ (value.nil? || value.empty?) ? default_value : value
10
+ end
11
+
12
+ def prompt_missing_ssh_key
13
+ keys = @tddium_api.get_keys
14
+ if keys.empty? then
15
+ say Text::Process::SSH_KEY_NEEDED
16
+ keydata = prompt_ssh_key(nil)
17
+ result = @tddium_api.set_keys({:keys => [keydata]})
18
+ return true
19
+ end
20
+ false
21
+ rescue TddiumError => e
22
+ exit_failure e.message
23
+ rescue TddiumClient::Error::API => e
24
+ exit_failure e.explanation
25
+ end
26
+
27
+ def prompt_ssh_key(current, name='default')
28
+ # Prompt for ssh-key file
29
+ ssh_file = prompt(Text::Prompt::SSH_KEY, current, Default::SSH_FILE)
30
+ Tddium::Ssh.load_ssh_key(ssh_file, name)
31
+ end
32
+
33
+ def prompt_suite_params(options, params, current={})
34
+ say Text::Process::DETECTED_BRANCH % params[:branch] if params[:branch]
35
+ params[:ruby_version] ||= tool_version(:ruby)
36
+ params[:bundler_version] ||= normalize_bundler_version(tool_version(:bundle))
37
+ params[:rubygems_version] ||= tool_version(:gem)
38
+
39
+ ask_or_update = lambda do |key, text, default|
40
+ params[key] = prompt(text, options[key], current.fetch(key.to_s, default), options[:non_interactive])
41
+ end
42
+
43
+ account_announced = false
44
+
45
+ # If we already have a suite, it already has an account, so no need to
46
+ # figure it out here.
47
+ unless current['account_id']
48
+ # Find an account id. Strategy:
49
+ # 1. Use a command line option, if specified.
50
+ # 2. If the user has only one account, use that.
51
+ # 3. If the user has existing suites with the same repo, and they are
52
+ # all in the same account, prompt with that as a default.
53
+ # 4. Prompt.
54
+ # IF we're not allowed to prompt and have no default, fail.
55
+ accounts = user_details["participating_accounts"]
56
+ account_name = if options[:account]
57
+ account_announced = true
58
+ say Text::Process::USING_ACCOUNT_FROM_FLAG % options[:account]
59
+ options[:account]
60
+ elsif accounts.length == 1
61
+ account_announced = true
62
+ say Text::Process::USING_ACCOUNT % accounts.first["account"]
63
+ accounts.first["account"]
64
+ else
65
+ # Get all of this user's suites with this repo.
66
+ repo_suites = @tddium_api.get_suites(:repo_url => params[:repo_url])
67
+ acct_ids = repo_suites.map{|s| s['account']}.uniq
68
+ default = acct_ids.length == 1 ? acct_ids.first : nil
69
+
70
+ if not options[:non_interactive] or default.nil?
71
+ say "You are a member of these organizations:"
72
+ accounts.each do |account|
73
+ say " " + account['account']
74
+ end
75
+ end
76
+
77
+ msg = default.nil? ? Text::Prompt::ACCOUNT : Text::Prompt::ACCOUNT_DEFAULT
78
+ prompt(msg, nil, default, options[:non_interactive])
79
+ end
80
+
81
+ if account_name.nil?
82
+ exit_failure (options[:non_interactive] ?
83
+ Text::Error::MISSING_ACCOUNT_OPTION :
84
+ Text::Error::MISSING_ACCOUNT)
85
+ end
86
+ account = accounts.select{|a| a['account'] == account_name}.first
87
+ if account.nil?
88
+ exit_failure Text::Error::NOT_IN_ACCOUNT % account_name
89
+ end
90
+
91
+ if !account_announced then
92
+ say Text::Process::USING_ACCOUNT % account_name
93
+ end
94
+ params[:account_id] = account["account_id"].to_s
95
+ end
96
+
97
+ pattern = configured_test_pattern
98
+ cfn = @repo_config.config_filename
99
+
100
+ if pattern.is_a?(Array)
101
+ say Text::Process::CONFIGURED_PATTERN % [cfn, pattern.map{|p| " - #{p}"}.join("\n"), cfn]
102
+ params[:test_pattern] = pattern.join(",")
103
+ elsif pattern
104
+ exit_failure Text::Error::INVALID_CONFIGURED_PATTERN % [cfn, cfn, pattern.inspect, cfn]
105
+ else
106
+ say Text::Process::TEST_PATTERN_INSTRUCTIONS unless options[:non_interactive]
107
+ ask_or_update.call(:test_pattern, Text::Prompt::TEST_PATTERN, Default::SUITE_TEST_PATTERN)
108
+ end
109
+
110
+ exclude_pattern = configured_test_exclude_pattern
111
+ cfn = @repo_config.config_filename
112
+
113
+ if exclude_pattern.is_a?(Array)
114
+ say Text::Process::CONFIGURED_EXCLUDE_PATTERN % [cfn, exclude_pattern.map{|p| " - #{p}"}.join("\n"), cfn]
115
+ params[:test_exclude_pattern] = exclude_pattern.join(",")
116
+ elsif exclude_pattern
117
+ exit_failure Text::Error::INVALID_CONFIGURED_PATTERN % [cfn, cfn, exclude_pattern.inspect, cfn]
118
+ end
119
+
120
+ unless options[:non_interactive]
121
+ say(Text::Process::SETUP_CI)
122
+ end
123
+
124
+ ask_or_update.call(:ci_pull_url, Text::Prompt::CI_PULL_URL, @scm.origin_url)
125
+ ask_or_update.call(:ci_push_url, Text::Prompt::CI_PUSH_URL, nil)
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,122 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ protected
6
+
7
+ def show_config_details(scope, config)
8
+ if !config || config.length == 0
9
+ say Text::Process::NO_CONFIG
10
+ else
11
+ say Text::Status::CONFIG_DETAILS % scope
12
+ config.each do |k,v|
13
+ say "#{k}=#{v}"
14
+ end
15
+ end
16
+ say Text::Process::CONFIG_EDIT_COMMANDS
17
+ end
18
+
19
+ def show_attributes(names_to_display, attributes)
20
+ names_to_display.each do |attr|
21
+ say Text::Status::ATTRIBUTE_DETAIL % [attr.gsub("_", " ").capitalize, attributes[attr]] if attributes[attr]
22
+ end
23
+ end
24
+
25
+ def show_keys_details(keys)
26
+ say Text::Status::KEYS_DETAILS
27
+ if keys.length == 0
28
+ say Text::Process::NO_KEYS
29
+ else
30
+ keys.each do |k|
31
+ if k["fingerprint"]
32
+ say((" %-18.18s %s" % [k["name"], k["fingerprint"]]).rstrip)
33
+ elsif k["pub"]
34
+ fingerprint = ssh_key_fingerprint(k["pub"])
35
+ if fingerprint then
36
+ say((" %-18.18s %s" % [k["name"], fingerprint]).rstrip)
37
+ else
38
+ say((" %-18.18s" % k["name"]).rstrip)
39
+ end
40
+ else
41
+ say((" %-18.18s" % k["name"]).rstrip)
42
+ end
43
+ end
44
+ end
45
+ say Text::Process::KEYS_EDIT_COMMANDS
46
+ end
47
+
48
+ def show_third_party_keys_details(user)
49
+ say ERB.new(Text::Status::USER_THIRD_PARTY_KEY_DETAILS).result(binding)
50
+ end
51
+
52
+ def show_ssh_config(dir=nil)
53
+ dir ||= ENV['TDDIUM_GEM_KEY_DIR']
54
+ dir ||= Default::SSH_OUTPUT_DIR
55
+
56
+ path = File.expand_path(File.join(dir, "identity.tddium.*"))
57
+
58
+ Dir[path].reject{|fn| fn =~ /.pub$/}.each do |fn|
59
+ say Text::Process::SSH_CONFIG % {:scm_host=>"git.solanolabs.com", :file=>fn}
60
+ end
61
+ end
62
+
63
+ def format_usage(usage)
64
+ "All tests: %.2f worker-hours ($%.2f)" % [
65
+ usage["hours"] || 0, usage["charge"] || 0]
66
+ end
67
+
68
+ def show_user_details(user)
69
+ current_suites = @tddium_api.get_suites
70
+ memberships = @tddium_api.get_memberships
71
+ account_usage = @tddium_api.get_usage
72
+
73
+ # Given the user is logged in, he should be able to
74
+ # use "tddium account" to display information about his account:
75
+ # Email address
76
+ # Account creation date
77
+ say ERB.new(Text::Status::USER_DETAILS).result(binding)
78
+
79
+ # Use "all_accounts" here instead of "participating_accounts" -- these
80
+ # are the accounts the user can administer.
81
+ user["all_accounts"].each do |acct|
82
+ id = acct['account_id'].to_i
83
+
84
+ say ERB.new(Text::Status::ACCOUNT_DETAILS).result(binding)
85
+
86
+ acct_suites = current_suites.select{|s| s['account_id'].to_i == id}
87
+ if acct_suites.empty? then
88
+ say ' ' + Text::Status::NO_SUITE
89
+ else
90
+ say ' ' + Text::Status::ALL_SUITES
91
+ suites = acct_suites.sort_by{|s| "#{s['org_name']}/#{s['repo_name']}"}
92
+ print_table suites.map {|suite|
93
+ repo_name = suite['repo_name']
94
+ if suite['org_name'] && suite['org_name'] != 'unknown'
95
+ repo_name = suite['org_name'] + '/' + repo_name
96
+ end
97
+ [repo_name, suite['branch'], suite['repo_url'] || '']
98
+ }, :indent => 4
99
+ end
100
+
101
+ # Uugh, json converts the keys to strings.
102
+ usage = account_usage[id.to_s]
103
+ if usage
104
+ say "\n Usage:"
105
+ say " Current month: " + format_usage(usage["current_month"])
106
+ say " Last month: " + format_usage(usage["last_month"])
107
+ end
108
+
109
+ acct_members = memberships.select{|m| m['account_id'].to_i == id}
110
+ if acct_members.length > 1
111
+ say "\n " + Text::Status::ACCOUNT_MEMBERS
112
+ print_table acct_members.map {|ar|
113
+ [ar['user_handle'], ar['user_email'], ar['role']]
114
+ }, :indent => 4
115
+ end
116
+ end
117
+
118
+ rescue TddiumClient::Error::Base => e
119
+ exit_failure e.message
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,179 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ protected
6
+
7
+ def update_suite(suite, options)
8
+ params = {}
9
+ prompt_suite_params(options, params, suite)
10
+
11
+ ask_or_update = lambda do |key, text, default|
12
+ params[key] = prompt(text, options[key], suite.fetch(key.to_s, default), options[:non_interactive])
13
+ end
14
+
15
+ ask_or_update.call(:campfire_room, Text::Prompt::CAMPFIRE_ROOM, '')
16
+ ask_or_update.call(:hipchat_room, Text::Prompt::HIPCHAT_ROOM, '')
17
+
18
+ @tddium_api.update_suite(suite['id'], params)
19
+ say Text::Process::UPDATED_SUITE
20
+ end
21
+
22
+ def suite_auto_configure
23
+ # Did the user set a configuration option on the command line?
24
+ # If so, auto-configure a new suite and re-configure an existing one
25
+ user_config = options.member?(:tool)
26
+
27
+ current_suite_id = @tddium_api.current_suite_id
28
+ if current_suite_id && !user_config then
29
+ current_suite = @tddium_api.get_suite_by_id(current_suite_id)
30
+ else
31
+ params = Hash.new
32
+ params[:branch] = @scm.current_branch
33
+ params[:repo_url] = @scm.origin_url
34
+ params[:repo_name] = @scm.repo_name
35
+ params[:scm] = @scm.scm_name
36
+ if options[:account] && !params.member?(:account_id) then
37
+ account_id = @tddium_api.get_account_id(options[:account])
38
+ params[:account_id] = account_id if account_id
39
+ end
40
+
41
+ tool_cli_populate(options, params)
42
+ defaults = {}
43
+
44
+ prompt_suite_params(options.merge({:non_interactive => true}), params, defaults)
45
+
46
+ # Create new suite if it does not exist yet
47
+ say Text::Process::CREATING_SUITE % [params[:repo_name], params[:branch]]
48
+
49
+ current_suite = @tddium_api.create_suite(params)
50
+
51
+ # Save the created suite
52
+ @api_config.set_suite(current_suite)
53
+ @api_config.write_config
54
+ end
55
+ return current_suite
56
+ end
57
+
58
+ def format_suite_details(suite)
59
+ # Given an API response containing a "suite" key, compose a string with
60
+ # important information about the suite
61
+ tddium_deploy_key_file_name = @api_config.tddium_deploy_key_file_name
62
+ details = ERB.new(Text::Status::SUITE_DETAILS).result(binding)
63
+ details
64
+ end
65
+
66
+ def suite_for_current_branch?
67
+ return true if @tddium_api.current_suite_id
68
+ say Text::Error::NO_SUITE_EXISTS % @scm.current_branch
69
+ false
70
+ end
71
+
72
+ def suite_for_default_branch?
73
+ return true if @tddium_api.default_suite_id
74
+ say Text::Error::NO_SUITE_EXISTS % @scm.default_branch
75
+ false
76
+ end
77
+
78
+ # Update the suite parameters from solano.yml
79
+ def update_suite_parameters!(current_suite, session_id=nil)
80
+ update_params = {}
81
+
82
+ update_params[:session_id] = session_id if session_id
83
+
84
+ pattern = configured_test_pattern
85
+ if pattern.is_a?(Array)
86
+ pattern = pattern.join(",")
87
+ end
88
+ if pattern && current_suite["test_pattern"] != pattern then
89
+ update_params[:test_pattern] = pattern
90
+ end
91
+
92
+ exclude_pattern = configured_test_exclude_pattern
93
+ if exclude_pattern.is_a?(Array)
94
+ exclude_pattern = exclude_pattern.join(",")
95
+ end
96
+ if exclude_pattern && current_suite["test_exclude_pattern"] != exclude_pattern then
97
+ update_params[:test_exclude_pattern] = exclude_pattern
98
+ end
99
+
100
+ ruby_version = sniff_ruby_version
101
+ if ruby_version && ruby_version != current_suite["ruby_version"] then
102
+ update_params[:ruby_version] = ruby_version
103
+ end
104
+
105
+ bundler_version = @repo_config["bundler_version"]
106
+ if bundler_version && bundler_version != current_suite["bundler_version"] then
107
+ update_params[:bundler_version] = bundler_version
108
+ end
109
+
110
+ test_configs = @repo_config["tests"] || []
111
+ if test_configs != (current_suite['test_configs'] || []) then
112
+ if test_configs != 'disable' && !test_configs.is_a?(Array) then
113
+ warn(Text::Warning::TEST_CONFIGS_MUST_BE_LIST)
114
+ test_configs = []
115
+ end
116
+ update_params[:test_configs] = test_configs
117
+ end
118
+
119
+ %w(golang java leiningen nodejs php python scala).each do |lang|
120
+ config_name = "#{lang}_config"
121
+ lang_config = @repo_config[lang] || {}
122
+ current_lang_config = current_suite[config_name] || {}
123
+ if lang_config != (current_suite[config_name] || {}) then
124
+ update_params[lang.to_sym] = lang_config
125
+ end
126
+ end
127
+
128
+ if !update_params.empty? then
129
+ @tddium_api.update_suite(@tddium_api.current_suite_id, update_params)
130
+ if update_params[:test_pattern]
131
+ say Text::Process::UPDATED_TEST_PATTERN % pattern
132
+ end
133
+ if update_params[:test_exclude_pattern]
134
+ say Text::Process::UPDATED_TEST_EXCLUDE_PATTERN % exclude_pattern
135
+ end
136
+ if update_params[:ruby_version]
137
+ say Text::Process::UPDATED_RUBY_VERSION % ruby_version
138
+ end
139
+ if update_params[:bundler_version]
140
+ say Text::Process::UPDATED_BUNDLER_VERSION % bundler_version
141
+ end
142
+ if update_params[:test_configs]
143
+ say Text::Process::UPDATED_TEST_CONFIGS % YAML.dump(test_configs)
144
+ say "(was:\n#{YAML.dump(current_suite['test_configs'])})\n"
145
+ end
146
+ if update_params[:python_config]
147
+ say Text::Process::UPDATED_PYTHON_CONFIG % YAML.dump(python_config)
148
+ end
149
+ if update_params[:golang_config]
150
+ say Text::Process::UPDATED_PYTHON_CONFIG % YAML.dump(golang_config)
151
+ end
152
+ if update_params[:java_config]
153
+ say Text::Process::UPDATED_PYTHON_CONFIG % YAML.dump(java_config)
154
+ end
155
+ end
156
+ end
157
+
158
+ def suite_remembered_option(options, key, default, &block)
159
+ remembered = false
160
+ if options[key] != default
161
+ result = options[key]
162
+ elsif remembered = current_suite_options[key.to_s]
163
+ result = remembered
164
+ remembered = true
165
+ else
166
+ result = default
167
+ end
168
+
169
+ if result then
170
+ msg = Text::Process::USING_SPEC_OPTION[key] % result
171
+ msg += Text::Process::REMEMBERED if remembered
172
+ msg += "\n"
173
+ say msg
174
+ yield result if block_given?
175
+ end
176
+ result
177
+ end
178
+ end
179
+ end