artofmission-heroku 1.6.3

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