solano 1.31.0

Sign up to get free protection for your applications and to get access to all the features.
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