pagoda 0.1.0
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/.bundle/config +2 -0
- data/.gitignore +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +39 -0
- data/README +3 -0
- data/Rakefile +11 -0
- data/bin/pagoda +13 -0
- data/lib/pagoda/client.rb +221 -0
- data/lib/pagoda/command.rb +93 -0
- data/lib/pagoda/commands/app.rb +243 -0
- data/lib/pagoda/commands/auth.rb +149 -0
- data/lib/pagoda/commands/base.rb +184 -0
- data/lib/pagoda/commands/help.rb +100 -0
- data/lib/pagoda/commands/tunnel.rb +49 -0
- data/lib/pagoda/helpers.rb +127 -0
- data/lib/pagoda/tunnel_proxy.rb +130 -0
- data/lib/pagoda/version.rb +3 -0
- data/lib/pagoda.rb +3 -0
- data/pagoda.gemspec +29 -0
- data/spec/base.rb +21 -0
- data/spec/client_spec.rb +255 -0
- data/spec/command_spec.rb +26 -0
- data/spec/commands/auth_spec.rb +57 -0
- metadata +167 -0
@@ -0,0 +1,130 @@
|
|
1
|
+
require "socket"
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module Pagoda
|
5
|
+
class TunnelProxy
|
6
|
+
include Pagoda::Helpers
|
7
|
+
|
8
|
+
def initialize(type, user, pass, app, instance)
|
9
|
+
@type = type
|
10
|
+
@user = user
|
11
|
+
@pass = pass
|
12
|
+
@app = app
|
13
|
+
@instance = instance
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
|
18
|
+
[:INT, :TERM].each do |sig|
|
19
|
+
Signal.trap(sig) do
|
20
|
+
display "Tunnel Closed."
|
21
|
+
display "-----------------------------------------------"
|
22
|
+
display
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
local_port = 3307
|
28
|
+
remote_host = "www.pagodabox.com"
|
29
|
+
remote_port = 3306
|
30
|
+
|
31
|
+
max_threads = 20
|
32
|
+
threads = []
|
33
|
+
|
34
|
+
chunk = 4096
|
35
|
+
|
36
|
+
#puts "start TCP server"
|
37
|
+
display "+> Opening Tunnel"
|
38
|
+
bound = false
|
39
|
+
until bound
|
40
|
+
begin
|
41
|
+
proxy_server = TCPServer.new('0.0.0.0', local_port)
|
42
|
+
bound = true
|
43
|
+
rescue Errno::EADDRINUSE
|
44
|
+
local_port += 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
display
|
49
|
+
display "Tunnel Established! Accepting connections on :"
|
50
|
+
display "-----------------------------------------------"
|
51
|
+
display
|
52
|
+
display "HOST : 127.0.0.1 (or localhost)", true, 2
|
53
|
+
display "PORT : #{local_port}", true, 2
|
54
|
+
display "USER : (found in pagodabox dashboard)", true, 2
|
55
|
+
display "PASS : (found in pagodabox dashboard)", true, 2
|
56
|
+
display
|
57
|
+
display "-----------------------------------------------"
|
58
|
+
display "(note : ctrl-c To close this tunnel)"
|
59
|
+
|
60
|
+
loop do
|
61
|
+
|
62
|
+
#puts "start a new thread for every client connection"
|
63
|
+
threads << Thread.new(proxy_server.accept) do |client_socket|
|
64
|
+
|
65
|
+
begin
|
66
|
+
# puts "client connection"
|
67
|
+
begin
|
68
|
+
server_socket = TCPSocket.new(remote_host, remote_port)
|
69
|
+
ssl_context = OpenSSL::SSL::SSLContext.new()
|
70
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(server_socket, ssl_context)
|
71
|
+
ssl_socket.sync_close = true
|
72
|
+
ssl_socket.connect
|
73
|
+
rescue Errno::ECONNREFUSED
|
74
|
+
# puts "connection refused"
|
75
|
+
client_socket.close
|
76
|
+
raise
|
77
|
+
end
|
78
|
+
|
79
|
+
# puts "authenticate"
|
80
|
+
if ssl_socket.readpartial(chunk) == "auth"
|
81
|
+
# puts "authentication"
|
82
|
+
ssl_socket.write "auth=#{@user}:#{@pass}:#{@app}:#{@instance}"
|
83
|
+
if ssl_socket.readpartial(chunk) == "success"
|
84
|
+
# puts "successful connection"
|
85
|
+
else
|
86
|
+
# puts "failed connection"
|
87
|
+
end
|
88
|
+
else
|
89
|
+
# puts "danger will robbinson! abort!"
|
90
|
+
end
|
91
|
+
|
92
|
+
loop do
|
93
|
+
# puts "wait for data on either socket"
|
94
|
+
(ready_sockets, dummy, dummy) = IO.select([client_socket, ssl_socket])
|
95
|
+
|
96
|
+
# puts "full duplex connection until data stream ends"
|
97
|
+
begin
|
98
|
+
ready_sockets.each do |socket|
|
99
|
+
data = socket.readpartial(chunk)
|
100
|
+
if socket == client_socket
|
101
|
+
#puts "read from client and write to server"
|
102
|
+
ssl_socket.write data
|
103
|
+
ssl_socket.flush
|
104
|
+
else
|
105
|
+
#puts "read from server and write to client."
|
106
|
+
client_socket.write data
|
107
|
+
client_socket.flush
|
108
|
+
end
|
109
|
+
end
|
110
|
+
rescue EOFError
|
111
|
+
break
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
rescue StandardError => error
|
116
|
+
end
|
117
|
+
client_socket.close rescue StandardError
|
118
|
+
ssl_socket.close rescue StandardError
|
119
|
+
end
|
120
|
+
|
121
|
+
#puts "clean up the dead threads, and wait until we have available threads"
|
122
|
+
threads = threads.select { |thread| thread.alive? ? true : (thread.join; false) }
|
123
|
+
while threads.size >= max_threads
|
124
|
+
sleep 1
|
125
|
+
threads = threads.select { |thread| thread.alive? ? true : (thread.join; false) }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/lib/pagoda.rb
ADDED
data/pagoda.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "pagoda/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "pagoda"
|
7
|
+
s.version = Pagoda::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["lyon hill"]
|
10
|
+
s.email = ["hal@pagodabox.com"]
|
11
|
+
s.homepage = "http://www.pagodabox.com/"
|
12
|
+
s.summary = %q{Terminal client for interacting with the pagodabox}
|
13
|
+
s.description = %q{Terminal client for interacting with the pagodabox. This client does not contain full api functionality, just functionality that will enhance the workflow experience.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "pagoda"
|
16
|
+
|
17
|
+
s.add_development_dependency "rspec"
|
18
|
+
s.add_development_dependency "webmock"
|
19
|
+
|
20
|
+
s.add_dependency "crack"
|
21
|
+
s.add_dependency "iniparse"
|
22
|
+
s.add_dependency "json_pure"
|
23
|
+
s.add_dependency "rest-client"
|
24
|
+
|
25
|
+
s.files = `git ls-files`.split("\n")
|
26
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
27
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
28
|
+
s.require_paths = ["lib"]
|
29
|
+
end
|
data/spec/base.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'webmock/rspec'
|
2
|
+
|
3
|
+
require 'pagoda/command'
|
4
|
+
require 'pagoda/commands/base'
|
5
|
+
|
6
|
+
Dir["#{File.dirname(__FILE__)}/../lib/pagoda/commands/*"].each { |c| require c }
|
7
|
+
|
8
|
+
include WebMock::API
|
9
|
+
|
10
|
+
def stub_api_request(method, path)
|
11
|
+
stub_request(method, "http://www.pagodabox.com#{path}")
|
12
|
+
end
|
13
|
+
|
14
|
+
def prepare_command(klass)
|
15
|
+
command = klass.new(['--app', 'myapp'])
|
16
|
+
command.stub!(:args).and_return([])
|
17
|
+
command.stub!(:display)
|
18
|
+
command.stub!(:pagoda).and_return(mock('pagoda client', :host => 'pagoda.com'))
|
19
|
+
command.stub!(:extract_app).and_return('myapp')
|
20
|
+
command
|
21
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/base"
|
2
|
+
require "#{File.dirname(__FILE__)}/../lib/pagoda/client"
|
3
|
+
|
4
|
+
describe Pagoda::Client do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@client = Pagoda::Client.new(nil, nil)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "app" do
|
11
|
+
|
12
|
+
# errors come back like this:
|
13
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
14
|
+
# <errors>
|
15
|
+
# <error>Username can't be blank</error>
|
16
|
+
# <error>Email can't be blank</error>
|
17
|
+
# <error>Password can't be blank</error>
|
18
|
+
# </errors>
|
19
|
+
|
20
|
+
it "should display information" do
|
21
|
+
stub = %{
|
22
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
23
|
+
<app>
|
24
|
+
<name>testapp</name>
|
25
|
+
<git-url>git@github.com:tylerflint/pagoda-pilot.git</git-url>
|
26
|
+
<owner>
|
27
|
+
<username>owner1</username>
|
28
|
+
<email>owner1@test.com</email>
|
29
|
+
</owner>
|
30
|
+
<collaborators type="array">
|
31
|
+
<collaborator>
|
32
|
+
<username>guy1</username>
|
33
|
+
<email>guy1@test.com</email>
|
34
|
+
</collaborator>
|
35
|
+
<collaborator>
|
36
|
+
<username>guy2</username>
|
37
|
+
<email>guy2@test.com</email>
|
38
|
+
</collaborator>
|
39
|
+
</collaborators>
|
40
|
+
<transactions type="array">
|
41
|
+
<transaction>
|
42
|
+
<id>1</id>
|
43
|
+
<name>app.init</name>
|
44
|
+
<description>Deploying app to the Pagoda grid</description>
|
45
|
+
<state>started</state>
|
46
|
+
<status></status>
|
47
|
+
</transaction>
|
48
|
+
</transactions>
|
49
|
+
</app>
|
50
|
+
}
|
51
|
+
stub_api_request(:get, "/apps/testapp.xml").to_return(:body => stub)
|
52
|
+
@client.app_info('testapp').should == {
|
53
|
+
:name => 'testapp',
|
54
|
+
:git_url => 'git@github.com:tylerflint/pagoda-pilot.git',
|
55
|
+
:owner => {
|
56
|
+
:username => 'owner1',
|
57
|
+
:email => 'owner1@test.com'
|
58
|
+
},
|
59
|
+
:collaborators => [
|
60
|
+
{:username => 'guy1', :email => 'guy1@test.com'},
|
61
|
+
{:username => 'guy2', :email => 'guy2@test.com'}
|
62
|
+
],
|
63
|
+
:transactions => [
|
64
|
+
{:id => '1', :name => 'app.init', :description => 'Deploying app to the Pagoda grid', :state => 'started', :status => nil}
|
65
|
+
]
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
it "lists incomplete transactions" do
|
70
|
+
stub = %{
|
71
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
72
|
+
<transactions type="array">
|
73
|
+
<transaction>
|
74
|
+
<id>1</id>
|
75
|
+
<name>app.increment</name>
|
76
|
+
<description>spawn new instance of app</description>
|
77
|
+
<state>started</state>
|
78
|
+
<status></status>
|
79
|
+
</transaction>
|
80
|
+
<transaction>
|
81
|
+
<id>2</id>
|
82
|
+
<name>app.deploy</name>
|
83
|
+
<description>deploy code</description>
|
84
|
+
<state>ready</state>
|
85
|
+
<status></status>
|
86
|
+
</transaction>
|
87
|
+
</transactions>
|
88
|
+
}
|
89
|
+
stub_api_request(:get, "/apps/testapp/transactions.xml").to_return(:body => stub)
|
90
|
+
@client.transaction_list('testapp').should == [
|
91
|
+
{:id => '1', :name => 'app.increment', :description => 'spawn new instance of app', :state => 'started', :status => nil},
|
92
|
+
{:id => '2', :name => 'app.deploy', :description => 'deploy code', :state => 'ready', :status => nil}
|
93
|
+
]
|
94
|
+
end
|
95
|
+
|
96
|
+
it "lists details of a transaction" do
|
97
|
+
stub = %{
|
98
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
99
|
+
<transaction>
|
100
|
+
<id>1</id>
|
101
|
+
<name>app.increment</name>
|
102
|
+
<description>spawn new instance of app</description>
|
103
|
+
<state>started</state>
|
104
|
+
<status></status>
|
105
|
+
</transaction>
|
106
|
+
}
|
107
|
+
stub_api_request(:get, "/apps/testapp/transactions/123.xml").to_return(:body => stub)
|
108
|
+
@client.transaction_status('testapp', '123').should == {:id => '1', :name => 'app.increment', :description => 'spawn new instance of app', :state => 'started', :status => nil}
|
109
|
+
end
|
110
|
+
|
111
|
+
it "deploys" do
|
112
|
+
stub = %{
|
113
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
114
|
+
<transaction>
|
115
|
+
<id>1</id>
|
116
|
+
<name>app.deploy</name>
|
117
|
+
<description>deploy new code</description>
|
118
|
+
<state>started</state>
|
119
|
+
<status></status>
|
120
|
+
</transaction>
|
121
|
+
}
|
122
|
+
stub_api_request(:put, "/apps/testapp/deploy.xml").to_return(:body => stub)
|
123
|
+
@client.deploy('testapp').should == {:id => '1', :name => 'app.deploy', :description => 'deploy new code', :state => 'started', :status => nil}
|
124
|
+
end
|
125
|
+
|
126
|
+
it "rewinds deploy list" do
|
127
|
+
stub = %{
|
128
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
129
|
+
<transaction>
|
130
|
+
<id>1</id>
|
131
|
+
<name>app.traverse</name>
|
132
|
+
<description>traverse the code</description>
|
133
|
+
<state>started</state>
|
134
|
+
<status></status>
|
135
|
+
</transaction>
|
136
|
+
}
|
137
|
+
stub_api_request(:put, "/apps/testapp/rewind.xml").to_return(:body => stub)
|
138
|
+
@client.rewind('testapp', 1).should == {:id => '1', :name => 'app.traverse', :description => 'traverse the code', :state => 'started', :status => nil}
|
139
|
+
end
|
140
|
+
|
141
|
+
it "fast-forwards deploy list" do
|
142
|
+
stub = %{
|
143
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
144
|
+
<transaction>
|
145
|
+
<id>1</id>
|
146
|
+
<name>app.traverse</name>
|
147
|
+
<description>traverse the code</description>
|
148
|
+
<state>started</state>
|
149
|
+
<status></status>
|
150
|
+
</transaction>
|
151
|
+
}
|
152
|
+
stub_api_request(:put, "/apps/testapp/fast-forward.xml").to_return(:body => stub)
|
153
|
+
@client.fast_forward('testapp', 1).should == {:id => '1', :name => 'app.traverse', :description => 'traverse the code', :state => 'started', :status => nil}
|
154
|
+
end
|
155
|
+
|
156
|
+
it "scales up" do
|
157
|
+
stub = %{
|
158
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
159
|
+
<transaction>
|
160
|
+
<id>1</id>
|
161
|
+
<name>app.scaleup</name>
|
162
|
+
<description>scaling app</description>
|
163
|
+
<state>started</state>
|
164
|
+
<status></status>
|
165
|
+
</transaction>
|
166
|
+
}
|
167
|
+
stub_api_request(:put, "/apps/testapp/scale-up.xml").to_return(:body => stub)
|
168
|
+
@client.scale_up('testapp', 1).should == {:id => '1', :name => 'app.scaleup', :description => 'scaling app', :state => 'started', :status => nil}
|
169
|
+
end
|
170
|
+
|
171
|
+
it "scales down" do
|
172
|
+
stub = %{
|
173
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
174
|
+
<transaction>
|
175
|
+
<id>1</id>
|
176
|
+
<name>app.scaledown</name>
|
177
|
+
<description>scaling app</description>
|
178
|
+
<state>started</state>
|
179
|
+
<status></status>
|
180
|
+
</transaction>
|
181
|
+
}
|
182
|
+
stub_api_request(:put, "/apps/testapp/scale-down.xml").to_return(:body => stub)
|
183
|
+
@client.scale_down('testapp', 1).should == {:id => '1', :name => 'app.scaledown', :description => 'scaling app', :state => 'started', :status => nil}
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
describe "user" do
|
189
|
+
|
190
|
+
it "should list all active apps" do
|
191
|
+
stub = %{
|
192
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
193
|
+
<apps type="array">
|
194
|
+
<app>
|
195
|
+
<id>1</id>
|
196
|
+
<name>burt</name>
|
197
|
+
<git-url>git@github.com:tylerflint/pagoda-pilot.git</git-url>
|
198
|
+
</app>
|
199
|
+
<app>
|
200
|
+
<id>2</id>
|
201
|
+
<name>ernie</name>
|
202
|
+
<git-url>git@github.com:tylerflint/pagoda-pilot.git</git-url>
|
203
|
+
</app>
|
204
|
+
</apps>
|
205
|
+
}
|
206
|
+
stub_api_request(:get, "/apps.xml").to_return(:body => stub)
|
207
|
+
@client.app_list.should == [{:id => '1', :name => 'burt', :git_url => 'git@github.com:tylerflint/pagoda-pilot.git'}, {:id => '2', :name => 'ernie', :git_url => 'git@github.com:tylerflint/pagoda-pilot.git'}]
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should create a new app" do
|
211
|
+
stub = %{
|
212
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
213
|
+
<app>
|
214
|
+
<name>testapp</name>
|
215
|
+
<git-url>git@github.com:tylerflint/pagoda-pilot.git</git-url>
|
216
|
+
<owner>
|
217
|
+
<username>tylerflint</username>
|
218
|
+
<email>tylerflint@gmail.com</email>
|
219
|
+
</owner>
|
220
|
+
<collaborators>
|
221
|
+
</collaborators>
|
222
|
+
<transactions type="array">
|
223
|
+
<transaction>
|
224
|
+
<id>1</id>
|
225
|
+
<name>app.init</name>
|
226
|
+
<description>Deploying app to the Pagoda grid</description>
|
227
|
+
<state>started</state>
|
228
|
+
<status></status>
|
229
|
+
</transaction>
|
230
|
+
</transactions>
|
231
|
+
</app>
|
232
|
+
}
|
233
|
+
stub_api_request(:post, '/apps.xml').with(:body => "app[name]=testapp&app[git_url]=git%40github.com%3Atylerflint%2Fpagoda-pilot.git").to_return(:body => stub)
|
234
|
+
@client.app_create('testapp', 'git@github.com:tylerflint/pagoda-pilot.git').should == {
|
235
|
+
:name => 'testapp',
|
236
|
+
:git_url => 'git@github.com:tylerflint/pagoda-pilot.git',
|
237
|
+
:owner => {
|
238
|
+
:username => 'tylerflint',
|
239
|
+
:email => 'tylerflint@gmail.com'
|
240
|
+
},
|
241
|
+
:collaborators => [],
|
242
|
+
:transactions => [
|
243
|
+
{:id => '1', :name => 'app.init', :description => 'Deploying app to the Pagoda grid', :state => 'started', :status => nil}
|
244
|
+
]
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should destroy an active app" do
|
249
|
+
stub_api_request(:delete, '/apps/testapp.xml')
|
250
|
+
@client.app_destroy('testapp')
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
describe Pagoda::Command do
|
3
|
+
|
4
|
+
it "extracts error messages from response when available in XML" do
|
5
|
+
Pagoda::Command.extract_error('<errors><error>Invalid app name</error></errors>').should == ' ! Invalid app name'
|
6
|
+
end
|
7
|
+
|
8
|
+
it "shows Internal Server Error when the response doesn't contain a XML" do
|
9
|
+
Pagoda::Command.extract_error('<h1>HTTP 500</h1>').should == ' ! Internal server error'
|
10
|
+
end
|
11
|
+
|
12
|
+
it "handles a nil body in parse_error_xml" do
|
13
|
+
lambda { Pagoda::Command.parse_error_xml(nil) }.should_not raise_error
|
14
|
+
end
|
15
|
+
|
16
|
+
it "correctly resolves commands" do
|
17
|
+
class Pagoda::Command::Test; end
|
18
|
+
class Pagoda::Command::Test::Multiple; end
|
19
|
+
|
20
|
+
Pagoda::Command.parse("foo").should == [ Pagoda::Command::App, :foo ]
|
21
|
+
Pagoda::Command.parse("test").should == [ Pagoda::Command::Test, :index ]
|
22
|
+
Pagoda::Command.parse("test:foo").should == [ Pagoda::Command::Test, :foo ]
|
23
|
+
Pagoda::Command.parse("test:multiple:foo").should == [ Pagoda::Command::Test::Multiple, :foo ]
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Pagoda::Command
|
2
|
+
describe Auth do
|
3
|
+
before do
|
4
|
+
@cli = prepare_command(Auth)
|
5
|
+
@sandbox = "#{Dir.tmpdir}/cli_spec_#{Process.pid}"
|
6
|
+
File.open(@sandbox, "w") { |f| f.write "user\npass\n" }
|
7
|
+
@cli.stub!(:credentials_file).and_return(@sandbox)
|
8
|
+
@cli.stub!(:running_on_a_mac?).and_return(false)
|
9
|
+
end
|
10
|
+
|
11
|
+
after do
|
12
|
+
FileUtils.rm_rf(@sandbox)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "reads credentials from the credentials file" do
|
16
|
+
@cli.read_credentials.should == %w(user pass)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "takes the user from the first line and the password from the second line" do
|
20
|
+
@cli.user.should == 'user'
|
21
|
+
@cli.password.should == 'pass'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "asks for credentials when the file doesn't exist" do
|
25
|
+
FileUtils.rm_rf(@sandbox)
|
26
|
+
@cli.should_receive(:ask_for_credentials).and_return([ 'u', 'p'])
|
27
|
+
@cli.should_receive(:save_credentials)
|
28
|
+
@cli.get_credentials.should == [ 'u', 'p' ]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "writes the credentials to a file" do
|
32
|
+
@cli.stub!(:credentials).and_return(['one', 'two'])
|
33
|
+
@cli.should_receive(:set_credentials_permissions)
|
34
|
+
@cli.write_credentials
|
35
|
+
File.read(@sandbox).should == "one\ntwo\n"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "sets ~/.pagoda/credentials to be readable only by the user" do
|
39
|
+
unless RUBY_PLATFORM =~ /mswin32|mingw32/
|
40
|
+
sandbox = "#{Dir.tmpdir}/cli_spec_#{Process.pid}"
|
41
|
+
FileUtils.rm_rf(sandbox)
|
42
|
+
FileUtils.mkdir_p(sandbox)
|
43
|
+
fname = "#{sandbox}/file"
|
44
|
+
system "touch #{fname}"
|
45
|
+
@cli.stub!(:credentials_file).and_return(fname)
|
46
|
+
@cli.set_credentials_permissions
|
47
|
+
File.stat(sandbox).mode.should == 040700
|
48
|
+
File.stat(fname).mode.should == 0100600
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "deletes the credentials file" do
|
53
|
+
FileUtils.should_receive(:rm_f).with(@cli.credentials_file)
|
54
|
+
@cli.delete_credentials
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|