artofmission-heroku 1.6.3

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.
Files changed (47) hide show
  1. data/README.md +66 -0
  2. data/Rakefile +107 -0
  3. data/bin/heroku +15 -0
  4. data/lib/heroku.rb +5 -0
  5. data/lib/heroku/client.rb +487 -0
  6. data/lib/heroku/command.rb +96 -0
  7. data/lib/heroku/commands/account.rb +13 -0
  8. data/lib/heroku/commands/addons.rb +109 -0
  9. data/lib/heroku/commands/app.rb +239 -0
  10. data/lib/heroku/commands/auth.rb +137 -0
  11. data/lib/heroku/commands/base.rb +133 -0
  12. data/lib/heroku/commands/bundles.rb +51 -0
  13. data/lib/heroku/commands/config.rb +55 -0
  14. data/lib/heroku/commands/db.rb +129 -0
  15. data/lib/heroku/commands/domains.rb +31 -0
  16. data/lib/heroku/commands/help.rb +148 -0
  17. data/lib/heroku/commands/keys.rb +49 -0
  18. data/lib/heroku/commands/logs.rb +11 -0
  19. data/lib/heroku/commands/maintenance.rb +13 -0
  20. data/lib/heroku/commands/plugins.rb +25 -0
  21. data/lib/heroku/commands/ps.rb +37 -0
  22. data/lib/heroku/commands/service.rb +23 -0
  23. data/lib/heroku/commands/sharing.rb +29 -0
  24. data/lib/heroku/commands/ssl.rb +33 -0
  25. data/lib/heroku/commands/version.rb +7 -0
  26. data/lib/heroku/helpers.rb +23 -0
  27. data/lib/heroku/plugin.rb +65 -0
  28. data/spec/base.rb +23 -0
  29. data/spec/client_spec.rb +366 -0
  30. data/spec/command_spec.rb +15 -0
  31. data/spec/commands/addons_spec.rb +47 -0
  32. data/spec/commands/app_spec.rb +175 -0
  33. data/spec/commands/auth_spec.rb +104 -0
  34. data/spec/commands/base_spec.rb +114 -0
  35. data/spec/commands/bundles_spec.rb +48 -0
  36. data/spec/commands/config_spec.rb +45 -0
  37. data/spec/commands/db_spec.rb +53 -0
  38. data/spec/commands/domains_spec.rb +31 -0
  39. data/spec/commands/keys_spec.rb +60 -0
  40. data/spec/commands/logs_spec.rb +21 -0
  41. data/spec/commands/maintenance_spec.rb +21 -0
  42. data/spec/commands/plugins_spec.rb +26 -0
  43. data/spec/commands/ps_spec.rb +16 -0
  44. data/spec/commands/sharing_spec.rb +32 -0
  45. data/spec/commands/ssl_spec.rb +25 -0
  46. data/spec/plugin_spec.rb +64 -0
  47. metadata +150 -0
@@ -0,0 +1,175 @@
1
+ require File.dirname(__FILE__) + '/../base'
2
+
3
+ module Heroku::Command
4
+ describe App do
5
+ before(:each) do
6
+ @cli = prepare_command(App)
7
+ end
8
+
9
+ it "shows app info, converting bytes to kbs/mbs" do
10
+ @cli.stub!(:args).and_return(['myapp'])
11
+ @cli.heroku.should_receive(:info).with('myapp').and_return({ :name => 'myapp', :collaborators => [], :addons => [], :repo_size => 2*1024, :database_size => 5*1024*1024 })
12
+ @cli.should_receive(:display).with('=== myapp')
13
+ @cli.should_receive(:display).with('Web URL: http://myapp.heroku.com/')
14
+ @cli.should_receive(:display).with('Repo size: 2k')
15
+ @cli.should_receive(:display).with('Data size: 5M')
16
+ @cli.info
17
+ end
18
+
19
+ it "shows app info using the --app syntax" do
20
+ @cli.stub!(:args).and_return(['--app', 'myapp'])
21
+ @cli.heroku.should_receive(:info).with('myapp').and_return({ :collaborators => [], :addons => []})
22
+ @cli.info
23
+ end
24
+
25
+ it "shows app info reading app from current git dir" do
26
+ @cli.stub!(:args).and_return([])
27
+ @cli.stub!(:extract_app_in_dir).and_return('myapp')
28
+ @cli.heroku.should_receive(:info).with('myapp').and_return({ :collaborators => [], :addons => []})
29
+ @cli.info
30
+ end
31
+
32
+ it "creates without a name" do
33
+ @cli.heroku.should_receive(:create).with(nil, {}).and_return("untitled-123")
34
+ @cli.create
35
+ end
36
+
37
+ it "creates with a name" do
38
+ @cli.stub!(:args).and_return([ 'myapp' ])
39
+ @cli.heroku.should_receive(:create).with('myapp', {}).and_return("myapp")
40
+ @cli.create
41
+ end
42
+
43
+ it "renames an app" do
44
+ @cli.stub!(:args).and_return([ 'myapp2' ])
45
+ @cli.heroku.should_receive(:update).with('myapp', { :name => 'myapp2' })
46
+ @cli.rename
47
+ end
48
+
49
+ it "runs a rake command on the app" do
50
+ @cli.stub!(:args).and_return(([ 'db:migrate' ]))
51
+ @cli.heroku.should_receive(:start).
52
+ with('myapp', 'rake db:migrate', attached=true).
53
+ and_return(['foo', 'bar', 'baz'])
54
+ @cli.rake
55
+ end
56
+
57
+ it "runs a single console command on the app" do
58
+ @cli.stub!(:args).and_return([ '2+2' ])
59
+ @cli.heroku.should_receive(:console).with('myapp', '2+2')
60
+ @cli.console
61
+ end
62
+
63
+ it "offers a console, opening and closing the session with the client" do
64
+ @console = mock('heroku console')
65
+ @cli.stub!(:console_history_read)
66
+ @cli.stub!(:console_history_add)
67
+ @cli.heroku.should_receive(:console).with('myapp').and_yield(@console)
68
+ Readline.should_receive(:readline).and_return('exit')
69
+ @cli.console
70
+ end
71
+
72
+ it "asks to restart servers" do
73
+ @cli.heroku.should_receive(:restart).with('myapp')
74
+ @cli.restart
75
+ end
76
+
77
+ it "scales dynos" do
78
+ @cli.stub!(:args).and_return(['+4'])
79
+ @cli.heroku.should_receive(:set_dynos).with('myapp', '+4').and_return(7)
80
+ @cli.dynos
81
+ end
82
+
83
+ it "destroys the app specified with --app if user confirms" do
84
+ @cli.stub!(:ask).and_return('y')
85
+ @cli.stub!(:args).and_return(['--app', 'myapp'])
86
+ @cli.heroku.stub!(:info).and_return({})
87
+ @cli.heroku.should_receive(:destroy).with('myapp')
88
+ @cli.destroy
89
+ end
90
+
91
+ it "doesn't destroy the app if the user doesn't confirms" do
92
+ @cli.stub!(:ask).and_return('no')
93
+ @cli.stub!(:args).and_return(['--app', 'myapp'])
94
+ @cli.heroku.stub!(:info).and_return({})
95
+ @cli.heroku.should_not_receive(:destroy)
96
+ @cli.destroy
97
+ end
98
+
99
+ it "doesn't destroy the app in the current dir" do
100
+ @cli.stub!(:extract_app).and_return('myapp')
101
+ @cli.heroku.should_not_receive(:destroy)
102
+ @cli.destroy
103
+ end
104
+
105
+ context "Git Integration" do
106
+ include SandboxHelper
107
+ before(:all) do
108
+ # setup a git dir to serve as a remote
109
+ @git = "/tmp/git_spec_#{Process.pid}"
110
+ FileUtils.mkdir_p(@git)
111
+ FileUtils.cd(@git) { |d| `git --bare init` }
112
+ end
113
+
114
+ after(:all) do
115
+ FileUtils.rm_rf(@git)
116
+ end
117
+
118
+ # setup sandbox in /tmp
119
+ before(:each) do
120
+ @sandbox = "/tmp/app_spec_#{Process.pid}"
121
+ FileUtils.mkdir_p(@sandbox)
122
+ bash "git init"
123
+ Dir.stub!(:pwd).and_return(@sandbox)
124
+ end
125
+
126
+ after(:each) do
127
+ FileUtils.rm_rf(@sandbox)
128
+ end
129
+
130
+ it "creates adding heroku to git remote" do
131
+ @cli.heroku.should_receive(:create).and_return('myapp')
132
+ @cli.create
133
+ bash("git remote").strip.should == 'heroku'
134
+ end
135
+
136
+ it "creates adding a custom git remote" do
137
+ @cli.stub!(:args).and_return([ 'myapp', '--remote', 'myremote' ])
138
+ @cli.heroku.should_receive(:create).and_return('myapp')
139
+ @cli.create
140
+ bash("git remote").strip.should == 'myremote'
141
+ end
142
+
143
+ it "doesn't add a git remote if it already exists" do
144
+ @cli.heroku.should_receive(:create).and_return('myapp')
145
+ bash "git remote add heroku #{@git}"
146
+ @cli.create
147
+ end
148
+
149
+ it "renames updating the corresponding heroku git remote" do
150
+ bash "git remote add github git@github.com:test/test.git"
151
+ bash "git remote add production git@heroku.com:myapp.git"
152
+ bash "git remote add staging git@heroku.com:myapp-staging.git"
153
+
154
+ @cli.heroku.stub!(:update)
155
+ @cli.stub!(:args).and_return([ 'myapp2' ])
156
+ @cli.rename
157
+ remotes = bash("git remote -v")
158
+ remotes.should include('git@github.com:test/test.git')
159
+ remotes.should include('git@heroku.com:myapp-staging.git')
160
+ remotes.should include('git@heroku.com:myapp2.git')
161
+ remotes.should_not include('git@heroku.com:myapp.git')
162
+ end
163
+
164
+ it "destroys removing any remotes pointing to the app" do
165
+ bash("git remote add heroku git@heroku.com:myapp.git")
166
+ @cli.stub!(:args).and_return(['--app', 'myapp'])
167
+ @cli.heroku.stub!(:info).and_return({})
168
+ @cli.stub!(:ask).and_return('y')
169
+ @cli.heroku.should_receive(:destroy)
170
+ @cli.destroy
171
+ bash("git remote").strip.should == ''
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,104 @@
1
+ require File.dirname(__FILE__) + '/../base'
2
+
3
+ module Heroku::Command
4
+ describe Auth do
5
+ before do
6
+ @cli = prepare_command(Auth)
7
+ @sandbox = "#{Dir.tmpdir}/cli_spec_#{Process.pid}"
8
+ File.open(@sandbox, "w") { |f| f.write "user\npass\n" }
9
+ @cli.stub!(:credentials_file).and_return(@sandbox)
10
+ @cli.stub!(:running_on_a_mac?).and_return(false)
11
+ end
12
+
13
+ after do
14
+ FileUtils.rm_rf(@sandbox)
15
+ end
16
+
17
+ it "reads credentials from the credentials file" do
18
+ @cli.read_credentials.should == %w(user pass)
19
+ end
20
+
21
+ it "takes the user from the first line and the password from the second line" do
22
+ @cli.user.should == 'user'
23
+ @cli.password.should == 'pass'
24
+ end
25
+
26
+ it "asks for credentials when the file doesn't exist" do
27
+ FileUtils.rm_rf(@sandbox)
28
+ @cli.should_receive(:ask_for_credentials).and_return([ 'u', 'p'])
29
+ @cli.should_receive(:save_credentials)
30
+ @cli.get_credentials.should == [ 'u', 'p' ]
31
+ end
32
+
33
+ it "writes the credentials to a file" do
34
+ @cli.stub!(:credentials).and_return(['one', 'two'])
35
+ @cli.should_receive(:set_credentials_permissions)
36
+ @cli.write_credentials
37
+ File.read(@sandbox).should == "one\ntwo\n"
38
+ end
39
+
40
+ it "sets ~/.heroku/credentials to be readable only by the user" do
41
+ unless RUBY_PLATFORM =~ /mswin32|mingw32/
42
+ sandbox = "#{Dir.tmpdir}/cli_spec_#{Process.pid}"
43
+ FileUtils.rm_rf(sandbox)
44
+ FileUtils.mkdir_p(sandbox)
45
+ fname = "#{sandbox}/file"
46
+ system "touch #{fname}"
47
+ @cli.stub!(:credentials_file).and_return(fname)
48
+ @cli.set_credentials_permissions
49
+ File.stat(sandbox).mode.should == 040700
50
+ File.stat(fname).mode.should == 0100600
51
+ end
52
+ end
53
+
54
+ it "writes credentials and uploads authkey when credentials are saved" do
55
+ @cli.stub!(:credentials)
56
+ @cli.should_receive(:write_credentials)
57
+ Heroku::Command.should_receive(:run_internal).with('keys:add', [])
58
+ @cli.save_credentials
59
+ end
60
+
61
+ it "preserves the args when running keys:add" do
62
+ @cli.stub!(:credentials)
63
+ @cli.stub!(:args).and_return(['mykey.pub'])
64
+ Heroku::Command.should_receive(:run_internal).with('keys:add', ['mykey.pub'])
65
+ @cli.save_credentials
66
+ end
67
+
68
+ it "save_credentials deletes the credentials when the upload authkey is unauthorized" do
69
+ @cli.stub!(:write_credentials)
70
+ @cli.stub!(:retry_login?).and_return(false)
71
+ Heroku::Command.should_receive(:run_internal).with('keys:add', []).and_raise(RestClient::Unauthorized)
72
+ @cli.should_receive(:delete_credentials)
73
+ lambda { @cli.save_credentials }.should raise_error(RestClient::Unauthorized)
74
+ end
75
+
76
+ it "save_credentials deletes the credentials when there's no authkey" do
77
+ @cli.stub!(:write_credentials)
78
+ Heroku::Command.should_receive(:run_internal).with('keys:add', []).and_raise(RuntimeError)
79
+ @cli.should_receive(:delete_credentials)
80
+ lambda { @cli.save_credentials }.should raise_error
81
+ end
82
+
83
+ it "save_credentials deletes the credentials when the authkey is weak" do
84
+ @cli.stub!(:write_credentials)
85
+ Heroku::Command.should_receive(:run_internal).with('keys:add', []).and_raise(RestClient::RequestFailed)
86
+ @cli.should_receive(:delete_credentials)
87
+ lambda { @cli.save_credentials }.should raise_error
88
+ end
89
+
90
+ it "asks for login again when not authorized, for three times" do
91
+ @cli.stub!(:read_credentials)
92
+ @cli.stub!(:write_credentials)
93
+ @cli.stub!(:delete_credentials)
94
+ Heroku::Command.stub!(:run_internal).with('keys:add', []).and_raise(RestClient::Unauthorized)
95
+ @cli.should_receive(:ask_for_credentials).exactly(4).times
96
+ lambda { @cli.save_credentials }.should raise_error(RestClient::Unauthorized)
97
+ end
98
+
99
+ it "deletes the credentials file" do
100
+ FileUtils.should_receive(:rm_f).with(@cli.credentials_file)
101
+ @cli.delete_credentials
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,114 @@
1
+ require File.dirname(__FILE__) + '/../base'
2
+
3
+ module Heroku::Command
4
+ describe Base do
5
+ before do
6
+ @args = [1, 2]
7
+ @base = Base.new(@args)
8
+ @base.stub!(:display)
9
+ @client = mock('heroku client', :host => 'heroku.com')
10
+ end
11
+
12
+ it "initializes the heroku client with the Auth command" do
13
+ Heroku::Command.should_receive(:run_internal).with('auth:client', @args)
14
+ @base.heroku
15
+ end
16
+
17
+ context "detecting the app" do
18
+ before do
19
+ @base.stub!(:heroku).and_return(@client)
20
+ end
21
+
22
+ it "attempts to find the app via the --app argument" do
23
+ @base.stub!(:args).and_return(['--app', 'myapp'])
24
+ @base.extract_app.should == 'myapp'
25
+ end
26
+
27
+ it "read remotes from git config" do
28
+ File.stub!(:exists?).with('/home/dev/myapp/.git/config').and_return(true)
29
+ File.stub!(:read).with('/home/dev/myapp/.git/config').and_return("
30
+ [core]
31
+ repositoryformatversion = 0
32
+ filemode = true
33
+ bare = false
34
+ logallrefupdates = true
35
+ [remote \"staging\"]
36
+ url = git@heroku.com:myapp-staging.git
37
+ fetch = +refs/heads/*:refs/remotes/staging/*
38
+ [remote \"production\"]
39
+ url = git@heroku.com:myapp.git
40
+ fetch = +refs/heads/*:refs/remotes/production/*
41
+ [remote \"github\"]
42
+ url = git@github.com:myapp.git
43
+ fetch = +refs/heads/*:refs/remotes/github/*
44
+ ")
45
+ @base.git_remotes('/home/dev/myapp').should == { 'staging' => 'myapp-staging', 'production' => 'myapp' }
46
+ end
47
+
48
+ it "attempts to find the git config file in parent folders" do
49
+ File.should_receive(:exists?).with('/home/dev/myapp/app/models/.git/config').and_return(false)
50
+ File.should_receive(:exists?).with('/home/dev/myapp/app/.git/config').and_return(false)
51
+ File.should_receive(:exists?).with('/home/dev/myapp/.git/config').and_return(true)
52
+ File.should_receive(:read).with('/home/dev/myapp/.git/config').and_return('')
53
+ @base.git_remotes('/home/dev/myapp/app/models')
54
+ end
55
+
56
+ it "gets the app from remotes when there's only one app" do
57
+ @base.stub!(:git_remotes).and_return({ 'heroku' => 'myapp' })
58
+ @base.extract_app.should == 'myapp'
59
+ end
60
+
61
+ it "uses the remote named after the current folder name when there are multiple" do
62
+ @base.stub!(:git_remotes).and_return({ 'staging' => 'myapp-staging', 'production' => 'myapp' })
63
+ Dir.stub!(:pwd).and_return('/home/dev/myapp')
64
+ @base.extract_app.should == 'myapp'
65
+ end
66
+
67
+ it "accepts a --remote argument to choose the app from the remote name" do
68
+ @base.stub!(:git_remotes).and_return({ 'staging' => 'myapp-staging', 'production' => 'myapp' })
69
+ @base.stub!(:args).and_return(['--remote', 'staging'])
70
+ @base.extract_app.should == 'myapp-staging'
71
+ end
72
+
73
+ it "raises when cannot determine which app is it" do
74
+ @base.stub!(:git_remotes).and_return({ 'staging' => 'myapp-staging', 'production' => 'myapp' })
75
+ lambda { @base.extract_app }.should raise_error(Heroku::Command::CommandFailed)
76
+ end
77
+ end
78
+
79
+ context "option parsing" do
80
+ it "extracts options from args" do
81
+ @base.stub!(:args).and_return(%w( a b --something value c d ))
82
+ @base.extract_option('--something').should == 'value'
83
+ end
84
+
85
+ it "accepts options without value" do
86
+ @base.stub!(:args).and_return(%w( a b --something))
87
+ @base.extract_option('--something').should be_true
88
+ end
89
+
90
+ it "doesn't consider parameters as a value" do
91
+ @base.stub!(:args).and_return(%w( a b --something --something-else c d))
92
+ @base.extract_option('--something').should be_true
93
+ end
94
+
95
+ it "accepts a default value" do
96
+ @base.stub!(:args).and_return(%w( a b --something))
97
+ @base.extract_option('--something', 'default').should == 'default'
98
+ end
99
+
100
+ it "is not affected by multiple arguments with the same value" do
101
+ @base.stub!(:args).and_return(%w( --arg1 val --arg2 val ))
102
+ @base.extract_option('--arg1').should == 'val'
103
+ @base.args.should == ['--arg2', 'val']
104
+ end
105
+ end
106
+
107
+ describe "formatting" do
108
+ it "formats app urls (http and git), displayed as output on create and other commands" do
109
+ @base.stub!(:heroku).and_return(mock('heroku client', :host => 'example.com'))
110
+ @base.app_urls('test').should == "http://test.example.com/ | git@example.com:test.git"
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,48 @@
1
+ require File.dirname(__FILE__) + '/../base'
2
+
3
+ module Heroku::Command
4
+ describe Bundles do
5
+ before do
6
+ @bundles = prepare_command(Bundles)
7
+ end
8
+
9
+ it "lists bundles" do
10
+ @bundles.heroku.should_receive(:bundles).with('myapp').and_return([])
11
+ @bundles.list
12
+ end
13
+
14
+ it "captures a bundle with the specified name" do
15
+ @bundles.stub!(:args).and_return(['mybundle'])
16
+ @bundles.heroku.should_receive(:bundle_capture).with('myapp', 'mybundle')
17
+ @bundles.capture
18
+ end
19
+
20
+ it "captures a bundle with no name" do
21
+ @bundles.heroku.should_receive(:bundle_capture).with('myapp', nil)
22
+ @bundles.capture
23
+ end
24
+
25
+ it "destroys a bundle" do
26
+ @bundles.stub!(:args).and_return(['mybundle'])
27
+ @bundles.heroku.should_receive(:bundle_destroy).with('myapp', 'mybundle')
28
+ @bundles.destroy
29
+ end
30
+
31
+ it "downloads a bundle to appname.tar.gz" do
32
+ @bundles.stub!(:args).and_return(['mybundle'])
33
+ @bundles.heroku.should_receive(:bundle_url).with('myapp', 'mybundle').and_return('https://s3.amazonaws.com/herokubundles/123.tar.gz')
34
+ RestClient.should_receive(:get).with('https://s3.amazonaws.com/herokubundles/123.tar.gz').and_return('bundle contents')
35
+ @file = mock('bundle file')
36
+ File.should_receive(:open).with('myapp.tar.gz', 'wb').and_yield(@file)
37
+ @file.should_receive(:write).with('bundle contents')
38
+ File.stub!(:stat).and_return(mock('app stat', :size => 1234))
39
+ @bundles.download
40
+ end
41
+
42
+ it "animates a bundle" do
43
+ @bundles.stub!(:args).and_return(['mybundle'])
44
+ @bundles.heroku.should_receive(:create).with(nil, :origin_bundle_app => 'myapp', :origin_bundle => 'mybundle')
45
+ @bundles.animate
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,45 @@
1
+ require File.dirname(__FILE__) + '/../base'
2
+
3
+ module Heroku::Command
4
+ describe Config do
5
+ before do
6
+ @config = prepare_command(Config)
7
+ end
8
+
9
+ it "shows all configs" do
10
+ @config.heroku.should_receive(:config_vars).and_return({ 'A' => 'one', 'B' => 'two' })
11
+ @config.should_receive(:display).with('A => one')
12
+ @config.should_receive(:display).with('B => two')
13
+ @config.index
14
+ end
15
+
16
+ it "trims long values" do
17
+ @config.heroku.should_receive(:config_vars).and_return({ 'LONG' => 'A' * 60 })
18
+ @config.should_receive(:display).with('LONG => AAAAAAAAAAAAAAAA...AAAAAAAAAAAAAAAA')
19
+ @config.index
20
+ end
21
+
22
+ it "sets config vars" do
23
+ @config.stub!(:args).and_return(['a=1', 'b=2'])
24
+ @config.heroku.should_receive(:add_config_vars).with('myapp', {'a'=>'1','b'=>'2'})
25
+ @config.add
26
+ end
27
+
28
+ it "allows config vars with = in the value" do
29
+ @config.stub!(:args).and_return(['a=b=c'])
30
+ @config.heroku.should_receive(:add_config_vars).with('myapp', {'a'=>'b=c'})
31
+ @config.add
32
+ end
33
+
34
+ it "unsets config vars" do
35
+ @config.stub!(:args).and_return(['a'])
36
+ @config.heroku.should_receive(:remove_config_var).with('myapp', 'a')
37
+ @config.remove
38
+ end
39
+
40
+ it "resets config" do
41
+ @config.heroku.should_receive(:clear_config_vars).with('myapp')
42
+ @config.clear
43
+ end
44
+ end
45
+ end