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