pebblescape 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +23 -0
- data/Rakefile +2 -0
- data/bin/pebbles +14 -0
- data/lib/pebbles.rb +23 -0
- data/lib/pebbles/api.rb +122 -0
- data/lib/pebbles/api/apps.rb +71 -0
- data/lib/pebbles/api/config_vars.rb +33 -0
- data/lib/pebbles/api/errors.rb +27 -0
- data/lib/pebbles/api/login.rb +14 -0
- data/lib/pebbles/api/releases.rb +33 -0
- data/lib/pebbles/api/user.rb +14 -0
- data/lib/pebbles/auth.rb +302 -0
- data/lib/pebbles/cli.rb +35 -0
- data/lib/pebbles/command.rb +256 -0
- data/lib/pebbles/command/apps.rb +225 -0
- data/lib/pebbles/command/auth.rb +85 -0
- data/lib/pebbles/command/base.rb +231 -0
- data/lib/pebbles/command/config.rb +147 -0
- data/lib/pebbles/command/help.rb +124 -0
- data/lib/pebbles/git.rb +69 -0
- data/lib/pebbles/helpers.rb +284 -0
- data/lib/pebbles/version.rb +3 -0
- data/pebbles.gemspec +27 -0
- metadata +142 -0
data/lib/pebbles/auth.rb
ADDED
@@ -0,0 +1,302 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "netrc"
|
3
|
+
require "pebbles"
|
4
|
+
require "pebbles/api"
|
5
|
+
require "pebbles/helpers"
|
6
|
+
|
7
|
+
class Pebbles::Auth
|
8
|
+
class << self
|
9
|
+
include Pebbles::Helpers
|
10
|
+
|
11
|
+
attr_accessor :credentials
|
12
|
+
|
13
|
+
def api
|
14
|
+
@api ||= begin
|
15
|
+
debug "Using API with key: #{password[0,6]}..."
|
16
|
+
Pebbles::API.new(default_params.merge(:api_key => password))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def login
|
21
|
+
delete_credentials
|
22
|
+
get_credentials
|
23
|
+
end
|
24
|
+
|
25
|
+
def logout
|
26
|
+
delete_credentials
|
27
|
+
end
|
28
|
+
|
29
|
+
# just a stub; will raise if not authenticated
|
30
|
+
def check
|
31
|
+
api.get_user
|
32
|
+
end
|
33
|
+
|
34
|
+
def default_host
|
35
|
+
"pebblesinspace.com"
|
36
|
+
end
|
37
|
+
|
38
|
+
def http_git_host
|
39
|
+
ENV['PEBBLES_HTTP_GIT_HOST'] || "git.#{host}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def git_host
|
43
|
+
ENV['PEBBLES_GIT_HOST'] || host
|
44
|
+
end
|
45
|
+
|
46
|
+
def host
|
47
|
+
ENV['PEBBLES_HOST'] || default_host
|
48
|
+
end
|
49
|
+
|
50
|
+
def subdomains
|
51
|
+
%w(api git)
|
52
|
+
end
|
53
|
+
|
54
|
+
def reauthorize
|
55
|
+
@credentials = ask_for_and_save_credentials
|
56
|
+
end
|
57
|
+
|
58
|
+
def user # :nodoc:
|
59
|
+
get_credentials[0]
|
60
|
+
end
|
61
|
+
|
62
|
+
def password # :nodoc:
|
63
|
+
get_credentials[1]
|
64
|
+
end
|
65
|
+
|
66
|
+
def api_key(user=get_credentials[0], password=get_credentials[1])
|
67
|
+
@api ||= Pebbles::API.new(default_params)
|
68
|
+
api_key = @api.post_login(user, password).body["api_key"]
|
69
|
+
@api = nil
|
70
|
+
api_key
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_credentials # :nodoc:
|
74
|
+
@credentials ||= (read_credentials || ask_for_and_save_credentials)
|
75
|
+
end
|
76
|
+
|
77
|
+
def delete_credentials
|
78
|
+
return
|
79
|
+
if netrc
|
80
|
+
subdomains.each do |sub|
|
81
|
+
netrc.delete("#{sub}.#{host}")
|
82
|
+
end
|
83
|
+
netrc.save
|
84
|
+
end
|
85
|
+
@api, @credentials = nil, nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def netrc_path
|
89
|
+
default = Netrc.default_path
|
90
|
+
encrypted = default + ".gpg"
|
91
|
+
if File.exists?(encrypted)
|
92
|
+
encrypted
|
93
|
+
else
|
94
|
+
default
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def netrc # :nodoc:
|
99
|
+
@netrc ||= begin
|
100
|
+
File.exists?(netrc_path) && Netrc.read(netrc_path)
|
101
|
+
rescue => error
|
102
|
+
case error.message
|
103
|
+
when /^Permission bits for/
|
104
|
+
abort("#{error.message}.\nYou should run `chmod 0600 #{netrc_path}` so that your credentials are NOT accessible by others.")
|
105
|
+
when /EACCES/
|
106
|
+
error("Error reading #{netrc_path}\n#{error.message}\nMake sure this user can read/write this file.")
|
107
|
+
else
|
108
|
+
error("Error reading #{netrc_path}\n#{error.message}\nYou may need to delete this file and run `pebbles login` to recreate it.")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def read_credentials
|
114
|
+
if ENV['PEBBLES_API_KEY']
|
115
|
+
['', ENV['PEBBLES_API_KEY']]
|
116
|
+
else
|
117
|
+
# read netrc credentials if they exist
|
118
|
+
if netrc
|
119
|
+
netrc["api.#{host}"]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def write_credentials
|
125
|
+
FileUtils.mkdir_p(File.dirname(netrc_path))
|
126
|
+
FileUtils.touch(netrc_path)
|
127
|
+
unless running_on_windows?
|
128
|
+
FileUtils.chmod(0600, netrc_path)
|
129
|
+
end
|
130
|
+
subdomains.each do |sub|
|
131
|
+
netrc["#{sub}.#{host}"] = self.credentials
|
132
|
+
end
|
133
|
+
netrc.save
|
134
|
+
end
|
135
|
+
|
136
|
+
def echo_off
|
137
|
+
with_tty do
|
138
|
+
system "stty -echo"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def echo_on
|
143
|
+
with_tty do
|
144
|
+
system "stty echo"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def ask_for_credentials
|
149
|
+
puts "Enter your Pebblescape credentials."
|
150
|
+
|
151
|
+
print "Email: "
|
152
|
+
user = ask
|
153
|
+
|
154
|
+
print "Password (typing will be hidden): "
|
155
|
+
password = running_on_windows? ? ask_for_password_on_windows : ask_for_password
|
156
|
+
[user, api_key(user, password)]
|
157
|
+
end
|
158
|
+
|
159
|
+
def ask_for_password_on_windows
|
160
|
+
require "Win32API"
|
161
|
+
char = nil
|
162
|
+
password = ''
|
163
|
+
|
164
|
+
while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
|
165
|
+
break if char == 10 || char == 13 # received carriage return or newline
|
166
|
+
if char == 127 || char == 8 # backspace and delete
|
167
|
+
password.slice!(-1, 1)
|
168
|
+
else
|
169
|
+
# windows might throw a -1 at us so make sure to handle RangeError
|
170
|
+
(password << char.chr) rescue RangeError
|
171
|
+
end
|
172
|
+
end
|
173
|
+
puts
|
174
|
+
return password
|
175
|
+
end
|
176
|
+
|
177
|
+
def ask_for_password
|
178
|
+
begin
|
179
|
+
echo_off
|
180
|
+
password = ask
|
181
|
+
puts
|
182
|
+
ensure
|
183
|
+
echo_on
|
184
|
+
end
|
185
|
+
return password
|
186
|
+
end
|
187
|
+
|
188
|
+
def ask_for_and_save_credentials
|
189
|
+
@credentials = ask_for_credentials
|
190
|
+
debug "Logged in as #{@credentials[0]} with key: #{@credentials[1][0,6]}..."
|
191
|
+
write_credentials
|
192
|
+
check
|
193
|
+
@credentials
|
194
|
+
rescue Pebbles::API::Errors::Unauthorized => e
|
195
|
+
delete_credentials
|
196
|
+
display "Authentication failed."
|
197
|
+
warn "WARNING: PEBBLES_API_KEY is set to an invalid key." if ENV['PEBBLES_API_KEY']
|
198
|
+
retry if retry_login?
|
199
|
+
exit 1
|
200
|
+
rescue => e
|
201
|
+
delete_credentials
|
202
|
+
raise e
|
203
|
+
end
|
204
|
+
|
205
|
+
def associate_or_generate_ssh_key
|
206
|
+
unless File.exists?("#{home_directory}/.ssh/id_rsa.pub")
|
207
|
+
display "Could not find an existing public key at ~/.ssh/id_rsa.pub"
|
208
|
+
display "Would you like to generate one? [Yn] ", false
|
209
|
+
unless ask.strip.downcase =~ /^n/
|
210
|
+
display "Generating new SSH public key."
|
211
|
+
generate_ssh_key("#{home_directory}/.ssh/id_rsa")
|
212
|
+
associate_key("#{home_directory}/.ssh/id_rsa.pub")
|
213
|
+
return
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
chosen = ssh_prompt
|
218
|
+
associate_key(chosen) if chosen
|
219
|
+
end
|
220
|
+
|
221
|
+
def ssh_prompt
|
222
|
+
public_keys = Dir.glob("#{home_directory}/.ssh/*.pub").sort
|
223
|
+
case public_keys.length
|
224
|
+
when 0
|
225
|
+
error("No SSH keys found")
|
226
|
+
return nil
|
227
|
+
when 1
|
228
|
+
display "Found an SSH public key at #{public_keys.first}"
|
229
|
+
display "Would you like to upload it to Pebblescape? [Yn] ", false
|
230
|
+
return ask.strip.downcase =~ /^n/ ? nil : public_keys.first
|
231
|
+
else
|
232
|
+
display "Found the following SSH public keys:"
|
233
|
+
public_keys.each_with_index do |key, index|
|
234
|
+
display "#{index+1}) #{File.basename(key)}"
|
235
|
+
end
|
236
|
+
display "Which would you like to use with your Pebblescape account? ", false
|
237
|
+
choice = ask.to_i - 1
|
238
|
+
chosen = public_keys[choice]
|
239
|
+
if choice == -1 || chosen.nil?
|
240
|
+
error("Invalid choice")
|
241
|
+
end
|
242
|
+
return chosen
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def generate_ssh_key(keyfile)
|
247
|
+
ssh_dir = File.dirname(keyfile)
|
248
|
+
FileUtils.mkdir_p ssh_dir, :mode => 0700
|
249
|
+
output = `ssh-keygen -t rsa -N "" -f \"#{keyfile}\" 2>&1`
|
250
|
+
if ! $?.success?
|
251
|
+
error("Could not generate key: #{output}")
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def associate_key(key)
|
256
|
+
action("Uploading SSH public key #{key}") do
|
257
|
+
if File.exists?(key)
|
258
|
+
api.post_key(File.read(key))
|
259
|
+
else
|
260
|
+
error("Could not upload SSH public key: key file '" + key + "' does not exist")
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def retry_login?
|
266
|
+
@login_attempts ||= 0
|
267
|
+
@login_attempts += 1
|
268
|
+
@login_attempts < 3
|
269
|
+
end
|
270
|
+
|
271
|
+
def base_host(host)
|
272
|
+
parts = URI.parse(full_host(host)).host.split(".")
|
273
|
+
return parts.first if parts.size == 1
|
274
|
+
parts[-2..-1].join(".")
|
275
|
+
end
|
276
|
+
|
277
|
+
def full_host(host)
|
278
|
+
scheme = debugging? ? 'http' : 'https'
|
279
|
+
(host =~ /^http/) ? host : "#{scheme}://api.#{host}"
|
280
|
+
end
|
281
|
+
|
282
|
+
def verify_host?(host)
|
283
|
+
return false if ENV["PEBBLES_SSL_VERIFY"] == "disable"
|
284
|
+
base_host(host) == "pebblesinspace.com"
|
285
|
+
end
|
286
|
+
|
287
|
+
protected
|
288
|
+
|
289
|
+
def default_params
|
290
|
+
uri = URI.parse(full_host(host))
|
291
|
+
params = {
|
292
|
+
:headers => {'User-Agent' => Pebbles.user_agent},
|
293
|
+
:host => uri.host,
|
294
|
+
:port => uri.port.to_s,
|
295
|
+
:scheme => uri.scheme,
|
296
|
+
:ssl_verify_peer => verify_host?(host)
|
297
|
+
}
|
298
|
+
|
299
|
+
params
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
data/lib/pebbles/cli.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'pebbles'
|
2
|
+
require 'pebbles/command'
|
3
|
+
require 'pebbles/git'
|
4
|
+
require 'pebbles/helpers'
|
5
|
+
require 'excon'
|
6
|
+
|
7
|
+
class Pebbles::CLI
|
8
|
+
extend Pebbles::Helpers
|
9
|
+
|
10
|
+
def self.start(*args)
|
11
|
+
$stdin.sync = true if $stdin.isatty
|
12
|
+
$stdout.sync = true if $stdout.isatty
|
13
|
+
Pebbles::Git.check_git_version
|
14
|
+
command = args.shift.strip rescue "help"
|
15
|
+
Pebbles::Command.load
|
16
|
+
Pebbles::Command.run(command, args)
|
17
|
+
rescue Errno::EPIPE => e
|
18
|
+
error(e.message)
|
19
|
+
rescue Interrupt => e
|
20
|
+
`stty icanon echo`
|
21
|
+
if ENV["PEBBLES_DEBUG"]
|
22
|
+
styled_error(e)
|
23
|
+
else
|
24
|
+
error("Command cancelled.", false)
|
25
|
+
end
|
26
|
+
rescue => error
|
27
|
+
if ENV["PEBBLES_DEBUG"]
|
28
|
+
raise
|
29
|
+
else
|
30
|
+
styled_error(error)
|
31
|
+
end
|
32
|
+
exit(1)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
require 'pebbles/helpers'
|
2
|
+
require 'pebbles/version'
|
3
|
+
require "optparse"
|
4
|
+
|
5
|
+
module Pebbles
|
6
|
+
module Command
|
7
|
+
class CommandFailed < RuntimeError; end
|
8
|
+
|
9
|
+
extend Pebbles::Helpers
|
10
|
+
|
11
|
+
def self.load
|
12
|
+
Dir[File.join(File.dirname(__FILE__), "command", "*.rb")].each do |file|
|
13
|
+
require file
|
14
|
+
end
|
15
|
+
unregister_commands_made_private_after_the_fact
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.commands
|
19
|
+
@@commands ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.command_aliases
|
23
|
+
@@command_aliases ||= {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.files
|
27
|
+
@@files ||= Hash.new {|hash,key| hash[key] = File.readlines(key).map {|line| line.strip}}
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.namespaces
|
31
|
+
@@namespaces ||= {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.register_command(command)
|
35
|
+
commands[command[:command]] = command
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.unregister_commands_made_private_after_the_fact
|
39
|
+
commands.values \
|
40
|
+
.select { |c| c[:klass].private_method_defined? c[:method] } \
|
41
|
+
.each { |c| commands.delete c[:command] }
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.register_namespace(namespace)
|
45
|
+
namespaces[namespace[:name]] = namespace
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.current_command
|
49
|
+
@current_command
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.current_command=(new_current_command)
|
53
|
+
@current_command = new_current_command
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.current_args
|
57
|
+
@current_args
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.current_options
|
61
|
+
@current_options ||= {}
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.global_options
|
65
|
+
@global_options ||= []
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.invalid_arguments
|
69
|
+
@invalid_arguments
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.shift_argument
|
73
|
+
# dup argument to get a non-frozen string
|
74
|
+
@invalid_arguments.shift.dup rescue nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.validate_arguments!
|
78
|
+
unless invalid_arguments.empty?
|
79
|
+
arguments = invalid_arguments.map {|arg| "\"#{arg}\""}
|
80
|
+
if arguments.length == 1
|
81
|
+
message = "Invalid argument: #{arguments.first}"
|
82
|
+
elsif arguments.length > 1
|
83
|
+
message = "Invalid arguments: "
|
84
|
+
message << arguments[0...-1].join(", ")
|
85
|
+
message << " and "
|
86
|
+
message << arguments[-1]
|
87
|
+
end
|
88
|
+
$stderr.puts(format_with_bang(message))
|
89
|
+
run(current_command, ["--help"])
|
90
|
+
exit(1)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.warnings
|
95
|
+
@warnings ||= []
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.display_warnings
|
99
|
+
unless warnings.empty?
|
100
|
+
$stderr.puts(warnings.uniq.map {|warning| " ! #{warning}"}.join("\n"))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.global_option(name, *args, &blk)
|
105
|
+
# args.sort.reverse gives -l, --long order
|
106
|
+
global_options << { :name => name.to_s, :args => args.sort.reverse, :proc => blk }
|
107
|
+
end
|
108
|
+
|
109
|
+
global_option :app, "-a", "--app APP" do |app|
|
110
|
+
raise OptionParser::InvalidOption.new(app) if app == "api" || app == "git"
|
111
|
+
end
|
112
|
+
|
113
|
+
global_option :confirm, "--confirm APP"
|
114
|
+
global_option :help, "-h", "--help"
|
115
|
+
global_option :remote, "-r", "--remote REMOTE"
|
116
|
+
|
117
|
+
def self.prepare_run(cmd, args=[])
|
118
|
+
command = parse(cmd)
|
119
|
+
|
120
|
+
if args.include?('-h') || args.include?('--help')
|
121
|
+
args.unshift(cmd) unless cmd =~ /^-.*/
|
122
|
+
cmd = 'help'
|
123
|
+
command = parse(cmd)
|
124
|
+
end
|
125
|
+
|
126
|
+
if cmd == '--version'
|
127
|
+
cmd = 'version'
|
128
|
+
command = parse(cmd)
|
129
|
+
end
|
130
|
+
|
131
|
+
@current_command = cmd
|
132
|
+
@normalized_args = []
|
133
|
+
|
134
|
+
opts = {}
|
135
|
+
invalid_options = []
|
136
|
+
|
137
|
+
parser = OptionParser.new do |parser|
|
138
|
+
# remove OptionParsers Officious['version'] to avoid conflicts
|
139
|
+
# see: https://github.com/ruby/ruby/blob/trunk/lib/optparse.rb#L814
|
140
|
+
parser.base.long.delete('version')
|
141
|
+
(global_options + (command && command[:options] || [])).each do |option|
|
142
|
+
parser.on(*option[:args]) do |value|
|
143
|
+
if option[:proc]
|
144
|
+
option[:proc].call(value)
|
145
|
+
end
|
146
|
+
opts[option[:name].gsub('-', '_').to_sym] = value
|
147
|
+
ARGV.join(' ') =~ /(#{option[:args].map {|arg| arg.split(' ', 2).first}.join('|')})/
|
148
|
+
@normalized_args << "#{option[:args].last.split(' ', 2).first} _"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
begin
|
154
|
+
parser.order!(args) do |nonopt|
|
155
|
+
invalid_options << nonopt
|
156
|
+
@normalized_args << '!'
|
157
|
+
end
|
158
|
+
rescue OptionParser::InvalidOption => ex
|
159
|
+
invalid_options << ex.args.first
|
160
|
+
@normalized_args << '!'
|
161
|
+
retry
|
162
|
+
end
|
163
|
+
|
164
|
+
args.concat(invalid_options)
|
165
|
+
|
166
|
+
@current_args = args
|
167
|
+
@current_options = opts
|
168
|
+
@invalid_arguments = invalid_options
|
169
|
+
|
170
|
+
if command
|
171
|
+
command_instance = command[:klass].new(args.dup, opts.dup)
|
172
|
+
|
173
|
+
if !@normalized_args.include?('--app _') && (implied_app = command_instance.app rescue nil)
|
174
|
+
@normalized_args << '--app _'
|
175
|
+
end
|
176
|
+
@normalized_command = [ARGV.first, @normalized_args.sort_by {|arg| arg.gsub('-', '')}].join(' ')
|
177
|
+
|
178
|
+
[ command_instance, command[:method] ]
|
179
|
+
else
|
180
|
+
error([
|
181
|
+
"`#{cmd}` is not a pebbles command.",
|
182
|
+
"See `pebbles help` for a list of available commands."
|
183
|
+
].compact.join("\n"))
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.run(cmd, arguments=[])
|
188
|
+
object, method = prepare_run(cmd, arguments.dup)
|
189
|
+
object.send(method)
|
190
|
+
rescue Pebbles::API::Errors::Unauthorized => e
|
191
|
+
retry_login = handle_auth_error(e)
|
192
|
+
retry if retry_login
|
193
|
+
rescue Pebbles::API::Errors::NotFound => e
|
194
|
+
error extract_error(e.response.body) {
|
195
|
+
e.response.body =~ /^([\w\s]+ not found).?$/ ? $1 : "Resource not found"
|
196
|
+
}
|
197
|
+
rescue Pebbles::API::Errors::Locked => e
|
198
|
+
app = e.response.headers[:x_confirmation_required]
|
199
|
+
if confirm_command(app, extract_error(e.response.body))
|
200
|
+
arguments << '--confirm' << app
|
201
|
+
retry
|
202
|
+
end
|
203
|
+
rescue Pebbles::API::Errors::Timeout
|
204
|
+
error "API request timed out. Please try again."
|
205
|
+
rescue Pebbles::API::Errors::Forbidden => e
|
206
|
+
error extract_error(e.response.body)
|
207
|
+
rescue Pebbles::API::Errors::ErrorWithResponse => e
|
208
|
+
error extract_error(e.response.body)
|
209
|
+
rescue CommandFailed => e
|
210
|
+
error e.message, false
|
211
|
+
rescue OptionParser::ParseError
|
212
|
+
commands[cmd] ? run("help", [cmd]) : run("help")
|
213
|
+
rescue Excon::Errors::SocketError, SocketError => e
|
214
|
+
error("Unable to connect to Pebblescape API, please check internet connectivity and try again.")
|
215
|
+
ensure
|
216
|
+
display_warnings
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.handle_auth_error(e)
|
220
|
+
if ENV['PEBBLES_API_KEY']
|
221
|
+
puts "Authentication failure with PEBBLES_API_KEY"
|
222
|
+
exit 1
|
223
|
+
else
|
224
|
+
puts "Authentication failure"
|
225
|
+
run "login"
|
226
|
+
true
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def self.parse(cmd)
|
231
|
+
commands[cmd] || commands[command_aliases[cmd]]
|
232
|
+
end
|
233
|
+
|
234
|
+
def self.extract_error(body, options={})
|
235
|
+
default_error = block_given? ? yield : "Internal server error."
|
236
|
+
parse_error_json(body) || parse_error_plain(body) || default_error
|
237
|
+
end
|
238
|
+
|
239
|
+
def self.parse_error_json(body)
|
240
|
+
json = json_decode(body.to_s) rescue false
|
241
|
+
case json
|
242
|
+
when Array
|
243
|
+
json.first.join(' ') # message like [['base', 'message']]
|
244
|
+
when Hash
|
245
|
+
json['error'] || json['error_message'] || json['message'] # message like {'error' => 'message'}
|
246
|
+
else
|
247
|
+
nil
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def self.parse_error_plain(body)
|
252
|
+
return unless body.respond_to?(:headers) && body.headers[:content_type].to_s.include?("text/plain")
|
253
|
+
body.to_s
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|