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,66 @@
1
+ # Copyright (c) 2014 Solano Labs All Rights Reserved
2
+
3
+ module ParamsHelper
4
+ include SolanoConstant
5
+
6
+ def default_host params
7
+ params['host'] || ENV['SOLANO_CLIENT_HOST'] || ENV['TDDIUM_CLIENT_HOST'] || 'ci.solanolabs.com'
8
+ end
9
+
10
+ def default_port params
11
+ if params['port']
12
+ params['port']
13
+ elsif ENV['SOLANO_CLIENT_PORT']
14
+ ENV['SOLANO_CLIENT_PORT'].to_i
15
+ elsif ENV['TDDIUM_CLIENT_PORT']
16
+ ENV['TDDIUM_CLIENT_PORT'].to_i
17
+ else
18
+ nil
19
+ end
20
+ end
21
+
22
+ def default_proto params
23
+ params['proto'] || ENV['SOLANO_CLIENT_PROTO'] || ENV['TDDIUM_CLIENT_PROTO'] || 'https'
24
+ end
25
+
26
+ def default_insecure params
27
+ if params.key?('insecure')
28
+ params['insecure']
29
+ elsif ENV['SOLANO_CLIENT_INSECURE'] || ENV['TDDIUM_CLIENT_INSECURE']
30
+ true
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ def load_params(defaults=true)
37
+ params = {}
38
+ if File.exists?(Default::PARAMS_PATH) then
39
+ File.open(Default::PARAMS_PATH, 'r') do |file|
40
+ params = JSON.parse file.read
41
+ end
42
+ elsif !defaults then
43
+ abort Text::Process::NOT_SAVED_OPTIONS
44
+ end
45
+ return params
46
+ end
47
+
48
+ def write_params options
49
+ begin
50
+ File.open(Default::PARAMS_PATH, File::CREAT|File::TRUNC|File::RDWR, 0600) do |file|
51
+ file.write options.to_json
52
+ end
53
+ say Text::Process::OPTIONS_SAVED
54
+ rescue Exception => e
55
+ say Text::Error::OPTIONS_NOT_SAVED
56
+ end
57
+ end
58
+
59
+ def display
60
+ store_params = load_params(false)
61
+ say 'Options:'
62
+ store_params.each do |k, v|
63
+ say " #{k.capitalize}:\t#{v}"
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,128 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SolanoCli < 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 = @solano_api.get_keys
14
+ if keys.empty? then
15
+ say Text::Process::SSH_KEY_NEEDED
16
+ keydata = prompt_ssh_key(nil)
17
+ result = @solano_api.set_keys({:keys => [keydata]})
18
+ return true
19
+ end
20
+ false
21
+ rescue SolanoError => 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
+ Solano::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 = @solano_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,136 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014, 2015 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SolanoCli < 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['SOLANO_GEM_KEY_DIR']
54
+ dir ||= Default::SSH_OUTPUT_DIR
55
+
56
+ path = File.expand_path(File.join(dir, "identity.solano.*"))
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 = @solano_api.get_suites
70
+ memberships = @solano_api.get_memberships
71
+ account_usage = @solano_api.get_usage
72
+
73
+ # Given the user is logged in, he should be able to
74
+ # use "solano 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
+ # Select those accounts that match org param or all if no org param
82
+
83
+ org = options['org']
84
+ regexp = org.nil? ? nil : Regexp.new(org.to_s)
85
+ account_list = user['all_accounts']
86
+ account_list.select! do |acct|
87
+ if regexp.nil? || regexp =~ acct[:account] then
88
+ acct
89
+ else
90
+ nil
91
+ end
92
+ end
93
+ account_list.compact!
94
+
95
+ account_list.each do |acct|
96
+ id = acct['account_id'].to_i
97
+
98
+ say ERB.new(Text::Status::ACCOUNT_DETAILS).result(binding)
99
+
100
+ acct_suites = current_suites.select{|s| s['account_id'].to_i == id}
101
+ if acct_suites.empty? then
102
+ say ' ' + Text::Status::NO_SUITE
103
+ else
104
+ say ' ' + Text::Status::ALL_SUITES
105
+ suites = acct_suites.sort_by{|s| "#{s['org_name']}/#{s['repo_name']}"}
106
+ print_table suites.map {|suite|
107
+ repo_name = suite['repo_name']
108
+ if suite['org_name'] && suite['org_name'] != 'unknown'
109
+ repo_name = suite['org_name'] + '/' + repo_name
110
+ end
111
+ [repo_name, suite['branch'], suite['repo_url'] || '']
112
+ }, :indent => 4
113
+ end
114
+
115
+ # Uugh, json converts the keys to strings.
116
+ usage = account_usage[id.to_s]
117
+ if usage
118
+ say "\n Usage:"
119
+ say " Current month: " + format_usage(usage["current_month"])
120
+ say " Last month: " + format_usage(usage["last_month"])
121
+ end
122
+
123
+ acct_members = memberships.select{|m| m['account_id'].to_i == id}
124
+ if acct_members.length > 1
125
+ say "\n " + Text::Status::ACCOUNT_MEMBERS
126
+ print_table acct_members.map {|ar|
127
+ [ar['user_handle'], ar['user_email'], ar['role']]
128
+ }, :indent => 4
129
+ end
130
+ end
131
+
132
+ rescue TddiumClient::Error::Base => e
133
+ exit_failure e.message
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,208 @@
1
+ # Copyright (c) 2011-2016 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SolanoCli < Thor
5
+ include SolanoConstant
6
+ extend ParamsHelper
7
+
8
+ attr_reader :scm
9
+ attr_reader :user_details
10
+
11
+ params = self.load_params
12
+
13
+ class_option :host, :type => :string,
14
+ :default => self.default_host(params),
15
+ :desc => "Solano CI app server hostname"
16
+
17
+ class_option :port, :type => :numeric,
18
+ :default => self.default_port(params),
19
+ :desc => "Solano CI app server port"
20
+
21
+ class_option :proto, :type => :string,
22
+ :default => self.default_proto(params),
23
+ :desc => "API Protocol"
24
+
25
+ class_option :insecure, :type => :boolean,
26
+ :default => self.default_insecure(params),
27
+ :desc => "Don't verify Solano CI app SSL server certificate"
28
+
29
+ def initialize(*args)
30
+ super(*args)
31
+
32
+ # TODO: read host from .solano file
33
+ # TODO: allow selecting which .solano "profile" to use
34
+ cli_opts = options[:insecure] ? { :insecure => true } : {}
35
+ cli_opts[:debug] = true
36
+ @tddium_client = TddiumClient::InternalClient.new(options[:host],
37
+ options[:port],
38
+ options[:proto],
39
+ 1,
40
+ caller_version,
41
+ cli_opts)
42
+ @tddium_clientv3 = TddiumClient::InternalClient.new(options[:host],
43
+ options[:port],
44
+ options[:proto],
45
+ "api/v3",
46
+ caller_version,
47
+ cli_opts)
48
+ @cli_options = options
49
+ end
50
+
51
+
52
+ require 'solano/cli/commands/account'
53
+ require 'solano/cli/commands/activate'
54
+ require 'solano/cli/commands/api'
55
+ require 'solano/cli/commands/heroku'
56
+ require 'solano/cli/commands/login'
57
+ require 'solano/cli/commands/logout'
58
+ require 'solano/cli/commands/password'
59
+ require 'solano/cli/commands/rerun'
60
+ require 'solano/cli/commands/find_failing'
61
+ require 'solano/cli/commands/spec'
62
+ require 'solano/cli/commands/stop'
63
+ require 'solano/cli/commands/suite'
64
+ require 'solano/cli/commands/status'
65
+ require 'solano/cli/commands/console'
66
+ require 'solano/cli/commands/keys'
67
+ require 'solano/cli/commands/config'
68
+ require 'solano/cli/commands/describe'
69
+ require 'solano/cli/commands/web'
70
+ require 'solano/cli/commands/hg'
71
+ require 'solano/cli/commands/server'
72
+ require 'solano/cli/commands/support'
73
+
74
+ map "-v" => :version
75
+ desc "version", "Print the solano gem version"
76
+ def version
77
+ say VERSION
78
+ end
79
+
80
+ # Thor has the wrong default behavior
81
+ def self.exit_on_failure?
82
+ return true
83
+ end
84
+
85
+ # Thor prints a confusing message for the "help" command in case an option
86
+ # follows in the wrong order before the command.
87
+ # This code patch overwrites this behavior and prints a better error message.
88
+ # For Thor version >= 0.18.0, release 2013-03-26.
89
+ if defined? no_commands
90
+ no_commands do
91
+ def invoke_command(command, *args)
92
+ begin
93
+ super
94
+ rescue InvocationError
95
+ if command.name == "help"
96
+ exit_failure Text::Error::CANT_INVOKE_COMMAND
97
+ else
98
+ raise
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ protected
106
+
107
+ def caller_version
108
+ "solano-#{VERSION}"
109
+ end
110
+
111
+ def configured_test_pattern
112
+ pattern = @repo_config["test_pattern"]
113
+
114
+ return nil if pattern.nil? || pattern.empty?
115
+ return pattern
116
+ end
117
+
118
+ def configured_test_exclude_pattern
119
+ pattern = @repo_config["test_exclude_pattern"]
120
+
121
+ return nil if pattern.nil? || pattern.empty?
122
+ return pattern
123
+ end
124
+
125
+ def solano_setup(params={})
126
+ if params[:deprecated] then
127
+ say Text::Error::COMMAND_DEPRECATED
128
+ end
129
+
130
+ # suite => repo => scm
131
+ params[:suite] = params[:suite] == true
132
+
133
+ params[:repo] = params[:repo] == true
134
+ params[:repo] ||= params[:suite]
135
+
136
+ params[:scm] = !params.member?(:scm) || params[:scm] == true
137
+ params[:scm] ||= params[:repo]
138
+
139
+ params[:login] = true unless params[:login] == false
140
+
141
+ $stdout.sync = true
142
+ $stderr.sync = true
143
+
144
+ set_shell
145
+
146
+ @scm, ok = Solano::SCM.configure
147
+ if params[:scm] && !ok then
148
+ say Text::Error::SCM_NOT_FOUND
149
+ exit_failure
150
+ end
151
+
152
+ @repo_config = RepoConfig.new(@scm)
153
+ if origin_url = @repo_config[:origin_url] then
154
+ @scm.default_origin_url = origin_url
155
+ end
156
+
157
+ host = @cli_options[:host]
158
+ @api_config = ApiConfig.new(@scm, @tddium_client, host, @cli_options)
159
+ @solano_api = SolanoAPI.new(@scm, @tddium_client, @api_config, {v3: @tddium_clientv3})
160
+
161
+ @api_config.set_api(@solano_api)
162
+
163
+ begin
164
+ @api_config.load_config
165
+ rescue ::Solano::SolanoError => e
166
+ say e.message
167
+ exit_failure
168
+ end
169
+
170
+ user_details = @solano_api.user_logged_in?(true, params[:login])
171
+ if params[:login] && user_details.nil? then
172
+ exit_failure
173
+ end
174
+
175
+ if params[:repo] then
176
+ if !@scm.repo? then
177
+ say Text::Error::SCM_NOT_A_REPOSITORY
178
+ exit_failure
179
+ end
180
+
181
+ if @scm.origin_url.nil? then
182
+ say Text::Error::SCM_NO_ORIGIN
183
+ exit_failure
184
+ end
185
+
186
+ begin
187
+ Solano::SCM.valid_repo_url?(@scm.origin_url)
188
+ rescue SolanoError => e
189
+ say e.message
190
+ exit_failure
191
+ end
192
+ end
193
+
194
+ if params[:suite] then
195
+ if @scm.current_branch.nil? then
196
+ say Text::Error::SCM_NO_BRANCH
197
+ exit_failure
198
+ end
199
+
200
+ if !suite_for_current_branch? then
201
+ exit_failure
202
+ end
203
+ end
204
+
205
+ @user_details = user_details
206
+ end
207
+ end
208
+ end