solano 1.31.0

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 (54) hide show
  1. checksums.yaml +15 -0
  2. data/bin/solano +29 -0
  3. data/bin/tddium +29 -0
  4. data/lib/solano.rb +19 -0
  5. data/lib/solano/agent.rb +3 -0
  6. data/lib/solano/agent/solano.rb +128 -0
  7. data/lib/solano/cli.rb +25 -0
  8. data/lib/solano/cli/api.rb +368 -0
  9. data/lib/solano/cli/commands/account.rb +50 -0
  10. data/lib/solano/cli/commands/activate.rb +16 -0
  11. data/lib/solano/cli/commands/api.rb +15 -0
  12. data/lib/solano/cli/commands/config.rb +78 -0
  13. data/lib/solano/cli/commands/console.rb +85 -0
  14. data/lib/solano/cli/commands/describe.rb +104 -0
  15. data/lib/solano/cli/commands/find_failing.rb +64 -0
  16. data/lib/solano/cli/commands/heroku.rb +17 -0
  17. data/lib/solano/cli/commands/hg.rb +48 -0
  18. data/lib/solano/cli/commands/keys.rb +81 -0
  19. data/lib/solano/cli/commands/login.rb +37 -0
  20. data/lib/solano/cli/commands/logout.rb +14 -0
  21. data/lib/solano/cli/commands/password.rb +26 -0
  22. data/lib/solano/cli/commands/rerun.rb +59 -0
  23. data/lib/solano/cli/commands/server.rb +21 -0
  24. data/lib/solano/cli/commands/spec.rb +401 -0
  25. data/lib/solano/cli/commands/status.rb +117 -0
  26. data/lib/solano/cli/commands/stop.rb +19 -0
  27. data/lib/solano/cli/commands/suite.rb +110 -0
  28. data/lib/solano/cli/commands/support.rb +24 -0
  29. data/lib/solano/cli/commands/web.rb +29 -0
  30. data/lib/solano/cli/config.rb +246 -0
  31. data/lib/solano/cli/params_helper.rb +66 -0
  32. data/lib/solano/cli/prompt.rb +128 -0
  33. data/lib/solano/cli/show.rb +136 -0
  34. data/lib/solano/cli/solano.rb +208 -0
  35. data/lib/solano/cli/suite.rb +104 -0
  36. data/lib/solano/cli/text_helper.rb +16 -0
  37. data/lib/solano/cli/timeformat.rb +21 -0
  38. data/lib/solano/cli/util.rb +132 -0
  39. data/lib/solano/constant.rb +581 -0
  40. data/lib/solano/scm.rb +18 -0
  41. data/lib/solano/scm/configure.rb +37 -0
  42. data/lib/solano/scm/git.rb +349 -0
  43. data/lib/solano/scm/git_log_parser.rb +67 -0
  44. data/lib/solano/scm/hg.rb +263 -0
  45. data/lib/solano/scm/hg_log_parser.rb +66 -0
  46. data/lib/solano/scm/scm.rb +119 -0
  47. data/lib/solano/scm/scm_stub.rb +9 -0
  48. data/lib/solano/scm/url.rb +75 -0
  49. data/lib/solano/script.rb +12 -0
  50. data/lib/solano/script/git-remote-hg +1258 -0
  51. data/lib/solano/ssh.rb +66 -0
  52. data/lib/solano/util.rb +63 -0
  53. data/lib/solano/version.rb +5 -0
  54. metadata +413 -0
@@ -0,0 +1,117 @@
1
+ # Copyright (c) 2011-2015 Solano Labs All Rights Reserved
2
+
3
+ require 'stringio'
4
+
5
+ module Solano
6
+ class SolanoCli < Thor
7
+ desc "status", "Display information about this suite, and any open dev sessions"
8
+ method_option :json, :type => :boolean, :default => false
9
+ method_option :commit, :type => :string, :default => nil
10
+ def status
11
+ solano_setup({:repo => true})
12
+
13
+ begin
14
+ # solano_setup asserts that we're in a supported SCM repo
15
+ origin_url = @scm.origin_url
16
+ repo_params = {
17
+ :active => true,
18
+ :repo_url => origin_url
19
+ }
20
+
21
+ if suite_for_current_branch? then
22
+ status_branch = @solano_api.current_branch
23
+ suite_params = {
24
+ :suite_id => @solano_api.current_suite_id,
25
+ :active => false,
26
+ :limit => 10
27
+ }
28
+ elsif suite_for_default_branch? then
29
+ status_branch = @solano_api.default_branch
30
+ say Text::Error::TRY_DEFAULT_BRANCH % status_branch
31
+ suite_params = {
32
+ :suite_id => @solano_api.default_suite_id,
33
+ :active => false,
34
+ :limit => 10
35
+ }
36
+ end
37
+
38
+ if options[:commit] then
39
+ repo_params[:last_commit_id] = options[:commit]
40
+ suite_params[:last_commit_id] = options[:commit]
41
+ end
42
+
43
+ if options[:json]
44
+ res = {}
45
+ res[:running] = { origin_url => @solano_api.get_sessions(repo_params) }
46
+ res[:history] = {
47
+ status_branch => @solano_api.get_sessions(suite_params)
48
+ } if suite_params
49
+ puts JSON.pretty_generate(res)
50
+ else
51
+ show_session_details(
52
+ status_branch,
53
+ repo_params,
54
+ Text::Status::NO_ACTIVE_SESSION,
55
+ Text::Status::ACTIVE_SESSIONS,
56
+ true
57
+ )
58
+ show_session_details(
59
+ status_branch,
60
+ suite_params,
61
+ Text::Status::NO_INACTIVE_SESSION,
62
+ Text::Status::INACTIVE_SESSIONS,
63
+ false
64
+ ) if suite_params
65
+ say Text::Process::RERUN_SESSION
66
+ end
67
+
68
+ rescue TddiumClient::Error::Base => e
69
+ exit_failure e.message
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def show_session_details(status_branch, params, no_session_prompt, all_session_prompt, include_branch)
76
+ current_sessions = @solano_api.get_sessions(params)
77
+
78
+ say ""
79
+ if current_sessions.empty? then
80
+ say no_session_prompt
81
+ else
82
+ commit_size = 0...7
83
+ head = @scm.current_commit[commit_size]
84
+
85
+ say all_session_prompt % (params[:suite_id] ? status_branch : "")
86
+ say ""
87
+ header = ["Session #", "Commit", ("Branch" if include_branch), "Status", "Duration", "Started"].compact
88
+ table = [header, header.map { |t| "-" * t.size }] + current_sessions.map do |session|
89
+ duration = "%ds" % session['duration']
90
+ start_timeago = "%s ago" % Solano::TimeFormat.seconds_to_human_time(Time.now - Time.parse(session["start_time"]))
91
+ status = session["status"]
92
+ if status.nil? || status.strip == "" then
93
+ status = 'unknown'
94
+ end
95
+
96
+ [
97
+ session["id"].to_s,
98
+ session["commit"] ? session['commit'][commit_size] : '- ',
99
+ (session["branch"] if include_branch),
100
+ status,
101
+ duration,
102
+ start_timeago
103
+ ].compact
104
+ end
105
+ say(capture_stdout { print_table table }.gsub(head, "\e[7m#{head}\e[0m"))
106
+ end
107
+ end
108
+
109
+ def capture_stdout
110
+ old, $stdout = $stdout, StringIO.new
111
+ yield
112
+ $stdout.string
113
+ ensure
114
+ $stdout = old
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2011-2015 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SolanoCli < Thor
5
+ desc "stop [SESSION]", "Stop session by id"
6
+ def stop(ls_id=nil)
7
+ solano_setup({:scm => false})
8
+ if ls_id then
9
+ begin
10
+ say "Stoping session #{ls_id} ..."
11
+ say @solano_api.stop_session(ls_id)['notice']
12
+ rescue
13
+ end
14
+ else
15
+ exit_failure 'Stop requires a session id -- e.g. `solano 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 Solano
4
+ class SolanoCli < 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
+ solano_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 = @solano_api.current_branch
39
+ end
40
+
41
+ suites = @solano_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
+ @solano_api.permanent_destroy_suite(suite['id'])
64
+ @api_config.delete_suite(suite['branch'])
65
+ @api_config.write_config
66
+
67
+ elsif @solano_api.current_suite_id then
68
+ # Suite ID set in solano config file
69
+ current_suite = @solano_api.get_suite_by_id(@solano_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 = @solano_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,24 @@
1
+ # Copyright (c) 2015 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SolanoCli < Thor
5
+ desc "support", "Generate support json dump"
6
+ define_method "support:dump" do |*args|
7
+ user_details = solano_setup({:login => false, :scm => false})
8
+
9
+ login_options = options.dup
10
+
11
+ support_data = {}
12
+
13
+ support_data['gem_version'] = Solano::VERSION
14
+ support_data['scm'] = @scm.support_data
15
+ support_data['user_details'] = user_details
16
+ support_data['api_config'] = @api_config.config
17
+
18
+ support_data = support_data.dup
19
+ ::Solano.sensitive(support_data)
20
+
21
+ puts JSON.pretty_generate(support_data)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ # Copyright (c) 2011-2015 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SolanoCli < Thor
5
+ desc "web [SESSION_ID]", "Open build report in web browser"
6
+ def web(*args)
7
+ session_id = args.first
8
+
9
+ params = {:login => false}
10
+ if session_id.nil? then
11
+ params[:scm] = true
12
+ params[:repo] = true
13
+ end
14
+
15
+ solano_setup(params)
16
+
17
+ if session_id
18
+ fragment = "1/reports/#{session_id}" if session_id =~ /^(\d+)(\.\w+)*$/
19
+ end
20
+ fragment ||= 'latest'
21
+
22
+ begin
23
+ Launchy.open("#{options[:proto]}://#{options[:host]}/#{fragment}")
24
+ rescue Launchy::Error => e
25
+ exit_failure e.message
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,246 @@
1
+ # Copyright (c) 2011-2015 Solano Labs All Rights Reserved
2
+
3
+ require 'yaml'
4
+
5
+ module Solano
6
+ class RepoConfig
7
+ include SolanoConstant
8
+ include ConfigHelper
9
+
10
+ def initialize(scm)
11
+ @scm = scm
12
+ @config = load_config
13
+ end
14
+
15
+ def [](key)
16
+ return @config[key.to_s]
17
+ end
18
+
19
+ def config_filename
20
+ @config_filename
21
+ end
22
+
23
+ def load_config
24
+ config, raw_config = read_config(false)
25
+ return config
26
+ end
27
+
28
+ def read_config(raise_error)
29
+ config = nil
30
+ raw_config = nil
31
+
32
+ root = @scm.root
33
+ cfgfile_pair = pick_config_pair(root, Config::CONFIG_PATHS)
34
+ cfgfile_pair_depr = pick_config_pair(root, Config::CONFIG_PATHS_DEPRECATED)
35
+
36
+ if cfgfile_pair && cfgfile_pair_depr then
37
+ abort Text::Error::CONFIG_PATHS_COLLISION % [cfgfile_pair, cfgfile_pair_depr]
38
+ end
39
+
40
+ cfgfile_pair = cfgfile_pair_depr if cfgfile_pair.nil?
41
+
42
+ if cfgfile_pair && cfgfile_pair.first then
43
+ cfgfile = cfgfile_pair.first
44
+ @config_filename = cfgfile_pair[1]
45
+ begin
46
+ raw_config = File.read(cfgfile)
47
+ if raw_config && raw_config !~ /\A\s*\z/ then
48
+ config = YAML.load(raw_config)
49
+ config = hash_stringify_keys(config)
50
+ config = config['solano'] || config['tddium'] || config
51
+ end
52
+ rescue Exception => e
53
+ message = Text::Warning::YAML_PARSE_FAILED % cfgfile
54
+ if raise_error then
55
+ raise ::Solano::SolanoError.new(message)
56
+ else
57
+ say message
58
+ end
59
+ end
60
+ end
61
+
62
+ config ||= Hash.new
63
+ raw_config ||= ''
64
+ return [config, raw_config]
65
+ end
66
+
67
+ private
68
+
69
+ def pick_config_pair(root, config_paths)
70
+ files = config_paths.map { |fn| [ File.join(root, fn), fn ] }
71
+ files.select { |p| File.exists?(p.first) }.first
72
+ end
73
+ end
74
+
75
+ class ApiConfig
76
+ include SolanoConstant
77
+
78
+ attr_reader :config, :cli_options
79
+
80
+ # BOTCH: should be a state object rather than entire CLI object
81
+ def initialize(scm, tddium_client, host, cli_options)
82
+ @scm = scm
83
+ @tddium_client = tddium_client
84
+ @host = host
85
+ @cli_options = cli_options
86
+ @config = Hash.new
87
+ end
88
+
89
+ # BOTCH: fugly
90
+ def set_api(solano_api)
91
+ @solano_api = solano_api
92
+ end
93
+
94
+ def logout
95
+ remove_solano_files
96
+ end
97
+
98
+ def populate_branches(branch)
99
+ suites = @solano_api.get_suites(:repo_url => @scm.origin_url, :branch=>branch)
100
+ suites.each do |ste|
101
+ set_suite(ste)
102
+ end
103
+ end
104
+
105
+ def get_branch(branch, var, options={})
106
+ if options['account'].nil? && @cli_options[:account] then
107
+ options['account'] = @cli_options[:account]
108
+ end
109
+
110
+ val = fetch_branch(branch, var, options)
111
+ return val unless val.nil?
112
+
113
+ populate_branches(branch)
114
+
115
+ return fetch_branch(branch, var, options)
116
+ end
117
+
118
+ def get_api_key(options = {})
119
+ options.any? ? load_config(options)['api_key'] : @config['api_key']
120
+ end
121
+
122
+ def set_api_key(api_key, user)
123
+ @config['api_key'] = api_key
124
+ end
125
+
126
+ def scm_ready_sleep
127
+ s = ENV["SOLANO_SCM_READY_SLEEP"] || Default::SCM_READY_SLEEP
128
+ s.to_f
129
+ end
130
+
131
+ def set_suite(suite)
132
+ id = suite['id']
133
+ branch = suite['branch']
134
+ return if id.nil? || branch.nil? || branch.empty?
135
+
136
+ keys = %w(id branch account repo_id ci_ssh_pubkey)
137
+ metadata = keys.inject({}) { |h, v| h[v] = suite[v]; h }
138
+
139
+ branches = @config["branches"] || {}
140
+ branches.merge!({id => metadata})
141
+ @config.merge!({"branches" => branches})
142
+ end
143
+
144
+ def delete_suite(branch, account=nil)
145
+ branches = @config["branches"] || {}
146
+ branches.delete_if do |k, v|
147
+ v['branch'] == branch && (account.nil? || v['account'] == account)
148
+ end
149
+ end
150
+
151
+ def load_config(options = {})
152
+ global_config = load_config_from_file(:global)
153
+ return global_config if options[:global]
154
+
155
+ repo_config = load_config_from_file
156
+ return repo_config if options[:repo]
157
+
158
+ @config = global_config.merge(repo_config)
159
+ end
160
+
161
+ def write_config
162
+ path = solano_file_name(:global)
163
+ File.open(path, File::CREAT|File::TRUNC|File::RDWR, 0600) do |file|
164
+ config = Hash.new
165
+ config['api_key'] = @config['api_key'] if @config.member?('api_key')
166
+ file.write(config.to_json)
167
+ end
168
+
169
+ path = solano_file_name(:repo)
170
+ File.open(path, File::CREAT|File::TRUNC|File::RDWR, 0600) do |file|
171
+ file.write(@config.to_json)
172
+ end
173
+
174
+ if @scm.repo? then
175
+ branch = @scm.current_branch
176
+ id = get_branch(branch, 'id', {})
177
+ suite = @config['branches'][id] rescue nil
178
+
179
+ if suite then
180
+ path = solano_deploy_key_file_name
181
+ File.open(path, File::CREAT|File::TRUNC|File::RDWR, 0644) do |file|
182
+ file.write(suite["ci_ssh_pubkey"])
183
+ end
184
+ end
185
+ write_scm_ignore # BOTCH: no need to write every time
186
+ end
187
+ end
188
+
189
+ def write_scm_ignore
190
+ path = @scm.ignore_path
191
+ content = File.exists?(path) ? File.read(path) : ''
192
+ unless content.include?(".solano*\n")
193
+ File.open(path, File::CREAT|File::APPEND|File::RDWR, 0644) do |file|
194
+ file.write(".solano*\n")
195
+ end
196
+ end
197
+ end
198
+
199
+ def solano_file_name(scope=:repo, kind='', root=nil)
200
+ ext = (@host == 'api.tddium.com' || @host == 'ci.solanolabs.com') ? '' : ".#{@host}"
201
+
202
+ case scope
203
+ when :repo
204
+ root ||= @scm.repo? ? @scm.root : Dir.pwd
205
+
206
+ when :global
207
+ root = ENV['HOME']
208
+ end
209
+
210
+ return File.join(root, ".solano#{kind}#{ext}")
211
+ end
212
+
213
+ def solano_deploy_key_file_name
214
+ return solano_file_name(:repo, '-deploy-key')
215
+ end
216
+
217
+ protected
218
+
219
+ def fetch_branch(branch, var, options)
220
+ h = @config['branches']
221
+ return nil unless h.is_a?(Hash)
222
+ h.each_pair do |id, data|
223
+ next unless data.is_a?(Hash)
224
+ branch_name = data['branch']
225
+ next unless branch_name == branch
226
+ if options.keys.all? { |k| data.member?(k) && data[k] == options[k] }
227
+ return data[var]
228
+ end
229
+ end
230
+ return nil
231
+ end
232
+
233
+ private
234
+
235
+ def remove_solano_files
236
+ [solano_file_name, solano_file_name(:global)].each do |solano_file_path|
237
+ File.delete(solano_file_path) if File.exists?(solano_file_path)
238
+ end
239
+ end
240
+
241
+ def load_config_from_file(solano_file_type = :repo)
242
+ path = solano_file_name(solano_file_type)
243
+ File.exists?(path) ? (JSON.parse(File.read(path)) rescue {}) : {}
244
+ end
245
+ end
246
+ end