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,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