nimbu 0.4 → 0.5.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.
data/lib/nimbu.rb CHANGED
@@ -1,28 +1,12 @@
1
1
  require "nimbu/version"
2
- require "nimbu/client"
2
+ require "nimbu-api"
3
3
 
4
4
  module Nimbu
5
5
  def self.debug=(value)
6
- @@debug = value
6
+ @debug = value
7
7
  end
8
8
 
9
9
  def self.debug
10
- @@debug
11
- end
12
-
13
- def self.development=(value)
14
- @@development = value
15
- end
16
-
17
- def self.development
18
- @@development
19
- end
20
-
21
- def self.v2=(value)
22
- @@v2 = value
23
- end
24
-
25
- def self.v2
26
- @@v2
10
+ @debug || false
27
11
  end
28
12
  end
data/lib/nimbu/auth.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require "yaml"
2
2
  require "nimbu"
3
- require "nimbu/client"
4
3
  require "nimbu/helpers"
5
4
 
6
5
  class Nimbu::Auth
@@ -12,9 +11,7 @@ class Nimbu::Auth
12
11
 
13
12
  def client
14
13
  @client ||= begin
15
- client = Nimbu::Client.new(user, password, host)
16
- client.on_warning { |msg| self.display("\n#{msg}\n\n") }
17
- client
14
+ Nimbu::Client.new(:oauth_token => token, :endpoint => host)
18
15
  end
19
16
  end
20
17
 
@@ -29,34 +26,38 @@ class Nimbu::Auth
29
26
 
30
27
  # just a stub; will raise if not authenticated
31
28
  def check
32
- client.list
29
+ client.sites.list
30
+ end
31
+
32
+ def host
33
+ ENV['NIMBU_HOST'] || "https://api.nimbu.io"
33
34
  end
34
35
 
35
36
  def default_host
36
- "getnimbu.com"
37
+ "https://api.nimbu.io"
37
38
  end
38
39
 
39
- def host
40
- @host ||= ENV['NIMBU_HOST'] || get_nimbu_host
40
+ def admin_host
41
+ @admin_host ||= host.gsub(/https?\:\/\/api\./,'')
42
+ end
43
+
44
+ def site
45
+ @site ||= ENV['NIMBU_SITE'] || get_nimbu_site
41
46
  end
42
47
 
43
48
  def theme
44
49
  @theme ||= ENV['NIMBU_THEME'] || get_nimbu_theme
45
50
  end
46
51
 
47
- def get_nimbu_host
48
- if Nimbu.development
49
- get_configuration[:development]
50
- else
51
- get_configuration[:hostname]
52
- end
52
+ def get_nimbu_site
53
+ get_configuration["site"]
53
54
  end
54
55
 
55
56
  def get_nimbu_theme
56
- get_configuration[:theme] || "default-theme"
57
+ get_configuration["theme"] || "default-theme"
57
58
  end
58
59
 
59
- def get_configuration # :nodoc:
60
+ def get_configuration
60
61
  @configuration ||= (read_configuration || ask_for_and_save_configuration)
61
62
  end
62
63
 
@@ -75,20 +76,75 @@ class Nimbu::Auth
75
76
  @host = nil
76
77
  end
77
78
 
78
- def ask_for_configuration
79
- puts "What is the hostname for this Nimbu site?"
80
- print "Hostname: "
81
- hostname = ask
79
+ def ask_for_configuration
82
80
 
83
- puts "What is the theme you are developing in this directory?"
84
- print "Theme (i.e. default): "
85
- theme = ask
81
+ subdomain = nil
82
+ sites = client.sites.list
86
83
 
87
- {:hostname => hostname, :theme => theme}
84
+ unless sites.respond_to?(:any?) && sites.any?
85
+ display "You don't have access to any Nimbu sites you can edit yet..."
86
+ display ""
87
+ display "Please visit http://nimbu.io, start your 30-day trial and discover our amazing platform!"
88
+ exit(1)
89
+ else
90
+ print_separator
91
+ display "\nLet's first setup the configuration for this directory..."
92
+ display "\nYou have access to following sites:\n"
93
+ sites.each_with_index do |site,i|
94
+ display " #{i+1}) #{site.name.white.bold} => http://#{site.domain}"
95
+ end
96
+ site_number = 0
97
+ retry_site = false
98
+ while site_number < 1 || site_number > sites.length
99
+ unless retry_site
100
+ print "\nOn which site would you like to work? "
101
+ else
102
+ print "\nPlease enter the number of your site (between 1-#{sites.length}): "
103
+ end
104
+ site_number_string = ask
105
+ site_number = site_number_string.to_i rescue 0
106
+ retry_site = true
107
+ end
108
+ puts ""
109
+ site = sites[site_number-1]
110
+ display "Site chosen => #{site.name.white.bold} (http://#{site.domain})"
111
+ subdomain = site.subdomain
112
+ @site = subdomain
113
+ end
114
+
115
+ themes = client.themes(:subdomain => subdomain).list
116
+ current_theme = if themes.length > 1
117
+ theme_number = 0
118
+ retry_theme = false
119
+ while theme_number < 1 || theme_number > themes.length
120
+ unless retry_theme
121
+ print "\nOn which theme would you like to work in this directory? "
122
+ else
123
+ print "\nPlease enter the number of your theme (between 1-#{themes.length}): "
124
+ end
125
+ theme_number_string = ask
126
+ theme_number = theme_number_string.to_i rescue 0
127
+ retry_theme = true
128
+ end
129
+ puts ""
130
+ display "Theme chosen => #{themes[theme_number-1].name}"
131
+ themes[theme_number-1]
132
+ else
133
+ themes.first
134
+ end
135
+ @theme = current_theme.short
136
+ print_separator
137
+
138
+ { "site" => subdomain, "theme" => current_theme.short }
88
139
  end
89
140
 
90
141
  def read_configuration
91
- File.exists?(configuration_file) and YAML::load(File.open( configuration_file ))
142
+ existing_config = YAML::load(File.open( configuration_file )) if File.exists?(configuration_file)
143
+ if existing_config && ! existing_config["site"].nil?
144
+ existing_config
145
+ else
146
+ nil
147
+ end
92
148
  end
93
149
 
94
150
  def write_configuration
@@ -102,23 +158,15 @@ class Nimbu::Auth
102
158
  @credentials = ask_for_and_save_credentials
103
159
  end
104
160
 
105
- def user # :nodoc:
106
- get_credentials[0]
107
- end
108
-
109
- def password # :nodoc:
110
- get_credentials[1]
111
- end
112
-
113
- def api_key
114
- Nimbu::Client.auth(user, password)["api_key"]
161
+ def token # :nodoc:
162
+ get_credentials
115
163
  end
116
164
 
117
165
  def credentials_file
118
166
  if host == default_host
119
167
  "#{home_directory}/.nimbu/credentials"
120
168
  else
121
- "#{home_directory}/.nimbu/credentials.#{CGI.escape(host)}"
169
+ "#{home_directory}/.nimbu/credentials.#{CGI.escape(host.gsub(/https?\:\/\//,''))}"
122
170
  end
123
171
  end
124
172
 
@@ -132,16 +180,17 @@ class Nimbu::Auth
132
180
  end
133
181
 
134
182
  def read_credentials
135
- if ENV['NIMBU_API_KEY']
136
- ['', ENV['NIMBU_API_KEY']]
183
+ credentials = File.read(credentials_file) if File.exists?(credentials_file)
184
+ if credentials && credentials =~ /^(bearer|oauth2|token) ([\w]+)$/i
185
+ $2
137
186
  else
138
- File.exists?(credentials_file) and File.read(credentials_file).split("\n")
187
+ nil
139
188
  end
140
189
  end
141
190
 
142
191
  def write_credentials
143
192
  FileUtils.mkdir_p(File.dirname(credentials_file))
144
- File.open(credentials_file, 'w') {|credentials| credentials.puts(self.credentials)}
193
+ File.open(credentials_file, 'w') {|credentials| credentials.print("token #{self.credentials}")}
145
194
  FileUtils.chmod(0700, File.dirname(credentials_file))
146
195
  FileUtils.chmod(0600, credentials_file)
147
196
  end
@@ -159,16 +208,21 @@ class Nimbu::Auth
159
208
  end
160
209
 
161
210
  def ask_for_credentials
162
- puts "Enter your Nimbu credentials."
163
-
164
- print "Email: "
211
+ print "Login: "
165
212
  user = ask
166
213
 
167
214
  print "Password: "
168
215
  password = running_on_windows? ? ask_for_password_on_windows : ask_for_password
169
- api_key = Nimbu::Client.auth(user, password)['api_key']
170
216
 
171
- [user, api_key]
217
+ begin
218
+ oauth = Nimbu::Client.new(:basic_auth => "#{user}:#{password}", :endpoint => host).authenticate
219
+ oauth.token
220
+ rescue Exception => e
221
+ if e.respond_to?(:http_status_code) && e.http_status_code == 401
222
+ display " => could not login... please check your username and/or password!\n\n"
223
+ end
224
+ nil
225
+ end
172
226
  end
173
227
 
174
228
  def ask_for_password_on_windows
@@ -202,15 +256,11 @@ class Nimbu::Auth
202
256
  end
203
257
 
204
258
  def ask_for_and_save_credentials
259
+ display "Please authenticate with Nimbu.io:"
205
260
  begin
206
261
  @credentials = ask_for_credentials
207
262
  write_credentials
208
263
  check
209
- rescue ::RestClient::Unauthorized, ::RestClient::ResourceNotFound => e
210
- delete_credentials
211
- display "Authentication failed."
212
- retry if retry_login?
213
- exit 1
214
264
  rescue Exception => e
215
265
  delete_credentials
216
266
  raise e
@@ -218,55 +268,16 @@ class Nimbu::Auth
218
268
  @credentials
219
269
  end
220
270
 
221
- def check_for_associated_ssh_key
222
- return unless client.keys.empty?
223
- associate_or_generate_ssh_key
224
- end
225
-
226
- def associate_or_generate_ssh_key
227
- public_keys = Dir.glob("#{home_directory}/.ssh/*.pub").sort
228
-
229
- case public_keys.length
230
- when 0 then
231
- display "Could not find an existing public key."
232
- display "Would you like to generate one? [Yn] ", false
233
- unless ask.strip.downcase == "n"
234
- display "Generating new SSH public key."
235
- generate_ssh_key("id_rsa")
236
- associate_key("#{home_directory}/.ssh/id_rsa.pub")
237
- end
238
- when 1 then
239
- display "Found existing public key: #{public_keys.first}"
240
- associate_key(public_keys.first)
241
- else
242
- display "Found the following SSH public keys:"
243
- public_keys.each_with_index do |key, index|
244
- display "#{index+1}) #{File.basename(key)}"
245
- end
246
- display "Which would you like to use with your Nimbu account? ", false
247
- chosen = public_keys[ask.to_i-1] rescue error("Invalid choice")
248
- associate_key(chosen)
249
- end
250
- end
251
-
252
- def generate_ssh_key(keyfile)
253
- ssh_dir = File.join(home_directory, ".ssh")
254
- unless File.exists?(ssh_dir)
255
- FileUtils.mkdir_p ssh_dir
256
- File.chmod(0700, ssh_dir)
257
- end
258
- `ssh-keygen -t rsa -N "" -f \"#{home_directory}/.ssh/#{keyfile}\" 2>&1`
259
- end
260
-
261
- def associate_key(key)
262
- display "Uploading SSH public key #{key}"
263
- client.add_key(File.read(key))
264
- end
265
-
266
271
  def retry_login?
267
272
  @login_attempts ||= 0
268
273
  @login_attempts += 1
269
274
  @login_attempts < 3
270
275
  end
276
+
277
+ def print_separator
278
+ print "\n"
279
+ 60.times { print "#"}
280
+ print "\n"
281
+ end
271
282
  end
272
283
  end
data/lib/nimbu/cli.rb CHANGED
@@ -1,12 +1,29 @@
1
1
  require "nimbu"
2
2
  require "nimbu/command"
3
+ require "nimbu/helpers"
3
4
 
4
5
  class Nimbu::CLI
5
6
 
7
+ extend Nimbu::Helpers
8
+
6
9
  def self.start(*args)
7
- command = args.shift.strip rescue "help"
8
- Nimbu::Command.load
9
- Nimbu::Command.run(command, args)
10
+ begin
11
+ if $stdin.isatty
12
+ $stdin.sync = true
13
+ end
14
+ if $stdout.isatty
15
+ $stdout.sync = true
16
+ end
17
+ command = args.shift.strip rescue "help"
18
+ Nimbu::Command.load
19
+ Nimbu::Command.run(command, args)
20
+ rescue Interrupt
21
+ `stty icanon echo`
22
+ error("Command cancelled.")
23
+ rescue => error
24
+ styled_error(error)
25
+ exit(1)
26
+ end
10
27
  end
11
28
 
12
29
  end
data/lib/nimbu/command.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  require 'nimbu/helpers'
2
2
  require 'nimbu/version'
3
+ require 'term/ansicolor'
3
4
  require "optparse"
4
5
 
6
+ class String
7
+ include Term::ANSIColor
8
+ end
9
+
5
10
  module Nimbu
6
11
  module Command
7
12
  class CommandFailed < RuntimeError; end
@@ -22,6 +27,10 @@ module Nimbu
22
27
  @@command_aliases ||= {}
23
28
  end
24
29
 
30
+ def self.files
31
+ @@files ||= Hash.new {|hash,key| hash[key] = File.readlines(key).map {|line| line.strip}}
32
+ end
33
+
25
34
  def self.namespaces
26
35
  @@namespaces ||= {}
27
36
  end
@@ -38,72 +47,70 @@ module Nimbu
38
47
  @current_command
39
48
  end
40
49
 
50
+ def self.current_command=(new_current_command)
51
+ @current_command = new_current_command
52
+ end
53
+
41
54
  def self.current_args
42
55
  @current_args
43
56
  end
44
57
 
45
58
  def self.current_options
46
- @current_options
59
+ @current_options ||= {}
47
60
  end
48
61
 
49
62
  def self.global_options
50
63
  @global_options ||= []
51
64
  end
52
65
 
53
- def self.global_option(name, *args)
54
- global_options << { :name => name, :args => args }
66
+ def self.global_option(name, *args, &blk)
67
+ global_options << { :name => name.to_s, :args => args.sort.reverse, :proc => blk }
68
+ end
69
+
70
+ def self.warnings
71
+ @warnings ||= []
72
+ end
73
+
74
+ def self.display_warnings
75
+ unless warnings.empty?
76
+ $stderr.puts(warnings.map {|warning| " ! #{warning}"}.join("\n"))
77
+ end
55
78
  end
56
79
 
57
- global_option :app, "--app APP", "-a"
58
- global_option :confirm, "--confirm APP"
59
80
  global_option :help, "--help", "-h"
60
- global_option :remote, "--remote REMOTE"
81
+ global_option :debug, "--debug"
61
82
 
62
83
  def self.prepare_run(cmd, args=[])
63
84
  command = parse(cmd)
64
85
 
65
- unless command
66
- if %w( -v --version ).include?(cmd)
67
- display Nimbu::VERSION
68
- exit
69
- end
70
-
71
- output_with_bang("`#{cmd}` is not a nimbu command.")
72
-
73
- distances = {}
74
- (commands.keys + command_aliases.keys).each do |suggestion|
75
- distance = string_distance(cmd, suggestion)
76
- distances[distance] ||= []
77
- distances[distance] << suggestion
78
- end
79
-
80
- if distances.keys.min < 4
81
- suggestions = distances[distances.keys.min].sort
82
- if suggestions.length == 1
83
- output_with_bang("Perhaps you meant `#{suggestions.first}`.")
84
- else
85
- output_with_bang("Perhaps you meant #{suggestions[0...-1].map {|suggestion| "`#{suggestion}`"}.join(', ')} or `#{suggestions.last}`.")
86
- end
87
- end
86
+ if args.include?('-h') || args.include?('--help')
87
+ args.unshift(cmd) unless cmd =~ /^-.*/
88
+ cmd = 'help'
89
+ command = parse(cmd)
90
+ end
88
91
 
89
- output_with_bang("See `nimbu help` for additional details.")
90
- exit(1)
92
+ if cmd == '--version'
93
+ cmd = 'version'
94
+ command = parse(cmd)
91
95
  end
92
96
 
93
97
  @current_command = cmd
98
+ @anonymized_args, @normalized_args = [], []
94
99
 
95
100
  opts = {}
96
101
  invalid_options = []
97
102
 
98
103
  parser = OptionParser.new do |parser|
99
- global_options.each do |global_option|
100
- parser.on(*global_option[:args]) do |value|
101
- opts[global_option[:name]] = value
102
- end
103
- end
104
- command[:options].each do |name, option|
105
- parser.on("-#{option[:short]}", "--#{option[:long]}", option[:desc]) do |value|
106
- opts[name.gsub("-", "_").to_sym] = value
104
+ parser.base.long.delete('version')
105
+ (global_options + (command && command[:options] || [])).each do |option|
106
+ parser.on(*option[:args]) do |value|
107
+ if option[:proc]
108
+ option[:proc].call(value)
109
+ end
110
+ opts[option[:name].gsub('-', '_').to_sym] = value
111
+ ARGV.join(' ') =~ /(#{option[:args].map {|arg| arg.split(' ', 2).first}.join('|')})/
112
+ @anonymized_args << "#{$1} _"
113
+ @normalized_args << "#{option[:args].last.split(' ', 2).first} _"
107
114
  end
108
115
  end
109
116
  end
@@ -111,53 +118,116 @@ module Nimbu
111
118
  begin
112
119
  parser.order!(args) do |nonopt|
113
120
  invalid_options << nonopt
121
+ @anonymized_args << '!'
122
+ @normalized_args << '!'
114
123
  end
115
124
  rescue OptionParser::InvalidOption => ex
116
125
  invalid_options << ex.args.first
126
+ @anonymized_args << '!'
127
+ @normalized_args << '!'
117
128
  retry
118
129
  end
119
130
 
120
- raise OptionParser::ParseError if opts[:help]
121
-
122
131
  args.concat(invalid_options)
123
132
 
124
133
  @current_args = args
125
134
  @current_options = opts
135
+ @invalid_arguments = invalid_options
136
+
137
+ if opts[:debug]
138
+ Nimbu.debug = true
139
+ end
140
+
141
+ @anonymous_command = [ARGV.first, *@anonymized_args].join(' ')
142
+ begin
143
+ usage_directory = "#{home_directory}/.nimbu/usage"
144
+ FileUtils.mkdir_p(usage_directory)
145
+ usage_file = usage_directory << "/#{Nimbu::VERSION}"
146
+ usage = if File.exists?(usage_file)
147
+ json_decode(File.read(usage_file))
148
+ else
149
+ {}
150
+ end
151
+ usage[@anonymous_command] ||= 0
152
+ usage[@anonymous_command] += 1
153
+ File.write(usage_file, json_encode(usage) + "\n")
154
+ rescue
155
+ # usage writing is not important, allow failures
156
+ end
157
+
158
+ if command
159
+ command_instance = command[:klass].new(args.dup, opts.dup)
126
160
 
127
- [ command[:klass].new(args.dup, opts.dup), command[:method] ]
161
+ if !@normalized_args.include?('--app _') && (implied_app = command_instance.app rescue nil)
162
+ @normalized_args << '--app _'
163
+ end
164
+ @normalized_command = [ARGV.first, @normalized_args.sort_by {|arg| arg.gsub('-', '')}].join(' ')
165
+
166
+ [ command_instance, command[:method] ]
167
+ else
168
+ error([
169
+ "`#{cmd}` is not a Nimbu command.",
170
+ suggestion(cmd, commands.keys + command_aliases.keys),
171
+ "See `Nimbu help` for a list of available commands."
172
+ ].compact.join("\n"))
173
+ end
128
174
  end
129
175
 
130
176
  def self.run(cmd, arguments=[])
131
- object, method = prepare_run(cmd, arguments.dup)
132
- object.send(method)
133
- rescue RestClient::Unauthorized
134
- puts "Authentication failure"
135
- unless ENV['HEROKU_API_KEY']
136
- run "login"
137
- retry
138
- end
139
- rescue RestClient::PaymentRequired => e
140
- retry if run('account:confirm_billing', arguments.dup)
141
- rescue RestClient::ResourceNotFound => e
142
- error extract_error(e.http_body) {
143
- e.http_body =~ /^[\w\s]+ not found$/ ? e.http_body : "Resource not found"
144
- }
145
- rescue RestClient::Locked => e
146
- app = e.response.headers[:x_confirmation_required]
147
- if confirm_command(app, extract_error(e.response.body))
148
- arguments << '--confirm' << app
149
- retry
177
+ begin
178
+ object, method = prepare_run(cmd, arguments.dup)
179
+ object.send(method)
180
+ rescue Interrupt, StandardError, SystemExit => error
181
+ # load likely error classes, as they may not be loaded yet due to defered loads
182
+ require 'rest_client'
183
+ raise(error)
150
184
  end
151
- rescue RestClient::RequestFailed => e
152
- error extract_error(e.http_body)
153
- rescue RestClient::RequestTimeout
154
- error "API request timed out. Please try again, or contact support@nimbu.com if this issue persists."
185
+ # rescue Nimbu::API::Errors::Unauthorized, RestClient::Unauthorized
186
+ # puts "Authentication failure"
187
+ # unless ENV['Nimbu_API_KEY']
188
+ # run "login"
189
+ # retry
190
+ # end
191
+ # rescue Nimbu::API::Errors::VerificationRequired, RestClient::PaymentRequired => e
192
+ # retry if Nimbu::Helpers.confirm_billing
193
+ # rescue Nimbu::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 RestClient::ResourceNotFound => e
198
+ # error extract_error(e.http_body) {
199
+ # e.http_body =~ /^([\w\s]+ not found).?$/ ? $1 : "Resource not found"
200
+ # }
201
+ # rescue Nimbu::API::Errors::Locked => e
202
+ # app = e.response.headers[:x_confirmation_required]
203
+ # if confirm_command(app, extract_error(e.response.body))
204
+ # arguments << '--confirm' << app
205
+ # retry
206
+ # end
207
+ # rescue RestClient::Locked => e
208
+ # app = e.response.headers[:x_confirmation_required]
209
+ # if confirm_command(app, extract_error(e.http_body))
210
+ # arguments << '--confirm' << app
211
+ # retry
212
+ # end
213
+ # rescue Nimbu::API::Errors::Timeout, RestClient::RequestTimeout
214
+ # error "API request timed out. Please try again, or contact support@Nimbu.com if this issue persists."
215
+ # rescue Nimbu::API::Errors::ErrorWithResponse => e
216
+ # error extract_error(e.response.body)
217
+ # rescue RestClient::RequestFailed => e
218
+ # error extract_error(e.http_body)
155
219
  rescue CommandFailed => e
156
220
  error e.message
157
- rescue OptionParser::ParseError => ex
221
+ rescue OptionParser::ParseError
158
222
  commands[cmd] ? run("help", [cmd]) : run("help")
159
- rescue Interrupt => e
160
- error "\n[exited]"
223
+ rescue Excon::Errors::SocketError => e
224
+ if e.message == 'getaddrinfo: nodename nor servname provided, or not known (SocketError)'
225
+ error("Unable to connect to Nimbu API, please check internet connectivity and try again.")
226
+ else
227
+ raise(e)
228
+ end
229
+ ensure
230
+ display_warnings
161
231
  end
162
232
 
163
233
  def self.parse(cmd)