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