warden-client 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +10 -0
- data/README.md +13 -0
- data/Rakefile +8 -0
- data/lib/warden/client.rb +115 -0
- data/lib/warden/client/v1.rb +286 -0
- data/lib/warden/client/version.rb +5 -0
- data/spec/client/v1_spec.rb +447 -0
- data/spec/client_spec.rb +224 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/mock_warden_server.rb +78 -0
- data/warden-client.gemspec +20 -0
- metadata +78 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# warden-client
|
2
|
+
|
3
|
+
> This README describes a **client** component. Please refer to the top
|
4
|
+
> level [README][tlr] for an overview of all components.
|
5
|
+
|
6
|
+
[tlr]: /README.md
|
7
|
+
|
8
|
+
## License
|
9
|
+
|
10
|
+
The project is licensed under the Apache 2.0 license (see the
|
11
|
+
[`LICENSE`][license] file in the root directory of the repository).
|
12
|
+
|
13
|
+
[license]: /LICENSE
|
data/Rakefile
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require "socket"
|
2
|
+
require "warden/protocol"
|
3
|
+
require "warden/client/v1"
|
4
|
+
|
5
|
+
module Warden
|
6
|
+
|
7
|
+
class Client
|
8
|
+
|
9
|
+
class Error < StandardError; end
|
10
|
+
class ServerError < Error; end
|
11
|
+
|
12
|
+
attr_reader :path
|
13
|
+
|
14
|
+
def initialize(path)
|
15
|
+
@path = path
|
16
|
+
@v1mode = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def connected?
|
20
|
+
!@sock.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def connect
|
24
|
+
raise "already connected" if connected?
|
25
|
+
@sock = ::UNIXSocket.new(path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def disconnect
|
29
|
+
raise "not connected" unless connected?
|
30
|
+
@sock.close rescue nil
|
31
|
+
@sock = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def reconnect
|
35
|
+
disconnect if connected?
|
36
|
+
connect
|
37
|
+
end
|
38
|
+
|
39
|
+
def io
|
40
|
+
rv = yield
|
41
|
+
if rv.nil?
|
42
|
+
disconnect
|
43
|
+
raise ::EOFError
|
44
|
+
end
|
45
|
+
|
46
|
+
rv
|
47
|
+
end
|
48
|
+
|
49
|
+
def read
|
50
|
+
length = io { @sock.gets }
|
51
|
+
data = io { @sock.read(length.to_i) }
|
52
|
+
|
53
|
+
# Discard \r\n
|
54
|
+
io { @sock.read(2) }
|
55
|
+
|
56
|
+
response = Warden::Protocol::Message.decode(data).response
|
57
|
+
|
58
|
+
# Raise error replies
|
59
|
+
if response.is_a?(Warden::Protocol::ErrorResponse)
|
60
|
+
raise Warden::Client::ServerError.new(response.message)
|
61
|
+
end
|
62
|
+
|
63
|
+
if @v1mode
|
64
|
+
response = V1.response_to_v1(response)
|
65
|
+
end
|
66
|
+
|
67
|
+
response
|
68
|
+
end
|
69
|
+
|
70
|
+
def write(request)
|
71
|
+
if request.kind_of?(Array)
|
72
|
+
@v1mode = true
|
73
|
+
request = V1.request_from_v1(request.dup)
|
74
|
+
end
|
75
|
+
|
76
|
+
unless request.kind_of?(Warden::Protocol::BaseRequest)
|
77
|
+
raise "Expected #kind_of? Warden::Protocol::BaseRequest"
|
78
|
+
end
|
79
|
+
|
80
|
+
data = request.wrap.encode.to_s
|
81
|
+
@sock.write data.length.to_s + "\r\n"
|
82
|
+
@sock.write data + "\r\n"
|
83
|
+
end
|
84
|
+
|
85
|
+
def stream(request, &blk)
|
86
|
+
unless request.is_a?(Warden::Protocol::StreamRequest)
|
87
|
+
msg = "Expected argument to be of type:"
|
88
|
+
msg << "'#{Warden::Protocol::StreamRequest}'"
|
89
|
+
msg << ", but received: '#{request.class}'."
|
90
|
+
raise ArgumentError, msg
|
91
|
+
end
|
92
|
+
|
93
|
+
response = call(request)
|
94
|
+
while response.exit_status.nil?
|
95
|
+
blk.call(response)
|
96
|
+
response = read
|
97
|
+
end
|
98
|
+
|
99
|
+
response
|
100
|
+
end
|
101
|
+
|
102
|
+
def call(request)
|
103
|
+
write(request)
|
104
|
+
read
|
105
|
+
end
|
106
|
+
|
107
|
+
def method_missing(sym, *args, &blk)
|
108
|
+
klass_name = sym.to_s.gsub(/(^|_)([a-z])/) { $2.upcase }
|
109
|
+
klass_name += "Request"
|
110
|
+
klass = Warden::Protocol.const_get(klass_name)
|
111
|
+
|
112
|
+
call(klass.new(*args))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,286 @@
|
|
1
|
+
require "warden/protocol"
|
2
|
+
|
3
|
+
module Warden
|
4
|
+
class Client
|
5
|
+
class V1
|
6
|
+
def self.request_from_v1(args)
|
7
|
+
command = args.shift
|
8
|
+
|
9
|
+
m = "convert_#{command}_request".downcase
|
10
|
+
if respond_to?(m)
|
11
|
+
send(m, args)
|
12
|
+
else
|
13
|
+
raise "Unknown command: #{command.upcase}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.response_to_v1(response)
|
18
|
+
klass_name = response.class.name.split("::").last
|
19
|
+
klass_name = klass_name.gsub(/Response$/, "")
|
20
|
+
klass_name = klass_name.gsub(/(.)([A-Z])/) { |m| "#{m[0]}_#{m[1]}" }
|
21
|
+
klass_name = klass_name.downcase
|
22
|
+
|
23
|
+
m = "convert_#{klass_name}_response".downcase
|
24
|
+
if respond_to?(m)
|
25
|
+
send(m, response)
|
26
|
+
else
|
27
|
+
raise "Unknown response: #{response.class.name.split("::").last}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def self.convert_create_request(args)
|
34
|
+
request = Protocol::CreateRequest.new
|
35
|
+
config = args.shift || {}
|
36
|
+
|
37
|
+
config.each do |key, value|
|
38
|
+
case key
|
39
|
+
when "bind_mounts"
|
40
|
+
bind_mounts = value.map do |src_path, dst_path, mode|
|
41
|
+
bind_mount = Protocol::CreateRequest::BindMount.new
|
42
|
+
bind_mount.src_path = src_path
|
43
|
+
bind_mount.dst_path = dst_path
|
44
|
+
|
45
|
+
if mode.kind_of?(Hash)
|
46
|
+
mode = mode["mode"]
|
47
|
+
end
|
48
|
+
|
49
|
+
bind_mount.mode = Protocol::CreateRequest::BindMount::Mode.const_get(mode.to_s.upcase)
|
50
|
+
bind_mount
|
51
|
+
end
|
52
|
+
|
53
|
+
request.bind_mounts = bind_mounts
|
54
|
+
when "grace_time"
|
55
|
+
request.grace_time = Integer(value)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
request
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.convert_create_response(response)
|
63
|
+
response.handle
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.convert_stop_request(args)
|
67
|
+
request = Protocol::StopRequest.new
|
68
|
+
request.handle = args.shift
|
69
|
+
request
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.convert_stop_response(response)
|
73
|
+
"ok"
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.convert_destroy_request(args)
|
77
|
+
request = Protocol::DestroyRequest.new
|
78
|
+
request.handle = args.shift
|
79
|
+
request
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.convert_destroy_response(response)
|
83
|
+
"ok"
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.convert_info_request(args)
|
87
|
+
request = Protocol::InfoRequest.new
|
88
|
+
request.handle = args.shift
|
89
|
+
request
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.convert_info_response(response)
|
93
|
+
stringify_hash response.to_hash
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.convert_spawn_request(args)
|
97
|
+
request = Protocol::SpawnRequest.new
|
98
|
+
request.handle = args.shift
|
99
|
+
request.script = args.shift
|
100
|
+
request
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.convert_spawn_response(response)
|
104
|
+
response.job_id
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.convert_link_request(args)
|
108
|
+
request = Protocol::LinkRequest.new
|
109
|
+
request.handle = args.shift
|
110
|
+
request.job_id = Integer(args.shift)
|
111
|
+
request
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.convert_link_response(response)
|
115
|
+
[response.exit_status, response.stdout, response.stderr]
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.convert_stream_request(args)
|
119
|
+
request = Protocol::StreamRequest.new
|
120
|
+
request.handle = args.shift
|
121
|
+
request.job_id = Integer(args.shift)
|
122
|
+
request
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.convert_stream_response(response)
|
126
|
+
[response.name, response.data, response.exit_status]
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.convert_run_request(args)
|
130
|
+
request = Protocol::RunRequest.new
|
131
|
+
request.handle = args.shift
|
132
|
+
request.script = args.shift
|
133
|
+
request
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.convert_run_response(response)
|
137
|
+
[response.exit_status, response.stdout, response.stderr]
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.convert_net_request(args)
|
141
|
+
request = nil
|
142
|
+
handle = args.shift
|
143
|
+
direction = args.shift
|
144
|
+
|
145
|
+
case direction
|
146
|
+
when "in"
|
147
|
+
request = Protocol::NetInRequest.new
|
148
|
+
request.handle = handle
|
149
|
+
when "out"
|
150
|
+
request = Protocol::NetOutRequest.new
|
151
|
+
request.handle = handle
|
152
|
+
|
153
|
+
network, port = args.shift.split(":", 2)
|
154
|
+
request.network = network
|
155
|
+
request.port = Integer(port)
|
156
|
+
else
|
157
|
+
raise "Unknown net direction: #{direction}"
|
158
|
+
end
|
159
|
+
|
160
|
+
request
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.convert_net_in_response(response)
|
164
|
+
stringify_hash response.to_hash
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.convert_net_out_response(response)
|
168
|
+
"ok"
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.convert_copy_request(args)
|
172
|
+
request = nil
|
173
|
+
handle = args.shift
|
174
|
+
direction = args.shift
|
175
|
+
src_path = args.shift
|
176
|
+
dst_path = args.shift
|
177
|
+
owner = args.shift
|
178
|
+
|
179
|
+
attributes = {
|
180
|
+
:handle => handle,
|
181
|
+
:src_path => src_path,
|
182
|
+
:dst_path => dst_path
|
183
|
+
}
|
184
|
+
|
185
|
+
case direction
|
186
|
+
when "in"
|
187
|
+
request = Protocol::CopyInRequest.new(attributes)
|
188
|
+
when "out"
|
189
|
+
request = Protocol::CopyOutRequest.new(attributes)
|
190
|
+
request.owner = owner if owner
|
191
|
+
else
|
192
|
+
raise "Unknown copy direction: #{direction}"
|
193
|
+
end
|
194
|
+
|
195
|
+
request
|
196
|
+
end
|
197
|
+
|
198
|
+
def self.convert_copy_in_response(response)
|
199
|
+
"ok"
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.convert_copy_out_response(response)
|
203
|
+
"ok"
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.convert_limit_request(args)
|
207
|
+
request = nil
|
208
|
+
handle = args.shift
|
209
|
+
limit = args.shift
|
210
|
+
|
211
|
+
attributes = {
|
212
|
+
:handle => handle,
|
213
|
+
}
|
214
|
+
|
215
|
+
case limit
|
216
|
+
when "mem"
|
217
|
+
request = Protocol::LimitMemoryRequest.new(attributes)
|
218
|
+
request.limit_in_bytes = Integer(args.shift) unless args.empty?
|
219
|
+
when "disk"
|
220
|
+
request = Protocol::LimitDiskRequest.new(attributes)
|
221
|
+
request.byte = Integer(args.shift) unless args.empty?
|
222
|
+
when "bandwidth"
|
223
|
+
request = Protocol::LimitBandwidthRequest.new(attributes)
|
224
|
+
request.rate = Integer(args.shift) unless args.empty?
|
225
|
+
request.burst = Integer(args.shift) unless args.empty?
|
226
|
+
else
|
227
|
+
raise "Unknown limit: #{limit}"
|
228
|
+
end
|
229
|
+
|
230
|
+
request
|
231
|
+
end
|
232
|
+
|
233
|
+
def self.convert_limit_memory_response(response)
|
234
|
+
response.limit_in_bytes
|
235
|
+
end
|
236
|
+
|
237
|
+
def self.convert_limit_disk_response(response)
|
238
|
+
response.byte
|
239
|
+
end
|
240
|
+
|
241
|
+
def self.convert_limit_bandwidth_response(response)
|
242
|
+
"rate: #{response.rate} burst: #{response.burst}"
|
243
|
+
end
|
244
|
+
|
245
|
+
def self.convert_ping_request(args)
|
246
|
+
request = Protocol::PingRequest.new
|
247
|
+
request
|
248
|
+
end
|
249
|
+
|
250
|
+
def self.convert_ping_response(response)
|
251
|
+
"pong"
|
252
|
+
end
|
253
|
+
|
254
|
+
def self.convert_list_request(args)
|
255
|
+
request = Protocol::ListRequest.new
|
256
|
+
request
|
257
|
+
end
|
258
|
+
|
259
|
+
def self.convert_list_response(response)
|
260
|
+
response.handles
|
261
|
+
end
|
262
|
+
|
263
|
+
def self.convert_echo_request(args)
|
264
|
+
request = Protocol::EchoRequest.new
|
265
|
+
request.message = args.shift
|
266
|
+
request
|
267
|
+
end
|
268
|
+
|
269
|
+
def self.convert_echo_response(response)
|
270
|
+
response.message
|
271
|
+
end
|
272
|
+
|
273
|
+
private
|
274
|
+
|
275
|
+
def self.stringify_hash(hash)
|
276
|
+
Hash[hash.map do |key, value|
|
277
|
+
if value.kind_of?(Hash)
|
278
|
+
value = stringify_hash(value)
|
279
|
+
end
|
280
|
+
|
281
|
+
[key.to_s, value]
|
282
|
+
end]
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
@@ -0,0 +1,447 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "warden/client/v1"
|
3
|
+
|
4
|
+
describe Warden::Client::V1 do
|
5
|
+
def to_request(args)
|
6
|
+
described_class.request_from_v1(args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_response(response)
|
10
|
+
described_class.response_to_v1(response)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "create" do
|
14
|
+
describe "request" do
|
15
|
+
describe "bind_mount parameter" do
|
16
|
+
it "should use src and dst parameters" do
|
17
|
+
request = to_request [
|
18
|
+
"create",
|
19
|
+
{ "bind_mounts" => [["/src", "/dst", "ro"]] },
|
20
|
+
]
|
21
|
+
|
22
|
+
request.bind_mounts.should have(1).bind_mount
|
23
|
+
|
24
|
+
bind_mount = request.bind_mounts.first
|
25
|
+
bind_mount.src_path.should == "/src"
|
26
|
+
bind_mount.dst_path.should == "/dst"
|
27
|
+
end
|
28
|
+
|
29
|
+
["ro", { "mode" => "ro" }].each do |mode|
|
30
|
+
it "should convert ro mode when passed as #{mode.inspect}" do
|
31
|
+
request = to_request [
|
32
|
+
"create",
|
33
|
+
{ "bind_mounts" => [["/src", "/dst", mode]] },
|
34
|
+
]
|
35
|
+
|
36
|
+
request.bind_mounts.should have(1).bind_mount
|
37
|
+
|
38
|
+
bind_mount = request.bind_mounts.first
|
39
|
+
bind_mount.src_path.should == "/src"
|
40
|
+
bind_mount.dst_path.should == "/dst"
|
41
|
+
bind_mount.mode.should == Warden::Protocol::CreateRequest::BindMount::Mode::RO
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
["rw", { "mode" => "rw" }].each do |mode|
|
46
|
+
it "should convert rw mode when passed as #{mode.inspect}" do
|
47
|
+
request = to_request [
|
48
|
+
"create",
|
49
|
+
{ "bind_mounts" => [["/src", "/dst", "rw"]] },
|
50
|
+
]
|
51
|
+
|
52
|
+
request.bind_mounts.first.mode.should ==
|
53
|
+
Warden::Protocol::CreateRequest::BindMount::Mode::RW
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
["rx", { "mode" => "rx" }].each do |mode|
|
58
|
+
it "should raise on an invalid mode when passed as #{mode.inspect}" do
|
59
|
+
expect do
|
60
|
+
to_request [
|
61
|
+
"create",
|
62
|
+
{ "bind_mounts" => [["/src", "/dst", "rx"]] },
|
63
|
+
]
|
64
|
+
end.to raise_error
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "grace_time parameter" do
|
70
|
+
it "should be converted to an integer" do
|
71
|
+
request = to_request [
|
72
|
+
"create",
|
73
|
+
{ "grace_time" => 1.1 },
|
74
|
+
]
|
75
|
+
|
76
|
+
request.grace_time.should == 1
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should raise on an invalid value" do
|
80
|
+
expect do
|
81
|
+
to_request [
|
82
|
+
"create",
|
83
|
+
{ "grace_time" => "invalid" },
|
84
|
+
]
|
85
|
+
end.to raise_error
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "response" do
|
91
|
+
it "should return the handle" do
|
92
|
+
response = to_response \
|
93
|
+
Warden::Protocol::CreateResponse.new(:handle => "handle")
|
94
|
+
response.should == "handle"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "stop" do
|
100
|
+
describe "request" do
|
101
|
+
subject { to_request ["stop", "handle"] }
|
102
|
+
|
103
|
+
its(:class) { should == Warden::Protocol::StopRequest }
|
104
|
+
its(:handle) { should == "handle" }
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "response" do
|
108
|
+
it "should always be ok" do
|
109
|
+
response = to_response \
|
110
|
+
Warden::Protocol::StopResponse.new
|
111
|
+
response.should == "ok"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "destroy" do
|
117
|
+
describe "request" do
|
118
|
+
subject { to_request ["destroy", "handle"] }
|
119
|
+
|
120
|
+
its(:class) { should == Warden::Protocol::DestroyRequest }
|
121
|
+
its(:handle) { should == "handle" }
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "response" do
|
125
|
+
it "should always be ok" do
|
126
|
+
response = to_response \
|
127
|
+
Warden::Protocol::DestroyResponse.new
|
128
|
+
response.should == "ok"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "info" do
|
134
|
+
describe "request" do
|
135
|
+
subject { to_request ["info", "handle"] }
|
136
|
+
|
137
|
+
its(:class) { should == Warden::Protocol::InfoRequest }
|
138
|
+
its(:handle) { should == "handle" }
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "response" do
|
142
|
+
let(:response) do
|
143
|
+
to_response \
|
144
|
+
Warden::Protocol::InfoResponse.new({
|
145
|
+
:state => "state",
|
146
|
+
:memory_stat => Warden::Protocol::InfoResponse::MemoryStat.new({
|
147
|
+
:cache => 1,
|
148
|
+
:rss => 2,
|
149
|
+
})
|
150
|
+
})
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should return a hash" do
|
154
|
+
response.should be_a(Hash)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should stringify keys" do
|
158
|
+
response["state"].should == "state"
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should stringify keys of nested hashes" do
|
162
|
+
response["memory_stat"]["cache"].should == 1
|
163
|
+
response["memory_stat"]["rss"].should == 2
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "spawn" do
|
169
|
+
describe "request" do
|
170
|
+
subject { to_request ["spawn", "handle", "echo foo"] }
|
171
|
+
|
172
|
+
its(:class) { should == Warden::Protocol::SpawnRequest }
|
173
|
+
its(:handle) { should == "handle" }
|
174
|
+
its(:script) { should == "echo foo" }
|
175
|
+
end
|
176
|
+
|
177
|
+
describe "response" do
|
178
|
+
it "should return job_id" do
|
179
|
+
response = to_response \
|
180
|
+
Warden::Protocol::SpawnResponse.new(:job_id => 3)
|
181
|
+
response.should == 3
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "link" do
|
187
|
+
describe "request" do
|
188
|
+
subject { to_request ["link", "handle", "1"] }
|
189
|
+
|
190
|
+
its(:class) { should == Warden::Protocol::LinkRequest }
|
191
|
+
its(:handle) { should == "handle" }
|
192
|
+
its(:job_id) { should == 1 }
|
193
|
+
end
|
194
|
+
|
195
|
+
describe "response" do
|
196
|
+
it "should return a 3-element tuple" do
|
197
|
+
response = to_response \
|
198
|
+
Warden::Protocol::LinkResponse.new(
|
199
|
+
:exit_status => 255,
|
200
|
+
:stdout => "stdout",
|
201
|
+
:stderr => "stderr"
|
202
|
+
)
|
203
|
+
|
204
|
+
response[0].should == 255
|
205
|
+
response[1].should == "stdout"
|
206
|
+
response[2].should == "stderr"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe "stream" do
|
212
|
+
describe "request" do
|
213
|
+
subject { to_request ["stream", "handle", "1"] }
|
214
|
+
|
215
|
+
its(:class) { should == Warden::Protocol::StreamRequest }
|
216
|
+
its(:handle) { should == "handle" }
|
217
|
+
its(:job_id) { should == 1 }
|
218
|
+
end
|
219
|
+
|
220
|
+
describe "response" do
|
221
|
+
it "should return a 3-element tuple" do
|
222
|
+
response = to_response \
|
223
|
+
Warden::Protocol::StreamResponse.new(
|
224
|
+
:name => "stdout",
|
225
|
+
:data => "data",
|
226
|
+
:exit_status => 25,
|
227
|
+
)
|
228
|
+
|
229
|
+
response[0].should == "stdout"
|
230
|
+
response[1].should == "data"
|
231
|
+
response[2].should == 25
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
describe "run" do
|
237
|
+
describe "request" do
|
238
|
+
subject { to_request ["run", "handle", "echo foo"] }
|
239
|
+
|
240
|
+
its(:class) { should == Warden::Protocol::RunRequest }
|
241
|
+
its(:handle) { should == "handle" }
|
242
|
+
its(:script) { should == "echo foo" }
|
243
|
+
end
|
244
|
+
|
245
|
+
describe "response" do
|
246
|
+
it "should return a 3-element tuple" do
|
247
|
+
response = to_response \
|
248
|
+
Warden::Protocol::RunResponse.new(
|
249
|
+
:exit_status => 255,
|
250
|
+
:stdout => "stdout",
|
251
|
+
:stderr => "stderr"
|
252
|
+
)
|
253
|
+
|
254
|
+
response[0].should == 255
|
255
|
+
response[1].should == "stdout"
|
256
|
+
response[2].should == "stderr"
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
describe "net" do
|
262
|
+
describe "request (in)" do
|
263
|
+
subject { to_request ["net", "handle", "in"] }
|
264
|
+
|
265
|
+
its(:class) { should == Warden::Protocol::NetInRequest }
|
266
|
+
its(:handle) { should == "handle" }
|
267
|
+
end
|
268
|
+
|
269
|
+
describe "request (out)" do
|
270
|
+
subject { to_request ["net", "handle", "out", "network:1234"] }
|
271
|
+
|
272
|
+
its(:class) { should == Warden::Protocol::NetOutRequest }
|
273
|
+
its(:handle) { should == "handle" }
|
274
|
+
its(:network) { should == "network" }
|
275
|
+
its(:port) { should == 1234 }
|
276
|
+
end
|
277
|
+
|
278
|
+
describe "response (in)" do
|
279
|
+
it "should return a hash with both properties" do
|
280
|
+
response = to_response \
|
281
|
+
Warden::Protocol::NetInResponse.new(
|
282
|
+
:host_port => 1234,
|
283
|
+
:container_port => 2345,
|
284
|
+
)
|
285
|
+
|
286
|
+
response["host_port"].should == 1234
|
287
|
+
response["container_port"].should == 2345
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe "response (out)" do
|
292
|
+
it "should always be ok" do
|
293
|
+
response = to_response \
|
294
|
+
Warden::Protocol::NetOutResponse.new
|
295
|
+
response.should == "ok"
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
describe "copy" do
|
301
|
+
describe "request (in)" do
|
302
|
+
subject { to_request ["copy", "handle", "in", "/src", "/dst"] }
|
303
|
+
|
304
|
+
its(:class) { should == Warden::Protocol::CopyInRequest }
|
305
|
+
its(:handle) { should == "handle" }
|
306
|
+
its(:src_path) { should == "/src" }
|
307
|
+
its(:dst_path) { should == "/dst" }
|
308
|
+
end
|
309
|
+
|
310
|
+
describe "request (out)" do
|
311
|
+
subject { to_request ["copy", "handle", "out", "/src", "/dst", "owner"] }
|
312
|
+
|
313
|
+
its(:class) { should == Warden::Protocol::CopyOutRequest }
|
314
|
+
its(:handle) { should == "handle" }
|
315
|
+
its(:src_path) { should == "/src" }
|
316
|
+
its(:dst_path) { should == "/dst" }
|
317
|
+
its(:owner) { should == "owner" }
|
318
|
+
end
|
319
|
+
|
320
|
+
describe "response (in)" do
|
321
|
+
it "should always be ok" do
|
322
|
+
response = to_response \
|
323
|
+
Warden::Protocol::CopyInResponse.new
|
324
|
+
response.should == "ok"
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
describe "response (out)" do
|
329
|
+
it "should always be ok" do
|
330
|
+
response = to_response \
|
331
|
+
Warden::Protocol::CopyOutResponse.new
|
332
|
+
response.should == "ok"
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
describe "limit" do
|
338
|
+
describe "request (mem)" do
|
339
|
+
describe "without limit" do
|
340
|
+
subject { to_request ["limit", "handle", "mem"] }
|
341
|
+
|
342
|
+
its(:class) { should == Warden::Protocol::LimitMemoryRequest }
|
343
|
+
its(:handle) { should == "handle" }
|
344
|
+
its(:limit_in_bytes) { should be_nil }
|
345
|
+
end
|
346
|
+
|
347
|
+
describe "with limit" do
|
348
|
+
subject { to_request ["limit", "handle", "mem", "1234"] }
|
349
|
+
|
350
|
+
its(:class) { should == Warden::Protocol::LimitMemoryRequest }
|
351
|
+
its(:handle) { should == "handle" }
|
352
|
+
its(:limit_in_bytes) { should == 1234 }
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
describe "response (mem)" do
|
357
|
+
it "should return #limit_in_bytes" do
|
358
|
+
response = to_response \
|
359
|
+
Warden::Protocol::LimitMemoryResponse.new({
|
360
|
+
:limit_in_bytes => 1234
|
361
|
+
})
|
362
|
+
response.should == 1234
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
describe "request (disk)" do
|
367
|
+
describe "without limit" do
|
368
|
+
subject { to_request ["limit", "handle", "disk"] }
|
369
|
+
|
370
|
+
its(:class) { should == Warden::Protocol::LimitDiskRequest }
|
371
|
+
its(:handle) { should == "handle" }
|
372
|
+
its(:byte) { should be_nil }
|
373
|
+
end
|
374
|
+
|
375
|
+
describe "with limit" do
|
376
|
+
subject { to_request ["limit", "handle", "disk", "1234"] }
|
377
|
+
|
378
|
+
its(:class) { should == Warden::Protocol::LimitDiskRequest }
|
379
|
+
its(:handle) { should == "handle" }
|
380
|
+
its(:byte) { should == 1234 }
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
describe "response (disk)" do
|
385
|
+
it "should return #byte" do
|
386
|
+
response = to_response \
|
387
|
+
Warden::Protocol::LimitDiskResponse.new({
|
388
|
+
:byte => 1234
|
389
|
+
})
|
390
|
+
response.should == 1234
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
describe "ping" do
|
396
|
+
describe "request" do
|
397
|
+
subject { to_request ["ping"] }
|
398
|
+
|
399
|
+
its(:class) { should == Warden::Protocol::PingRequest }
|
400
|
+
end
|
401
|
+
|
402
|
+
describe "response" do
|
403
|
+
it "should return pong" do
|
404
|
+
response = to_response \
|
405
|
+
Warden::Protocol::PingResponse.new
|
406
|
+
response.should == "pong"
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
describe "list" do
|
412
|
+
describe "request" do
|
413
|
+
subject { to_request ["list"] }
|
414
|
+
|
415
|
+
its(:class) { should == Warden::Protocol::ListRequest }
|
416
|
+
end
|
417
|
+
|
418
|
+
describe "response" do
|
419
|
+
it "should return an array with handles" do
|
420
|
+
response = to_response \
|
421
|
+
Warden::Protocol::ListResponse.new({
|
422
|
+
:handles => ["h1", "h2"]
|
423
|
+
})
|
424
|
+
response.should == ["h1", "h2"]
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
describe "echo" do
|
430
|
+
describe "request" do
|
431
|
+
subject { to_request ["echo", "hello world"] }
|
432
|
+
|
433
|
+
its(:class) { should == Warden::Protocol::EchoRequest }
|
434
|
+
its(:message) { should == "hello world" }
|
435
|
+
end
|
436
|
+
|
437
|
+
describe "response" do
|
438
|
+
it "should return #message" do
|
439
|
+
response = to_response \
|
440
|
+
Warden::Protocol::EchoResponse.new({
|
441
|
+
:message => "hello world"
|
442
|
+
})
|
443
|
+
response.should == "hello world"
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "support/mock_warden_server"
|
3
|
+
|
4
|
+
describe Warden::Client do
|
5
|
+
|
6
|
+
include_context :mock_warden_server
|
7
|
+
|
8
|
+
let(:client) do
|
9
|
+
new_client
|
10
|
+
end
|
11
|
+
|
12
|
+
it "shouldn't be able to connect without a server" do
|
13
|
+
expect do
|
14
|
+
client.connect
|
15
|
+
end.to raise_error
|
16
|
+
|
17
|
+
client.should_not be_connected
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should be able to connect with a server" do
|
21
|
+
start_server
|
22
|
+
|
23
|
+
expect do
|
24
|
+
client.connect
|
25
|
+
end.to_not raise_error
|
26
|
+
|
27
|
+
client.should be_connected
|
28
|
+
end
|
29
|
+
|
30
|
+
context "connection management" do
|
31
|
+
|
32
|
+
# This is super-racy: the ivar is updated from the server thread
|
33
|
+
def connection_count
|
34
|
+
sleep 0.001
|
35
|
+
@sessions.size
|
36
|
+
end
|
37
|
+
|
38
|
+
before(:each) do
|
39
|
+
@sessions = {}
|
40
|
+
start_server do |session, _|
|
41
|
+
@sessions[session] = 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when connected" do
|
46
|
+
|
47
|
+
before(:each) do
|
48
|
+
client.connect
|
49
|
+
client.should be_connected
|
50
|
+
connection_count.should == 1
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should not allow connecting" do
|
54
|
+
expect do
|
55
|
+
client.connect
|
56
|
+
end.to raise_error
|
57
|
+
|
58
|
+
# This should not affect the connection
|
59
|
+
client.should be_connected
|
60
|
+
|
61
|
+
# This should not reconnect
|
62
|
+
connection_count.should == 1
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should allow disconnecting" do
|
66
|
+
client.disconnect
|
67
|
+
client.should_not be_connected
|
68
|
+
|
69
|
+
# This should not reconnect
|
70
|
+
connection_count.should == 1
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should allow reconnecting" do
|
74
|
+
client.reconnect
|
75
|
+
client.should be_connected
|
76
|
+
|
77
|
+
# This should have reconnected
|
78
|
+
connection_count.should == 2
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "when disconnected" do
|
83
|
+
|
84
|
+
before(:each) do
|
85
|
+
connection_count.should == 0
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should not allow disconnecting" do
|
89
|
+
expect do
|
90
|
+
client.disconnect
|
91
|
+
end.to raise_error
|
92
|
+
|
93
|
+
# This should not affect the connection
|
94
|
+
client.should_not be_connected
|
95
|
+
|
96
|
+
# This should not reconnect
|
97
|
+
connection_count.should == 0
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should allow connecting" do
|
101
|
+
client.connect
|
102
|
+
client.should be_connected
|
103
|
+
|
104
|
+
# This should have connected
|
105
|
+
connection_count.should == 1
|
106
|
+
end
|
107
|
+
|
108
|
+
# While it is semantically impossible to reconnect when the client was
|
109
|
+
# never connected to begin with, it IS possible.
|
110
|
+
it "should allow reconnecting" do
|
111
|
+
client.reconnect
|
112
|
+
client.should be_connected
|
113
|
+
|
114
|
+
# This should have connected
|
115
|
+
connection_count.should == 1
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "when connected" do
|
121
|
+
|
122
|
+
before(:each) do
|
123
|
+
container = nil
|
124
|
+
job_id = nil
|
125
|
+
|
126
|
+
start_server do |session, request|
|
127
|
+
next if request.nil?
|
128
|
+
|
129
|
+
if request.class == Warden::Protocol::EchoRequest
|
130
|
+
case request.message
|
131
|
+
when "eof"
|
132
|
+
session.close
|
133
|
+
when "error"
|
134
|
+
args = { :message => "error" }
|
135
|
+
session.respond(Warden::Protocol::ErrorResponse.new(args))
|
136
|
+
else
|
137
|
+
args = { :message => request.message }
|
138
|
+
session.respond(request.create_response(args))
|
139
|
+
end
|
140
|
+
elsif request.class == Warden::Protocol::CreateRequest
|
141
|
+
raise 'Cannot create more than one container' unless container.nil?
|
142
|
+
|
143
|
+
container = "test"
|
144
|
+
args = { :handle => container }
|
145
|
+
session.respond(Warden::Protocol::CreateResponse.new(args))
|
146
|
+
elsif request.class == Warden::Protocol::SpawnRequest
|
147
|
+
raise 'Unknown handle' unless request.handle == container
|
148
|
+
raise 'Cannot spawn more than one job' unless job_id.nil?
|
149
|
+
|
150
|
+
job_id = 1
|
151
|
+
args = { :job_id => job_id }
|
152
|
+
session.respond(Warden::Protocol::SpawnResponse.new(args))
|
153
|
+
elsif request.class == Warden::Protocol::StreamRequest
|
154
|
+
raise 'Unknown handle' unless request.handle == container
|
155
|
+
raise 'Unknown job' unless request.job_id == job_id
|
156
|
+
|
157
|
+
args = { :name => "stream", :data => "test" }
|
158
|
+
session.respond(Warden::Protocol::StreamResponse.new(args))
|
159
|
+
args = { :exit_status => 0 }
|
160
|
+
session.respond(Warden::Protocol::StreamResponse.new(args))
|
161
|
+
else
|
162
|
+
raise "Unknown request type: #{request.class}."
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
client.connect
|
167
|
+
client.should be_connected
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should raise EOFError on eof" do
|
171
|
+
expect do
|
172
|
+
client.echo(:message => "eof")
|
173
|
+
end.to raise_error(::EOFError)
|
174
|
+
|
175
|
+
# This should update the connection status
|
176
|
+
client.should_not be_connected
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should raise Warden::Client::ServerError on error payloads" do
|
180
|
+
expect do
|
181
|
+
client.echo(:message => "error")
|
182
|
+
end.to raise_error(Warden::Client::ServerError)
|
183
|
+
|
184
|
+
# This should not affect the connection
|
185
|
+
client.should be_connected
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should return decoded payload for non-error replies" do
|
189
|
+
response = client.echo(:message => "hello")
|
190
|
+
response.message.should == "hello"
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should work when called with the old API" do
|
194
|
+
response = client.call(["echo", "hello"])
|
195
|
+
response.should == "hello"
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should stream data" do
|
199
|
+
handle = client.create.handle
|
200
|
+
response = client.spawn(:handle => handle, :script => "echo test")
|
201
|
+
|
202
|
+
called = false
|
203
|
+
block = lambda do |response|
|
204
|
+
raise "Block should not be called more than once." if called
|
205
|
+
|
206
|
+
response.should be_an_instance_of Warden::Protocol::StreamResponse
|
207
|
+
response.data.should == "test"
|
208
|
+
response.name.should == "stream"
|
209
|
+
response.exit_status.should be_nil
|
210
|
+
|
211
|
+
called = true
|
212
|
+
end
|
213
|
+
|
214
|
+
request = Warden::Protocol::StreamRequest.new(:handle => handle,
|
215
|
+
:job_id => response.job_id)
|
216
|
+
response = client.stream(request, &block)
|
217
|
+
response.data.should be_nil
|
218
|
+
response.name.should be_nil
|
219
|
+
response.exit_status.should == 0
|
220
|
+
|
221
|
+
called.should be_true
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require "socket"
|
2
|
+
require "tempfile"
|
3
|
+
require "warden/protocol"
|
4
|
+
|
5
|
+
class Session
|
6
|
+
|
7
|
+
def initialize(sock, handler = nil)
|
8
|
+
@sock = sock
|
9
|
+
@handler = handler
|
10
|
+
|
11
|
+
# Post-initialization
|
12
|
+
handle(nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
def handle(request)
|
16
|
+
@handler.call(self, request) if @handler
|
17
|
+
end
|
18
|
+
|
19
|
+
def close
|
20
|
+
@sock.close
|
21
|
+
ensure
|
22
|
+
@sock = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def respond(*responses)
|
26
|
+
responses.each do |response|
|
27
|
+
data = response.wrap.encode.to_s
|
28
|
+
@sock.write data.length.to_s + "\r\n"
|
29
|
+
@sock.write data + "\r\n"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def run!
|
34
|
+
while @sock && length = @sock.gets
|
35
|
+
data = @sock.read(length.to_i)
|
36
|
+
|
37
|
+
# Discard \r\n
|
38
|
+
@sock.read(2)
|
39
|
+
|
40
|
+
handle(Warden::Protocol::Message.decode(data).request)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
shared_context :mock_warden_server do
|
46
|
+
|
47
|
+
SERVER_PATH = File.expand_path("../../../tmp/mock_server.sock", __FILE__)
|
48
|
+
|
49
|
+
def new_client
|
50
|
+
Warden::Client.new(SERVER_PATH)
|
51
|
+
end
|
52
|
+
|
53
|
+
def start_server(&blk)
|
54
|
+
# Make sure the path to the unix socket is not used
|
55
|
+
FileUtils.rm_rf(SERVER_PATH)
|
56
|
+
|
57
|
+
# Create unix socket server
|
58
|
+
server = UNIXServer.new(SERVER_PATH)
|
59
|
+
|
60
|
+
# Accept new connections from a thread
|
61
|
+
@server = Thread.new do
|
62
|
+
begin
|
63
|
+
loop do
|
64
|
+
session = Session.new(server.accept, blk)
|
65
|
+
session.run!
|
66
|
+
end
|
67
|
+
rescue => ex
|
68
|
+
STDERR.puts ex.message
|
69
|
+
STDERR.puts ex.backtrace
|
70
|
+
raise
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
after(:each) do
|
76
|
+
@server.kill if @server
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "warden/client/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "warden-client"
|
7
|
+
s.version = Warden::Client::VERSION
|
8
|
+
s.authors = ["Pieter Noordhuis", "Matt Page"]
|
9
|
+
s.email = ["pcnoordhuis@gmail.com", "mpage@vmware.com"]
|
10
|
+
s.homepage = "http://www.cloudfoundry.org/"
|
11
|
+
s.summary = %q{Client driver for warden, the ephemeral container manager.}
|
12
|
+
s.description = %q{Provides a blocking client for interacting with the Warden.}
|
13
|
+
|
14
|
+
s.files = Dir.glob("**/*")
|
15
|
+
s.test_files = Dir.glob("spec/**/*")
|
16
|
+
s.executables = []
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_runtime_dependency "warden-protocol", "~> 0.1.0"
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: warden-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Pieter Noordhuis
|
9
|
+
- Matt Page
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-06-12 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: warden-protocol
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.1.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 0.1.0
|
31
|
+
description: Provides a blocking client for interacting with the Warden.
|
32
|
+
email:
|
33
|
+
- pcnoordhuis@gmail.com
|
34
|
+
- mpage@vmware.com
|
35
|
+
executables: []
|
36
|
+
extensions: []
|
37
|
+
extra_rdoc_files: []
|
38
|
+
files:
|
39
|
+
- Gemfile
|
40
|
+
- lib/warden/client/v1.rb
|
41
|
+
- lib/warden/client/version.rb
|
42
|
+
- lib/warden/client.rb
|
43
|
+
- Rakefile
|
44
|
+
- README.md
|
45
|
+
- spec/client/v1_spec.rb
|
46
|
+
- spec/client_spec.rb
|
47
|
+
- spec/spec_helper.rb
|
48
|
+
- spec/support/mock_warden_server.rb
|
49
|
+
- warden-client.gemspec
|
50
|
+
homepage: http://www.cloudfoundry.org/
|
51
|
+
licenses: []
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.8.25
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: Client driver for warden, the ephemeral container manager.
|
74
|
+
test_files:
|
75
|
+
- spec/client/v1_spec.rb
|
76
|
+
- spec/client_spec.rb
|
77
|
+
- spec/spec_helper.rb
|
78
|
+
- spec/support/mock_warden_server.rb
|