Class: HerokuGarden::CommandLine

Inherits:
Object
  • Object
show all
Defined in:
lib/herokugarden/command_line.rb

Overview

This wraps the Heroku::Client class with higher-level actions suitable for use from the command line.

Defined Under Namespace

Classes: CommandFailed

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#credentialsObject

Returns the value of attribute credentials.



194
195
196
# File 'lib/herokugarden/command_line.rb', line 194

def credentials
  @credentials
end

Instance Method Details

#add_collaborator(name, email, access) ⇒ Object



123
124
125
# File 'lib/herokugarden/command_line.rb', line 123

def add_collaborator(name, email, access)
	display heroku.add_collaborator(name, email, access)
end

#add_key(keyfile = nil) ⇒ Object



368
369
370
371
372
373
374
# File 'lib/herokugarden/command_line.rb', line 368

def add_key(keyfile=nil)
	keyfile ||= find_key
	key = File.read(keyfile)

	display "Uploading ssh public key #{keyfile}"
	heroku.add_key(key)
end

#ask_for_credentialsObject



241
242
243
244
245
246
247
248
249
250
251
# File 'lib/herokugarden/command_line.rb', line 241

def ask_for_credentials
	puts "Enter your Heroku credentials."

	print "Email: "
	user = gets.strip

	print "Password: "
	password = running_on_windows? ? ask_for_password_on_windows : ask_for_password

	[ user, password ]
end

#ask_for_passwordObject



270
271
272
273
274
275
276
# File 'lib/herokugarden/command_line.rb', line 270

def ask_for_password
	echo_off
	password = gets.strip
	puts
	echo_on
	return password
end

#ask_for_password_on_windowsObject



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/herokugarden/command_line.rb', line 253

def ask_for_password_on_windows
	require "Win32API"
	char = nil
	password = ''

	while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
		break if char == 10 || char == 13 # received carriage return or newline
		if char == 127 || char == 8 # backspace and delete
			password.slice!(-1, 1)
		else
			password << char.chr
		end
	end
	puts
	return password
end

#attempt_git_transition(gitconfig, app_name, domain) ⇒ Object



181
182
183
184
185
186
187
188
189
190
191
# File 'lib/herokugarden/command_line.rb', line 181

def attempt_git_transition(gitconfig, app_name, domain)
	heroku.host = domain
	if [nil, 'true'].include?(heroku.info(app_name, true)[:stack2])
		raise HerokuGarden::CommandLine::CommandFailed, "#{app_name} is running on the Heroku production platform.\nThis git repo URL is valid and does not need to be changed."
	else
		File.open(Dir.pwd + '/.git/config', 'w') do |f|
			f.write gitconfig.gsub("url = [email protected]:#{app_name}.git", "url = [email protected]:#{app_name}.git")
		end
		display "This git repo for #{app_name} has been updated to the new herokugarden.com URL."
	end
end

#authkeyObject



416
417
418
419
420
421
422
423
# File 'lib/herokugarden/command_line.rb', line 416

def authkey
	return @ssh_key if @ssh_key
	%w( rsa dsa ).each do |key_type|
		key = authkey_type(key_type)
		return key if key
	end
	raise "Your ssh public key was not found.  Make sure you have a rsa or dsa key in #{home_directory}/.ssh, or specify the full path to the keyfile."
end

#authkey_read(filename) ⇒ Object



412
413
414
# File 'lib/herokugarden/command_line.rb', line 412

def authkey_read(filename)
	File.read(filename) if File.exists?(filename)
end

#authkey_type(key_type) ⇒ Object



408
409
410
# File 'lib/herokugarden/command_line.rb', line 408

def authkey_type(key_type)
	authkey_read("#{home_directory}/.ssh/id_#{key_type}.pub")
end

#clone(args) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
# File 'lib/herokugarden/command_line.rb', line 78

def clone(args)
	name = args.shift.downcase.strip rescue ""
	if name.length == 0 or name.slice(0, 1) == '-'
		display "Usage: herokugarden clone <app>"
		display "(this command is deprecated in favor of using the git repo url directly)"
	else
		cmd = "git clone #{git_repo_for(name)}"
		display cmd
		system cmd
	end
end

#collaborators(args) ⇒ Object



119
120
121
# File 'lib/herokugarden/command_line.rb', line 119

def collaborators(args)
	sharing(args)
end

#create(args) ⇒ Object



51
52
53
54
55
# File 'lib/herokugarden/command_line.rb', line 51

def create(args)
	name = args.shift.downcase.strip rescue nil
	name = heroku.create(name, {})
	display "Created http://#{name}.#{heroku.host}/ | git@#{heroku.host}:#{name}.git"
end

#credentials_fileObject



214
215
216
# File 'lib/herokugarden/command_line.rb', line 214

def credentials_file
	"#{home_directory}/.heroku/herokugarden_credentials"
end

#delete_credentialsObject



315
316
317
# File 'lib/herokugarden/command_line.rb', line 315

def delete_credentials
	FileUtils.rm_f(credentials_file)
end

#destroy(args) ⇒ Object



90
91
92
93
94
95
96
97
98
# File 'lib/herokugarden/command_line.rb', line 90

def destroy(args)
	name = args.shift.strip.downcase rescue ""
	if name.length == 0 or name.slice(0, 1) == '-'
		display "Usage: herokugarden destroy <app>"
	else
		heroku.destroy(name)
		display "Destroyed #{name}"
	end
end

#display(msg) ⇒ Object

^^^ Deprecated



426
427
428
# File 'lib/herokugarden/command_line.rb', line 426

def display(msg)
	puts msg
end

#echo_offObject



233
234
235
# File 'lib/herokugarden/command_line.rb', line 233

def echo_off
	system "stty -echo"
end

#echo_onObject



237
238
239
# File 'lib/herokugarden/command_line.rb', line 237

def echo_on
	system "stty echo"
end

#execute(command, args) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
# File 'lib/herokugarden/command_line.rb', line 8

def execute(command, args)
	send(command, args)
rescue RestClient::Unauthorized
	display "Authentication failure"
rescue RestClient::ResourceNotFound
	display "Resource not found.  (Did you mistype the app name?)"
rescue RestClient::RequestFailed => e
	display extract_error(e.response)
rescue HerokuGarden::CommandLine::CommandFailed => e
	display e.message
end

#extract_error(response) ⇒ Object



434
435
436
437
438
439
440
# File 'lib/herokugarden/command_line.rb', line 434

def extract_error(response)
	return "Not found" if response.code.to_i == 404

	msg = parse_error_xml(response.body) rescue ''
	msg = 'Internal server error' if msg.empty?
	msg
end

#extract_key!Object



402
403
404
405
406
# File 'lib/herokugarden/command_line.rb', line 402

def extract_key!
	return unless key_path = extract_option(ARGV, ['-k', '--key'])
	raise "Please inform the full path for your ssh public key" if File.directory?(key_path)
	raise "Could not read ssh public key in #{key_path}" unless @ssh_key = authkey_read(key_path)
end

#extract_option(args, options, valid_values = nil) ⇒ Object



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/herokugarden/command_line.rb', line 319

def extract_option(args, options, valid_values=nil)
	values = options.is_a?(Array) ? options : [options]
	return unless opt_index = args.select { |a| values.include? a }.first
	opt_value = args[args.index(opt_index) + 1] rescue nil

	# remove option from args
	args.delete(opt_index)
	args.delete(opt_value)

	if valid_values
		opt_value = opt_value.downcase if opt_value
		raise CommandFailed, "Invalid value '#{opt_value}' for option #{values.last}" unless valid_values.include?(opt_value)
	end

	block_given? ? yield(opt_value) : opt_value
end

#find_keyObject

Raises:



386
387
388
389
390
391
392
# File 'lib/herokugarden/command_line.rb', line 386

def find_key
	%w(rsa dsa).each do |key_type|
		keyfile = "#{home_directory}/.ssh/id_#{key_type}.pub"
		return keyfile if File.exists? keyfile
	end
	raise CommandFailed, "No ssh public key found in #{home_directory}/.ssh/id_[rd]sa.pub.  You may want to specify the full path to the keyfile."
end

#get_credentialsObject

:nodoc:



218
219
220
221
222
223
224
225
# File 'lib/herokugarden/command_line.rb', line 218

def get_credentials    # :nodoc:
	return if @credentials
	unless @credentials = read_credentials
		@credentials = ask_for_credentials
		save_credentials
	end
	@credentials
end

#git_repo_for(name) ⇒ Object



142
143
144
# File 'lib/herokugarden/command_line.rb', line 142

def git_repo_for(name)
	"git@#{heroku.host}:#{name}.git"
end

#git_transition(args) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/herokugarden/command_line.rb', line 156

def git_transition(args)
	unless File.exists?(Dir.pwd + '/.git')
		raise HerokuGarden::CommandLine::CommandFailed, "The current directory is not a git repository.\nBe sure to cd into the app's directory and try again."
	end

	gitconfig = File.read(Dir.pwd + '/.git/config')
	if gitconfig.match(/url = git@herokugarden\.com:(.+)\.git/)
		raise HerokuGarden::CommandLine::CommandFailed, "This git repo for #{app_name} is already up to date and pointing at herokugarden.com."
	end
	if app_name_match = gitconfig.match(/url = git@heroku\.com:(.+)\.git/)
		app_name = app_name_match[1]
		begin
			attempt_git_transition(gitconfig, app_name, 'heroku.com')
		rescue RestClient::ResourceNotFound
			begin
				attempt_git_transition(gitconfig, app_name, 'herokugarden.com')
			rescue RestClient::ResourceNotFound
				raise HerokuGarden::CommandLine::CommandFailed, "The current directory does not contain a known Heroku app."
			end
		end
	else
		raise HerokuGarden::CommandLine::CommandFailed, "The current directory does not contain a known Heroku app."
	end
end

#herokuObject

:nodoc:



196
197
198
# File 'lib/herokugarden/command_line.rb', line 196

def heroku    # :nodoc:
	@heroku ||= init_heroku
end

#home_directoryObject



430
431
432
# File 'lib/herokugarden/command_line.rb', line 430

def home_directory
	running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
end

#info(args) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/herokugarden/command_line.rb', line 29

def info(args)
	name = args.shift.downcase.strip rescue ""
	if name.length == 0 or name.slice(0, 1) == '-'
		display "Usage: herokugarden info <app>"
	else
		attrs = heroku.info(name)
		display "=== #{attrs[:name]}"
		display "Web URL:        http://#{attrs[:name]}.#{heroku.host}/"
		display "Domain name:    http://#{attrs[:domain_name]}/" if attrs[:domain_name]
		display "Git Repo:       git@#{heroku.host}:#{attrs[:name]}.git"
		display "Mode:           #{ attrs[:production] == 'true' ? 'production' : 'development' }"
		display "Public:         #{ attrs[:'share-public'] == 'true' ? 'true' : 'false' }"

		first = true
		lead = "Collaborators:"
		attrs[:collaborators].each do |collaborator|
			display "#{first ? lead : ' ' * lead.length}  #{collaborator[:email]} (#{collaborator[:access]})"
			first = false
		end
	end
end

#init_herokuObject

:nodoc:



200
201
202
# File 'lib/herokugarden/command_line.rb', line 200

def init_heroku    # :nodoc:
	HerokuGarden::Client.new(user, password, ENV['HEROKU_HOST'] || 'herokugarden.com')
end

#keys(*args) ⇒ Object



336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/herokugarden/command_line.rb', line 336

def keys(*args)
	args = args.first  # something weird with ruby argument passing

	if args.empty?
		list_keys
		return
	end

	extract_option(args, '--add') do |keyfile|
		add_key(keyfile)
		return
	end
	extract_option(args, '--remove') do |arg|
		remove_key(arg)
		return
	end

	display "Usage: heroku keys [--add or --remove]"
end

#list(args) ⇒ Object



20
21
22
23
24
25
26
27
# File 'lib/herokugarden/command_line.rb', line 20

def list(args)
	list = heroku.list
	if list.size > 0
		display list.join("\n")
	else
		display "You have no apps."
	end
end

#list_collaborators(name) ⇒ Object



137
138
139
140
# File 'lib/herokugarden/command_line.rb', line 137

def list_collaborators(name)
	list = heroku.list_collaborators(name)
	display list.map { |c| "#{c[:email]} (#{c[:access]})" }.join("\n")
end

#list_keysObject



356
357
358
359
360
361
362
363
364
365
366
# File 'lib/herokugarden/command_line.rb', line 356

def list_keys
	keys = heroku.keys
	if keys.empty?
		display "No keys for #{user}"
	else
		display "=== #{keys.size} key#{keys.size > 1 ? 's' : ''} for #{user}"
		keys.each do |key|
			display key
		end
	end
end

#parse_error_xml(body) ⇒ Object



442
443
444
445
# File 'lib/herokugarden/command_line.rb', line 442

def parse_error_xml(body)
	xml_errors = REXML::Document.new(body).elements.to_a("//errors/error")
	xml_errors.map { |a| a.text }.join(" / ")
end

#passwordObject

:nodoc:



209
210
211
212
# File 'lib/herokugarden/command_line.rb', line 209

def password    # :nodoc:
	get_credentials
	@credentials[1]
end

#rake(args) ⇒ Object



146
147
148
149
150
151
152
153
154
# File 'lib/herokugarden/command_line.rb', line 146

def rake(args)
	app_name = args.shift.strip.downcase rescue ""
	cmd = args.join(' ')
	if app_name.length == 0 or cmd.length == 0
		display "Usage: herokugarden rake <app> <command>"
	else
		puts heroku.rake(app_name, cmd)
	end
end

#read_credentialsObject



227
228
229
230
231
# File 'lib/herokugarden/command_line.rb', line 227

def read_credentials
	if File.exists? credentials_file
		return File.read(credentials_file).split("\n")
	end
end

#remove_collaborator(name, email) ⇒ Object



132
133
134
135
# File 'lib/herokugarden/command_line.rb', line 132

def remove_collaborator(name, email)
	heroku.remove_collaborator(name, email)
	display "Collaborator removed"
end

#remove_key(arg) ⇒ Object



376
377
378
379
380
381
382
383
384
# File 'lib/herokugarden/command_line.rb', line 376

def remove_key(arg)
	if arg == 'all'
		heroku.remove_all_keys
		display "All keys removed."
	else
		heroku.remove_key(arg)
		display "Key #{arg} removed."
	end
end

#retry_login?Boolean

Returns:

  • (Boolean)


296
297
298
299
300
# File 'lib/herokugarden/command_line.rb', line 296

def retry_login?
	@login_attempts ||= 0
	@login_attempts += 1
	@login_attempts < 3
end

#running_on_windows?Boolean

Returns:

  • (Boolean)


447
448
449
# File 'lib/herokugarden/command_line.rb', line 447

def running_on_windows?
	RUBY_PLATFORM =~ /mswin32/
end

#save_credentialsObject



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/herokugarden/command_line.rb', line 278

def save_credentials
	begin
		write_credentials
		add_key
	rescue RestClient::Unauthorized => e
		delete_credentials
		raise e unless retry_login?

		display "\nAuthentication failed"
		@credentials = ask_for_credentials
		@heroku = init_heroku
		retry
	rescue Exception => e
		delete_credentials
		raise e
	end
end

#set_credentials_permissionsObject



310
311
312
313
# File 'lib/herokugarden/command_line.rb', line 310

def set_credentials_permissions
	FileUtils.chmod 0700, File.dirname(credentials_file)
	FileUtils.chmod 0600, credentials_file
end

#sharing(args) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/herokugarden/command_line.rb', line 100

def sharing(args)
	name = args.shift.strip.downcase rescue ""
	if name.length == 0 or name.slice(0, 1) == '-'
		display "Usage: herokugarden sharing <app>"
	else
		access = extract_option(args, '--access', %w( edit view )) || 'view'
		extract_option(args, '--add') do |email|
			return add_collaborator(name, email, access)
		end
		extract_option(args, '--update') do |email|
			return update_collaborator(name, email, access)
		end
		extract_option(args, '--remove') do |email|
			return remove_collaborator(name, email)
		end
		return list_collaborators(name)
	end
end

#update(args) ⇒ Object

Raises:



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/herokugarden/command_line.rb', line 57

def update(args)
	name = args.shift.downcase.strip rescue ""
	raise CommandFailed, "Invalid app name" if name.length == 0 or name.slice(0, 1) == '-'

	attributes = {}
	extract_option(args, '--name') do |new_name|
		attributes[:name] = new_name
	end
	extract_option(args, '--public', %w( true false )) do |public|
		attributes[:share_public] = (public == 'true')
	end
	extract_option(args, '--mode', %w( production development )) do |mode|
		attributes[:production] = (mode == 'production')
	end
	raise CommandFailed, "Nothing to update" if attributes.empty?
	heroku.update(name, attributes)

	app_name = attributes[:name] || name
	display "http://#{app_name}.#{heroku.host}/ updated"
end

#update_collaborator(name, email, access) ⇒ Object



127
128
129
130
# File 'lib/herokugarden/command_line.rb', line 127

def update_collaborator(name, email, access)
	heroku.update_collaborator(name, email, access)
	display "Collaborator updated"
end

#upload_authkey(*args) ⇒ Object

vvv Deprecated



395
396
397
398
399
400
# File 'lib/herokugarden/command_line.rb', line 395

def upload_authkey(*args)
	extract_key!
	display "Uploading ssh public key"
	display "(upload_authkey is deprecated, please use \"heroku keys --add\" instead)"
	heroku.add_key(authkey)
end

#userObject

:nodoc:



204
205
206
207
# File 'lib/herokugarden/command_line.rb', line 204

def user    # :nodoc:
	get_credentials
	@credentials[0]
end

#write_credentialsObject



302
303
304
305
306
307
308
# File 'lib/herokugarden/command_line.rb', line 302

def write_credentials
	FileUtils.mkdir_p(File.dirname(credentials_file))
	File.open(credentials_file, 'w') do |f|
		f.puts self.credentials
	end
	set_credentials_permissions
end