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,50 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014, 2015 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SolanoCli < Thor
5
+ desc "account [--org NAME]", "View account information"
6
+ method_option :org, type: :string
7
+ def account
8
+ user_details = solano_setup({:scm => false})
9
+
10
+ if user_details then
11
+ # User is already logged in, so just display the info
12
+ show_user_details(user_details)
13
+ else
14
+ exit_failure Text::Error::USE_ACTIVATE
15
+ end
16
+ end
17
+
18
+ desc "account:add [ROLE] [EMAIL]", "Authorize and invite a user to use your organization"
19
+ define_method "account:add" do |role, email|
20
+ solano_setup({:scm => false})
21
+
22
+ r = Regexp.new(/\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/)
23
+ if r !~ email then
24
+ exit_failure Text::Error::ADD_MEMBER_ERROR % [email, "Not a valid e-mail address: must be of the form user@host.domain"]
25
+ end
26
+
27
+ params = {:role=>role, :email=>email}
28
+ begin
29
+ say Text::Process::ADDING_MEMBER % [params[:email], params[:role]]
30
+ result = @solano_api.set_memberships(params)
31
+ say Text::Process::ADDED_MEMBER % email
32
+ rescue TddiumClient::Error::API => e
33
+ exit_failure Text::Error::ADD_MEMBER_ERROR % [email, e.message]
34
+ end
35
+ end
36
+
37
+ desc "account:remove [EMAIL]", "Remove a user from an organization"
38
+ define_method "account:remove" do |email|
39
+ solano_setup({:scm => false})
40
+
41
+ begin
42
+ say Text::Process::REMOVING_MEMBER % email
43
+ result = @solano_api.delete_memberships(email)
44
+ say Text::Process::REMOVED_MEMBER % email
45
+ rescue TddiumClient::Error::API => e
46
+ exit_failure Text::Error::REMOVE_MEMBER_ERROR % [email, e.message]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,16 @@
1
+ # Copyright (c) 2011-2015 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SolanoCli < Thor
5
+ desc "activate", "Activate an account (deprecated)"
6
+ method_option :email, :type => :string, :default => nil
7
+ method_option :password, :type => :string, :default => nil
8
+ method_option :ssh_key_file, :type => :string, :default => nil
9
+ def activate
10
+ say "To activate your account, please visit"
11
+ say "https://ci.solanolabs.com/"
12
+
13
+ solano_setup({:scm => false})
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ # Copyright (c) 2011-2015 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SolanoCli < Thor
5
+ desc "api:key", "Display Solano CI API Key"
6
+ define_method "api:key" do
7
+ user_details = solano_setup({:scm => false})
8
+ api_key = user_details['api_key']
9
+ if api_key.nil? then
10
+ exit_failure LIST_API_KEY_ERROR
11
+ end
12
+ say api_key
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,78 @@
1
+ # Copyright (c) 2011-2015 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SolanoCli < Thor
5
+ desc "config [suite | repo | org] [--org NAME]", "Display config variables.
6
+ The scope argument can be 'suite', 'repo', 'org'. The default is 'suite'."
7
+ method_option :account, :type => :string, :default => nil,
8
+ :aliases => %w(--org --organization)
9
+ def config(scope="suite")
10
+ params = {:repo => true}
11
+ if scope == 'suite' then
12
+ params[:suite] = true
13
+ end
14
+ if options[:account] then
15
+ params[:account] = options[:account]
16
+ end
17
+ solano_setup(params)
18
+
19
+ begin
20
+ config_details = @solano_api.get_config_key(scope)
21
+ show_config_details(scope, config_details['env'])
22
+ rescue TddiumClient::Error::API => e
23
+ exit_failure Text::Error::LIST_CONFIG_ERROR
24
+ rescue Exception => e
25
+ exit_failure e.message
26
+ end
27
+ end
28
+
29
+ desc "config:add [SCOPE] [KEY] [VALUE] [--org NAME]", "Set KEY=VALUE at SCOPE.
30
+ The scope argument can be 'suite', 'repo', 'org'."
31
+ method_option :account, :type => :string, :default => nil,
32
+ :aliases => %w(--org --organization)
33
+ define_method "config:add" do |scope, key, value|
34
+ params = {:repo => true}
35
+ if scope == 'suite' then
36
+ params[:suite] = true
37
+ end
38
+ if options[:account] then
39
+ params[:account] = options[:account]
40
+ end
41
+ solano_setup(params)
42
+
43
+ begin
44
+ say Text::Process::ADD_CONFIG % [key, value, scope]
45
+ result = @solano_api.set_config_key(scope, key, value)
46
+ say Text::Process::ADD_CONFIG_DONE % [key, value, scope]
47
+ rescue TddiumClient::Error::API => e
48
+ exit_failure Text::Error::ADD_CONFIG_ERROR
49
+ rescue Exception => e
50
+ exit_failure e.message
51
+ end
52
+ end
53
+
54
+ desc "config:remove [SCOPE] [KEY] [--org NAME]", "Remove config variable NAME from SCOPE."
55
+ method_option :account, :type => :string, :default => nil,
56
+ :aliases => %w(--org --organization)
57
+ define_method "config:remove" do |scope, key|
58
+ params = {:repo => true}
59
+ if scope == 'suite' then
60
+ params[:suite] = true
61
+ end
62
+ if options[:account] then
63
+ params[:account] = options[:account]
64
+ end
65
+ solano_setup(params)
66
+
67
+ begin
68
+ say Text::Process::REMOVE_CONFIG % [key, scope]
69
+ result = @solano_api.delete_config_key(scope, key)
70
+ say Text::Process::REMOVE_CONFIG_DONE % [key, scope]
71
+ rescue TddiumClient::Error::API => e
72
+ exit_failure Text::Error::REMOVE_CONFIG_ERROR
73
+ rescue Exception => e
74
+ exit_failure e.message
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,85 @@
1
+ # Copyright (c) 2011-2016 Solano Labs All Rights Reserved
2
+
3
+ require 'stringio'
4
+ require 'json'
5
+ require 'time'
6
+
7
+ module Solano
8
+ class SolanoCli < Thor
9
+ desc "console [COMMAND]", "Open an ssh Debug Console to the Solano worker, execute command if given else start shell."
10
+ method_option :json, :type => :boolean, :default => false
11
+ method_option :commit, :type => :string, :default => nil
12
+ def console(*cmd)
13
+ solano_setup({:repo => true})
14
+ origin = `git config --get remote.origin.url`.strip
15
+ session_result = @solano_api.call_api(:get, "/sessions", {:repo_url => origin})["sessions"]
16
+ if session_result.length > 0 then
17
+ session = session_result[0]
18
+ session_id = session["id"]
19
+ q_result = @solano_api.query_session(session_id).tddium_response["session"]
20
+ suite_id = q_result["suite_id"]
21
+ start_result = @solano_api.start_console(session_id, suite_id).tddium_response
22
+ session_id = start_result["interactive_session_id"] # the new interactive session's id
23
+ if start_result["message"] == "interactive started" then
24
+ say "Starting console session #{session_id}"
25
+ ssh_command = nil
26
+ failures = 0
27
+ while !ssh_command do
28
+ sleep(Default::SLEEP_TIME_BETWEEN_POLLS)
29
+ begin
30
+ session = @solano_api.query_session(session_id).tddium_response["session"]
31
+ failures = 0 # clear any previous transient failures
32
+ rescue Exception => e
33
+ failures += 1
34
+ say e.to_s
35
+ session = {}
36
+ end
37
+ if failures > 2 then
38
+ say "Errors connecting to server"
39
+ return # give up.
40
+ end
41
+ if session["stage2_ready"] && session["ssh_command"] then
42
+ if cmd then
43
+ ssh_command = "#{session['ssh_command']} -o StrictHostKeyChecking=no \"#{cmd.join(' ')}\""
44
+ else
45
+ ssh_command = "#{session['ssh_command']} -o StrictHostKeyChecking=no"
46
+ end
47
+ end
48
+ end
49
+ say "SSH Command is #{ssh_command}"
50
+ # exec terminates this ruby process and lets user control the ssh i/o
51
+ exec ssh_command
52
+ elsif start_result["message"] == "interactive already running"
53
+ dur = duration(Time.parse(start_result['session']['expires_at']) - Time.now)
54
+ say "Interactive session already running (expires in #{dur})"
55
+ session_id = start_result["session"]["id"]
56
+ session = @solano_api.query_session(session_id).tddium_response["session"]
57
+ if cmd then
58
+ exec "#{session['ssh_command']} -o StrictHostKeyChecking=no \"#{cmd.join(' ')}\""
59
+ else
60
+ exec "#{session['ssh_command']} -o StrictHostKeyChecking=no"
61
+ end
62
+ else
63
+ say start_result["message"]
64
+ end
65
+ else
66
+ say "Unable to find any previous sessions. Execute solano run first"
67
+ end
68
+ end
69
+
70
+ private
71
+ # return nice string version of hrs mins secs from time delta
72
+ def duration(d)
73
+ secs = d.to_int
74
+ mins = secs / 60
75
+ hours = mins / 60
76
+ if hours > 0 then
77
+ "#{hours} hours #{mins % 60} minutes"
78
+ elsif mins > 0 then
79
+ "#{mins} minutes #{secs % 60} seconds"
80
+ else
81
+ "#{secs} seconds"
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,104 @@
1
+ # Copyright (c) 2011-2015 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SolanoCli < Thor
5
+ map "show" => :describe
6
+ desc "describe [SESSION]", "Describe the state of a session, if it
7
+ is provided; otherwise, the latest session on current branch."
8
+ method_option :account, :type => :string, :default => nil,
9
+ :aliases => %w(--org --organization)
10
+ method_option :all, :type=>:boolean, :default=>false
11
+ method_option :type, :type=>:string, :default=>nil
12
+ method_option :json, :type=>:boolean, :default=>false
13
+ method_option :names, :type=>:boolean, :default=>false
14
+ method_option :verbose, :type=>:boolean, :default=>false
15
+ def describe(session_id=nil)
16
+ solano_setup({:repo => false})
17
+
18
+ status_message = ''
19
+ if !session_id then
20
+ # params to get the most recent session id on current branch
21
+ suite_params = {
22
+ :suite_id => @solano_api.current_suite_id,
23
+ :active => false,
24
+ :limit => 1
25
+ } if suite_for_current_branch?
26
+
27
+ sessions = suite_params ? @solano_api.get_sessions(suite_params) : []
28
+ if sessions.empty? then
29
+ exit_failure Text::Status::NO_INACTIVE_SESSION
30
+ end
31
+
32
+ session_id = sessions[0]['id']
33
+
34
+ session_status = sessions[0]['status'].upcase
35
+ session_commit = sessions[0]['commit']
36
+ current_commit = @scm.current_commit
37
+ if session_commit == current_commit
38
+ commit_message = "equal to your current commit"
39
+ else
40
+ cnt_ahead = @scm.number_of_commits(session_commit, current_commit)
41
+ if cnt_ahead == 0
42
+ cnt_behind = @scm.number_of_commits(current_commit, session_commit)
43
+ commit_message = "your workspace is behind by #{cnt_behind} commits"
44
+ else
45
+ commit_message = "your workspace is ahead by #{cnt_ahead} commits"
46
+ end
47
+ end
48
+
49
+ duration = sessions[0]['duration']
50
+ start_timeago = "%s ago" % Solano::TimeFormat.seconds_to_human_time(Time.now - Time.parse(sessions[0]["start_time"]))
51
+ if duration.nil?
52
+ finish_timeago = "no info about duration found, started #{start_timeago}"
53
+ elsif session_status == 'RUNNING'
54
+ finish_timeago = "in process, started #{start_timeago}"
55
+ else
56
+ finish_time = Time.parse(sessions[0]["start_time"]) + duration
57
+ finish_timeago = "%s ago" % Solano::TimeFormat.seconds_to_human_time(Time.now - finish_time)
58
+ end
59
+
60
+ status_message = Text::Status::SESSION_STATUS % [session_commit, commit_message, session_status, finish_timeago]
61
+ end
62
+
63
+ result = @solano_api.query_session_tests(session_id)
64
+
65
+ session_result = Hash.new
66
+ if options[:verbose] then
67
+ session_result = @solano_api.query_session(session_id)
68
+ end
69
+
70
+ filtered = result['session']['tests']
71
+ if !options[:all]
72
+ filtered = filtered.select{|x| x['status'] == 'failed'}
73
+ end
74
+
75
+ if options[:type]
76
+ filtered = filtered.select{|x| x['test_type'].downcase == options[:type].downcase}
77
+ end
78
+
79
+ if options[:json]
80
+ json = result['session']
81
+ json['session'] = session_result['session']
82
+ puts JSON.pretty_generate(json)
83
+ elsif options[:names]
84
+ say filtered.map{|x| x['test_name']}.join(" ")
85
+ else
86
+ filtered.sort!{|a,b| [a['test_type'], a['test_name']] <=> [b['test_type'], b['test_name']]}
87
+
88
+ say Text::Process::DESCRIBE_SESSION % [session_id, status_message, options[:all] ? 'all' : 'failed']
89
+
90
+ table =
91
+ [["Test", "Status", "Duration"],
92
+ ["----", "------", "--------"]] +
93
+ filtered.map do |x|
94
+ [
95
+ x['test_name'],
96
+ x['status'],
97
+ x['elapsed_time'] ? "#{x['elapsed_time']}s" : "-"
98
+ ]
99
+ end
100
+ print_table table
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,64 @@
1
+ # Copyright (c) 2011-2015 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SolanoCli < Thor
5
+ desc "find_failing FILES", "Find failing ordering for ruby specs by binary searching a failing test run"
6
+ desc "find_failing files+ failing_file", "Find out which file causes pollution / makes the failing file fail"
7
+ def find_failing(*files)
8
+ solano_setup({:repo => true})
9
+
10
+ failing = files.pop
11
+ if !files.include?(failing)
12
+ exit_failure "Files have to include the failing file, use the copy helper"
13
+ elsif files.size < 2
14
+ exit_failure "Files have to be more than 2, use the copy helper"
15
+ elsif !success?([failing])
16
+ exit_failure "#{failing} fails when run on it's own"
17
+ elsif success?(files)
18
+ exit_failure "tests pass locally"
19
+ else
20
+ loop do
21
+ a = remove_from(files, files.size / 2, :not => failing)
22
+ b = files - (a - [failing])
23
+ status, files = find_failing_set([a, b], failing)
24
+ if status == :finished
25
+ say "Fails when #{files.join(", ")} are run together"
26
+ break
27
+ elsif status == :continue
28
+ next
29
+ else
30
+ exit_failure "unable to isolate failure to 2 files"
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def find_failing_set(sets, failing)
39
+ sets.each do |set|
40
+ next if set == [failing]
41
+ if !success?(set)
42
+ if set.size == 2
43
+ return [:finished, set]
44
+ else
45
+ return [:continue, set]
46
+ end
47
+ end
48
+ end
49
+ return [:failure, []]
50
+ end
51
+
52
+ def remove_from(set, x, options)
53
+ set.dup.delete_if { |f| f != options[:not] && (x -= 1) >= 0 }
54
+ end
55
+
56
+ def success?(files)
57
+ command = "bundle exec ruby #{files.map { |f| "-r./#{f.sub(/\.rb$/, "")}" }.join(" ")} -e ''"
58
+ say "Running: #{command}"
59
+ status = system(command)
60
+ say "Status: #{status ? "Success" : "Failure"}"
61
+ status
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,17 @@
1
+ # Copyright (c) 2011-2015 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SolanoCli < Thor
5
+ desc "heroku", "Connect Heroku account with Solano CI (deprecated)"
6
+ method_option :email, :type => :string, :default => nil
7
+ method_option :password, :type => :string, :default => nil
8
+ method_option :ssh_key_file, :type => :string, :default => nil
9
+ method_option :app, :type => :string, :default => nil
10
+ def heroku
11
+ say "To activate your heroku account, please visit"
12
+ say "https://ci.solanolabs.com/"
13
+
14
+ solano_setup({:scm => false})
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,48 @@
1
+ # Copyright (c) 2012-2015 Solano Labs All Rights Reserved
2
+
3
+ require 'fileutils'
4
+
5
+ module Solano
6
+ class SolanoCli < Thor
7
+ desc "hg:mirror", "Construct local hg -> git mirror"
8
+ method_option :noop, :type => :boolean, :default => false
9
+ method_option :force, :type => :boolean, :default => false
10
+ define_method "hg:mirror" do |*args|
11
+ solano_setup({:repo => true, :deprecated => true})
12
+
13
+ if @scm.scm_name != 'hg' then
14
+ exit_failure("Current repository does not appear to be using Mercurial")
15
+ end
16
+
17
+ if @scm.origin_url.nil? then
18
+ exit_failure("Missing default path; please set default path in hgrc")
19
+ end
20
+
21
+ if File.exists?(@scm.mirror_path) then
22
+ if !options[:force] then
23
+ exit_failure("Mirror already exists; use --force to recreate")
24
+ end
25
+ if options[:noop] then
26
+ exit_failure("Running in no-op mode; not removing existing mirror")
27
+ end
28
+ FileUtils.rm_rf(@scm.mirror_path)
29
+ end
30
+
31
+ FileUtils.mkdir_p(@scm.mirror_path)
32
+
33
+ Solano::Scripts.prepend_script_path
34
+
35
+ clone_command = "git clone hg::#{@scm.root} #{@scm.mirror_path}"
36
+ # origin_command = "cd #{@scm.mirror_path} && git remote set-url origin #{@scm.origin_url}"
37
+
38
+ if options[:noop] then
39
+ puts "export PATH=#{ENV['PATH']}"
40
+ puts clone_command
41
+ # puts origin_command
42
+ else
43
+ Kernel.system(clone_command)
44
+ # Kernel.system(origin_command)
45
+ end
46
+ end
47
+ end
48
+ end