abundance 1.1.2 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/garden.rb +58 -62
- data/lib/gardener.rb +7 -8
- data/lib/toolshed.rb +64 -110
- metadata +2 -2
data/lib/garden.rb
CHANGED
@@ -32,68 +32,67 @@ class Garden
|
|
32
32
|
def initialize
|
33
33
|
@pid = fork do
|
34
34
|
@quit = false; @full_crop = false; @do_init = nil; @seed_all = nil; @init_all_crop = []
|
35
|
-
@harvest = []; @
|
35
|
+
@harvest = []; @rows_socket_paths = []; @init_done = []; @seed_all_done = []; @seed_all_crop = []
|
36
36
|
@seeds = []; @sprouts = []; @crops = []; @id = 0
|
37
|
-
|
38
|
-
@socket_client_temp = Toolshed.socket_client_temp
|
37
|
+
set_my_socket_as_a(:garden)
|
39
38
|
loop do
|
40
39
|
catch :fill_rows do
|
41
40
|
loop do
|
42
|
-
if ! @seed_all.nil? && ! @
|
43
|
-
|
44
|
-
unless @seed_all_done.include?(
|
45
|
-
|
46
|
-
@seed_all_done <<
|
41
|
+
if ! @seed_all.nil? && ! @rows_socket_paths.empty? && @seed_all_done.size != @seed_all[0]
|
42
|
+
row_socket_path = @rows_socket_paths.shift
|
43
|
+
unless @seed_all_done.include?( row_socket_path )
|
44
|
+
socket_send(:seed_all,@seed_all[1],row_socket_path)
|
45
|
+
@seed_all_done << row_socket_path
|
47
46
|
else
|
48
|
-
@
|
47
|
+
@rows_socket_paths << row_socket_path
|
49
48
|
end
|
50
|
-
elsif ! @do_init.nil? && ! @
|
51
|
-
|
52
|
-
unless @init_done.include?(
|
53
|
-
|
54
|
-
@init_done <<
|
49
|
+
elsif ! @do_init.nil? && ! @rows_socket_paths.empty? && @init_done.size != @do_init
|
50
|
+
row_socket_path = @rows_socket_paths.shift
|
51
|
+
unless @init_done.include?( row_socket_path )
|
52
|
+
socket_send(:init,'init_status',row_socket_path)
|
53
|
+
@init_done << row_socket_path
|
55
54
|
else
|
56
|
-
@
|
55
|
+
@rows_socket_paths << row_socket_path
|
57
56
|
end
|
58
|
-
elsif ! @seeds.empty? && ! @
|
57
|
+
elsif ! @seeds.empty? && ! @rows_socket_paths.empty?
|
59
58
|
seed = @seeds.shift
|
60
59
|
@sprouts[seed[:id]] = seed
|
61
|
-
|
62
|
-
|
63
|
-
elsif @quit && ! @
|
60
|
+
row_socket_path = @rows_socket_paths.shift
|
61
|
+
socket_send(:sprout,seed,row_socket_path)
|
62
|
+
elsif @quit && ! @rows_socket_paths.empty?
|
64
63
|
seed = nil
|
65
|
-
|
66
|
-
|
64
|
+
row_socket_path = @rows_socket_paths.shift
|
65
|
+
socket_send(:quit,seed,row_socket_path)
|
67
66
|
else
|
68
67
|
throw :fill_rows
|
69
68
|
end
|
70
69
|
end
|
71
70
|
end
|
72
|
-
command, data,
|
71
|
+
command, data, client_socket_path = socket_recv
|
73
72
|
case command
|
74
73
|
when :seed
|
75
74
|
@id += 1; @seeds << {:id => @id , :seed => data}
|
76
|
-
|
75
|
+
socket_send(command,@id,client_socket_path)
|
77
76
|
when :row
|
78
77
|
if @quit
|
79
78
|
command = :quit
|
80
79
|
seed = nil
|
81
80
|
elsif @seeds.empty?
|
82
81
|
seed = nil
|
83
|
-
@
|
82
|
+
@rows_socket_paths << data
|
84
83
|
else
|
85
84
|
seed = @seeds.shift
|
86
85
|
@sprouts[seed[:id]] = seed
|
87
86
|
end
|
88
|
-
|
87
|
+
socket_send(command,seed,client_socket_path)
|
89
88
|
when :crop
|
90
89
|
@sprouts[data[:id]] = nil
|
91
|
-
@crops[data[:id]] = data
|
90
|
+
@crops[data[:id]] = data
|
92
91
|
if @harvest[data[:id]]
|
93
|
-
|
92
|
+
socket_send(command,data, @harvest[data[:id]][:client_socket_path])
|
94
93
|
@crops[data[:id]] = @harvest[data[:id]] = nil
|
95
94
|
elsif @full_crop && @seeds.compact.empty? && @sprouts.compact.empty?
|
96
|
-
|
95
|
+
socket_send(command,@crops.compact,@mem_client_socket_path)
|
97
96
|
@crops.clear; @full_crop = false
|
98
97
|
end
|
99
98
|
when :growth
|
@@ -101,81 +100,79 @@ class Garden
|
|
101
100
|
when :progress
|
102
101
|
value = @crops.size.to_f / (@crops.size + @sprouts.compact.size + @seeds.size)
|
103
102
|
value = 1 if value.nan?; progress = sprintf( "%.2f", value)
|
104
|
-
|
103
|
+
socket_send(command,progress,client_socket_path)
|
105
104
|
when :seed
|
106
|
-
|
105
|
+
socket_send(command,@seeds.size,client_socket_path)
|
107
106
|
when :sprout
|
108
|
-
|
107
|
+
socket_send(command,@sprouts.compact.size,client_socket_path)
|
109
108
|
when :crop
|
110
|
-
|
109
|
+
socket_send(command,@crops.size,client_socket_path)
|
111
110
|
else
|
112
|
-
|
111
|
+
socket_send(command,false,client_socket_path)
|
113
112
|
end
|
114
113
|
when :harvest
|
115
114
|
case data
|
116
115
|
when :all
|
117
|
-
|
116
|
+
socket_send(command,{:seeds => @seeds, :sprouts => @sprouts.compact, :crops => @crops.compact},client_socket_path)
|
118
117
|
when :seed
|
119
|
-
|
118
|
+
socket_send(command,@seeds,client_socket_path)
|
120
119
|
when :sprout
|
121
|
-
|
120
|
+
socket_send(command,@sprouts.compact,client_socket_path)
|
122
121
|
when :crop
|
123
|
-
|
122
|
+
socket_send(command,@crops.compact,client_socket_path)
|
124
123
|
@crops.clear
|
125
124
|
when :full_crop
|
126
125
|
if @seeds.compact.empty? && @sprouts.compact.empty?
|
127
|
-
|
126
|
+
socket_send(command,@crops.compact,client_socket_path)
|
128
127
|
@crops.clear
|
129
128
|
else
|
130
129
|
@full_crop = true
|
131
|
-
@
|
130
|
+
@mem_client_socket_path = client_socket_path
|
132
131
|
end
|
133
132
|
else
|
134
133
|
if data.is_a? Integer
|
135
134
|
if @crops[data]
|
136
|
-
|
135
|
+
socket_send(command,@crops[data],client_socket_path)
|
137
136
|
@crops[data] = nil
|
138
137
|
else
|
139
|
-
@harvest[data] = {:
|
138
|
+
@harvest[data] = {:client_socket_path => client_socket_path}
|
140
139
|
end
|
141
140
|
else
|
142
|
-
|
141
|
+
socket_send(command,false,client_socket_path)
|
143
142
|
end
|
144
143
|
end
|
145
144
|
when :init
|
146
145
|
@do_init = data
|
147
|
-
@init_return = {:
|
146
|
+
@init_return = {:client_socket_path => client_socket_path}
|
148
147
|
when :init_crop
|
149
|
-
socket_server_send(command,true,clientaddr,clientport)
|
150
148
|
@init_all_crop << data
|
151
149
|
if @init_all_crop.size == @do_init
|
152
|
-
|
150
|
+
socket_send(command,@init_all_crop, @init_return[:client_socket_path])
|
153
151
|
@init_return = Hash.new; @init_done = Array.new; @do_init = nil; @init_all_crop = Array.new
|
154
152
|
end
|
155
153
|
when :seed_all
|
156
154
|
@seed_all = data
|
157
|
-
@seed_all_return = {:
|
155
|
+
@seed_all_return = {:client_socket_path => client_socket_path, :data => []}
|
158
156
|
when :seed_all_crop
|
159
|
-
socket_server_send(command,true,clientaddr,clientport)
|
160
157
|
@seed_all_crop << data
|
161
158
|
if @seed_all_crop.size == @seed_all[0]
|
162
|
-
|
159
|
+
socket_send(command,@seed_all_crop, @seed_all_return[:client_socket_path])
|
163
160
|
@seed_all = nil; @seed_all_return = Hash.new; @seed_all_done = Array.new; @seed_all_crop = Array.new
|
164
161
|
end
|
165
162
|
when :close
|
166
163
|
if data[:level] == :garden
|
167
164
|
@seeds_pid = data[:pid]
|
168
165
|
@quit = true
|
169
|
-
@
|
166
|
+
@mem_client_socket_path = client_socket_path
|
170
167
|
else
|
171
168
|
@seeds_pid.delete(data[:pid].to_i)
|
172
169
|
if @seeds_pid.empty?
|
173
|
-
|
170
|
+
socket_send(:close,{:seeds => @seeds, :sprouts => @sprouts.compact, :crops => @crops.compact}, @mem_client_socket_path)
|
174
171
|
exit
|
175
172
|
end
|
176
173
|
end
|
177
174
|
else
|
178
|
-
|
175
|
+
socket_send(command,false,client_socket_path)
|
179
176
|
end
|
180
177
|
end
|
181
178
|
end
|
@@ -194,7 +191,7 @@ class Garden
|
|
194
191
|
#
|
195
192
|
|
196
193
|
def rows(rows,init_timeout,grow_block)
|
197
|
-
Rows.new(rows,init_timeout,grow_block)
|
194
|
+
Rows.new(rows,init_timeout,@pid,grow_block)
|
198
195
|
end
|
199
196
|
|
200
197
|
# :title:Rows
|
@@ -213,17 +210,16 @@ class Garden
|
|
213
210
|
# === Parameter
|
214
211
|
# * _rows_ = garden rows number, the number of concurent threads
|
215
212
|
# * _init_timeout_ = allow to pause execution to allow for larger garden rows to initialize
|
213
|
+
# * _garden_pid_ = the parent Garden's pid, for loopback communication purpose
|
216
214
|
# === Example
|
217
215
|
# rows = Rows.new(4,2) { grow_block }
|
218
216
|
|
219
|
-
def initialize(rows,init_timeout,gardener_block)
|
217
|
+
def initialize(rows,init_timeout,garden_pid,gardener_block)
|
220
218
|
@pids = []
|
221
219
|
rows.times do
|
222
|
-
row_port = Toolshed.available_port
|
223
220
|
@pids << fork do
|
224
|
-
@socket_client_perm = Toolshed.socket_client_perm
|
225
221
|
@seed_all = false
|
226
|
-
|
222
|
+
set_my_socket_as_a(:row,garden_pid)
|
227
223
|
t1 = Thread.new do
|
228
224
|
gardener_block.call
|
229
225
|
end
|
@@ -231,15 +227,15 @@ class Garden
|
|
231
227
|
t2 = Thread.new do
|
232
228
|
loop do
|
233
229
|
if $seed.nil?
|
234
|
-
command, data =
|
230
|
+
command, data = socket_duplex(:row,my_socket_path)
|
235
231
|
if command == :quit
|
236
232
|
pid = Process.pid
|
237
|
-
|
233
|
+
socket_send(:close,{:level => :seed, :pid => pid})
|
238
234
|
exit
|
239
235
|
end
|
240
236
|
$seed = data
|
241
237
|
if $seed.nil?
|
242
|
-
command, data,
|
238
|
+
command, data, client_socket_path = socket_recv
|
243
239
|
case command
|
244
240
|
when :sprout
|
245
241
|
$seed = data
|
@@ -248,15 +244,15 @@ class Garden
|
|
248
244
|
$seed = {:id => Process.pid, :seed => data}
|
249
245
|
when :init
|
250
246
|
$init = {:seed => 'init_status', :message => 'No Init Message', :id => Process.pid} if $init.nil?
|
251
|
-
|
247
|
+
socket_send(:init_crop,$init)
|
252
248
|
end
|
253
249
|
end
|
254
250
|
elsif ! $seed[:success].nil?
|
255
251
|
if @seed_all
|
256
|
-
|
252
|
+
socket_send(:seed_all_crop,$seed)
|
257
253
|
@seed_all = false
|
258
254
|
else
|
259
|
-
|
255
|
+
socket_send(:crop,$seed)
|
260
256
|
end
|
261
257
|
$seed = nil;
|
262
258
|
else
|
data/lib/gardener.rb
CHANGED
@@ -35,12 +35,11 @@ class Gardener
|
|
35
35
|
elsif options[:wheelbarrow] < 1024 then 1024
|
36
36
|
else options[:wheelbarrow]
|
37
37
|
end
|
38
|
-
Toolshed::garden_port = Toolshed.available_port
|
39
38
|
|
40
39
|
@garden = Garden.new
|
41
40
|
@garden_rows = @garden.rows(options[:rows], options[:init_timeout], gardener_block)
|
42
41
|
|
43
|
-
|
42
|
+
set_my_socket_as_a(:gardener,@garden.pid)
|
44
43
|
end
|
45
44
|
|
46
45
|
# The +init_status+ method for the Gardener instance allow to harvest an initialisation status message
|
@@ -49,7 +48,7 @@ class Gardener
|
|
49
48
|
# === Example
|
50
49
|
# puts gardener.init_status.inspect # => [{:message=>"init ok", :success=>true, :pid=>4760}, {:message=>"init failed", :success=>false, :pid=>4761}]
|
51
50
|
def init_status
|
52
|
-
command, data =
|
51
|
+
command, data = socket_duplex(:init,@garden_rows.pids.size)
|
53
52
|
data.map! do |row|
|
54
53
|
{:success => row[:success], :message => row[:message], :pid => row[:id]}
|
55
54
|
end
|
@@ -63,7 +62,7 @@ class Gardener
|
|
63
62
|
# id_seed_1 = gardener.seed("system 'ruby -v'")
|
64
63
|
|
65
64
|
def seed(command)
|
66
|
-
command, data =
|
65
|
+
command, data = socket_duplex(:seed,command)
|
67
66
|
return data
|
68
67
|
end
|
69
68
|
|
@@ -76,7 +75,7 @@ class Gardener
|
|
76
75
|
# {:success=>true, :message=>["row pref changed to local"], :seed=>"pref local", :pid=>14913}]
|
77
76
|
def seed_all(command)
|
78
77
|
seed = [@garden_rows.pids.size, command]
|
79
|
-
command, data =
|
78
|
+
command, data = socket_duplex(:seed_all, seed)
|
80
79
|
data.map! do |row|
|
81
80
|
{:success => row[:success], :message => row[:message], :pid => row[:id]}
|
82
81
|
end
|
@@ -95,7 +94,7 @@ class Gardener
|
|
95
94
|
# puts "progress is now #{progress}" # => progress is now 0.75
|
96
95
|
|
97
96
|
def growth(crop=:progress)
|
98
|
-
command, data =
|
97
|
+
command, data = socket_duplex(:growth,crop)
|
99
98
|
return data
|
100
99
|
end
|
101
100
|
|
@@ -114,7 +113,7 @@ class Gardener
|
|
114
113
|
# seed1_result = gardener.harvest(id_seed_1)
|
115
114
|
|
116
115
|
def harvest(crop)
|
117
|
-
command, data =
|
116
|
+
command, data = socket_duplex(:harvest,crop)
|
118
117
|
return data
|
119
118
|
end
|
120
119
|
|
@@ -124,7 +123,7 @@ class Gardener
|
|
124
123
|
# final_harvest = gardener.close
|
125
124
|
|
126
125
|
def close
|
127
|
-
command, data =
|
126
|
+
command, data = socket_duplex(:close,{:level => :garden, :pid => @garden_rows.pids})
|
128
127
|
return data
|
129
128
|
end
|
130
129
|
|
data/lib/toolshed.rb
CHANGED
@@ -10,142 +10,96 @@
|
|
10
10
|
#
|
11
11
|
# :title:Toolshed
|
12
12
|
|
13
|
+
# TODO:
|
14
|
+
# -dedicated server on garden for 'ready!' rows where rows go write their PID
|
15
|
+
|
13
16
|
module Toolshed
|
17
|
+
require 'ftools'
|
14
18
|
require 'socket'
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
# The Toolshed.available_port method scans for a an available UDP port and returns its value.
|
20
|
-
def Toolshed.available_port
|
21
|
-
port = @@start_port + 1
|
22
|
-
catch :scan_port do
|
23
|
-
stat = `netstat`
|
24
|
-
loop do
|
25
|
-
throw :scan_port unless stat =~ /localhost\.#{port}/
|
26
|
-
port += 1
|
27
|
-
end
|
28
|
-
end
|
29
|
-
@@start_port = port
|
30
|
-
return port
|
31
|
-
end
|
32
|
-
|
33
|
-
# The Toolshed.socket_client_perm method creates a client socket permanently connected to the main server socket. Returns the client socket object.
|
34
|
-
def Toolshed.socket_client_perm
|
35
|
-
socket = UDPSocket.new
|
36
|
-
socket.connect(UDP_HOST,@@garden_port)
|
37
|
-
return socket
|
38
|
-
end
|
39
|
-
|
40
|
-
# The Toolshed.socket_client_temp method creates a client socket available for changing connections to multiple socket servers. Returns the client socket object.
|
41
|
-
def Toolshed.socket_client_temp
|
42
|
-
UDPSocket.new
|
43
|
-
end
|
44
|
-
|
45
|
-
# The Toolshed.socket_server method creates a permanent main server socket. Returns the server socket object.
|
46
|
-
def Toolshed.socket_server(port)
|
47
|
-
socket_server = UDPSocket.new
|
48
|
-
socket_server.bind(nil,port)
|
49
|
-
return socket_server
|
50
|
-
end
|
51
|
-
|
19
|
+
SOCKET_ROOT = '/tmp/abundance/'
|
20
|
+
Dir.mkdir(SOCKET_ROOT) unless File.exist?(SOCKET_ROOT)
|
21
|
+
|
52
22
|
# The Toolshed::block_size= method sets the UDP block size for socket operations.
|
53
23
|
def Toolshed::block_size=(block_size)
|
54
24
|
@@block_size = block_size
|
55
25
|
end
|
56
26
|
|
57
|
-
# The
|
58
|
-
def Toolshed::block_size
|
59
|
-
@@block_size
|
60
|
-
end
|
27
|
+
# The +assign_sockets+ method assign a value client and server variables
|
61
28
|
|
62
|
-
|
63
|
-
|
64
|
-
|
29
|
+
def set_my_socket_as_a(role,garden_pid=Process.pid)
|
30
|
+
case role
|
31
|
+
when :garden
|
32
|
+
set_my_socket
|
33
|
+
else
|
34
|
+
set_garden_path(garden_pid)
|
35
|
+
set_my_socket
|
36
|
+
end
|
65
37
|
end
|
66
38
|
|
67
|
-
|
68
|
-
|
69
|
-
@@garden_port
|
39
|
+
def my_socket_path
|
40
|
+
@my_socket_path
|
70
41
|
end
|
71
42
|
|
72
|
-
#
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
end
|
77
|
-
recv_block,address = block_filter { @socket_client_perm.recvfrom(@@block_size) }
|
78
|
-
return Marshal.load(recv_block)
|
43
|
+
#
|
44
|
+
|
45
|
+
def socket_send(command,data,server_socket_path=@garden_path)
|
46
|
+
send_block(command,data,server_socket_path)
|
79
47
|
end
|
80
48
|
|
81
|
-
def
|
82
|
-
|
83
|
-
|
84
|
-
end
|
49
|
+
def socket_duplex(command,data,server_socket_path=@garden_path)
|
50
|
+
send_block(command,data,server_socket_path)
|
51
|
+
Marshal.load(recv_whole_block)
|
85
52
|
end
|
53
|
+
|
54
|
+
def socket_recv
|
55
|
+
Marshal.load(recv_whole_block)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
86
59
|
|
87
|
-
|
88
|
-
|
89
|
-
@socket_client_temp.connect(UDP_HOST,port)
|
90
|
-
block_splitter([command,data]) do |block|
|
91
|
-
@socket_client_temp.send(block,0)
|
92
|
-
end
|
60
|
+
def socket_path(pid)
|
61
|
+
File.catname(pid.to_s,SOCKET_ROOT)
|
93
62
|
end
|
94
63
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
command, data = Marshal.load(block)
|
100
|
-
return command, data, clientport, clientname, clientaddr
|
64
|
+
def set_my_socket
|
65
|
+
@my_socket_path = socket_path(Process.pid)
|
66
|
+
File.delete(@my_socket_path) if File.exist?(@my_socket_path)
|
67
|
+
@my_socket = UNIXServer.open(@my_socket_path)
|
101
68
|
end
|
102
69
|
|
103
|
-
|
104
|
-
|
105
|
-
block_splitter([command,data]) do |block|
|
106
|
-
@socket_server.send(block, 0, clientaddr, clientport)
|
107
|
-
end
|
70
|
+
def set_garden_path(garden_pid)
|
71
|
+
@garden_path = socket_path(garden_pid)
|
108
72
|
end
|
109
73
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
74
|
+
def recv_whole_block
|
75
|
+
begin
|
76
|
+
client = @my_socket.accept; block = []
|
77
|
+
catch :whole_block do
|
78
|
+
loop do
|
79
|
+
packet = client.recvfrom(@@block_size)[0]
|
80
|
+
if packet == ''
|
81
|
+
throw :whole_block
|
82
|
+
else
|
83
|
+
block << packet
|
84
|
+
end
|
85
|
+
end
|
120
86
|
end
|
121
|
-
|
122
|
-
|
87
|
+
return block.join
|
88
|
+
rescue Errno::EADDRINUSE
|
89
|
+
retry
|
123
90
|
end
|
124
91
|
end
|
125
92
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
elsif block_array[0] == :block_part
|
136
|
-
@@sessions[block_array[1]] = {} if @@sessions[block_array[1]].nil?
|
137
|
-
@@sessions[block_array[1]][:data] = [] if @@sessions[block_array[1]][:data].nil?
|
138
|
-
@@sessions[block_array[1]][:data][block_array[2]] = block_array[3]
|
139
|
-
else
|
140
|
-
return block,address
|
141
|
-
end
|
142
|
-
if ! @@sessions[block_array[1]].nil? && ! @@sessions[block_array[1]][:data].nil? && @@sessions[block_array[1]][:data].size == @@sessions[block_array[1]][:size]
|
143
|
-
block = @@sessions[block_array[1]][:data].join
|
144
|
-
address = @@sessions[block_array[1]][:address]
|
145
|
-
@@sessions[block_array[1]] = nil
|
146
|
-
return block,address
|
147
|
-
end
|
93
|
+
def send_block(command,data,server_socket_path)
|
94
|
+
begin
|
95
|
+
client = UNIXSocket.open(server_socket_path)
|
96
|
+
client.send(Marshal.dump([command,data,@my_socket_path]),0)
|
97
|
+
client.close
|
98
|
+
rescue Errno::EADDRINUSE
|
99
|
+
retry
|
100
|
+
rescue Errno::ECONNREFUSED
|
101
|
+
retry
|
148
102
|
end
|
149
103
|
end
|
150
|
-
|
104
|
+
|
151
105
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: abundance
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Louis-Philippe Perron
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-01-
|
12
|
+
date: 2009-01-11 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|