rubysync 0.1.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/HISTORY.txt +11 -0
- data/Manifest.txt +4 -1
- data/Rakefile +3 -4
- data/bin/rubysync +242 -152
- data/bin/rubysync.rb +242 -152
- data/lib/ruby_sync.rb +1 -1
- data/lib/ruby_sync/connectors/base_connector.rb +286 -378
- data/lib/ruby_sync/connectors/connector_event_processing.rb +24 -20
- data/lib/ruby_sync/connectors/csv_file_connector.rb +3 -5
- data/lib/ruby_sync/connectors/dbm_association_tracking.rb +88 -0
- data/lib/ruby_sync/connectors/dbm_change_tracking.rb +110 -0
- data/lib/ruby_sync/connectors/ldap_changelog_connector.rb +79 -78
- data/lib/ruby_sync/connectors/ldap_connector.rb +84 -57
- data/lib/ruby_sync/connectors/memory_association_tracking.rb +60 -0
- data/lib/ruby_sync/connectors/memory_change_tracking.rb +84 -0
- data/lib/ruby_sync/connectors/memory_connector.rb +3 -0
- data/lib/ruby_sync/connectors/xml_connector.rb +6 -2
- data/lib/ruby_sync/event.rb +73 -50
- data/lib/ruby_sync/operation.rb +3 -3
- data/lib/ruby_sync/pipelines/base_pipeline.rb +76 -48
- data/lib/ruby_sync/util/utilities.rb +72 -29
- data/test/tc_csv_file_connector.rb +2 -2
- data/test/tc_ldap_connector.rb +2 -2
- data/test/tc_memory_connectors.rb +0 -2
- data/test/tc_transformation.rb +14 -13
- data/test/tc_xml_connectors.rb +4 -2
- data/test/ts_rubysync.rb +1 -1
- metadata +102 -84
- metadata.gz.sig +0 -0
- data/lib/ruby_sync/connectors/ldap_associations.rb +0 -126
data/bin/rubysync.rb
CHANGED
@@ -1,77 +1,77 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
4
|
+
# == Synopsis
|
5
|
+
#
|
6
|
+
# Command line tool for running *rubysync* <em>A Free MetaDirectory.</em>
|
7
|
+
#
|
8
|
+
# == Usage
|
9
|
+
#
|
10
|
+
# rubysync command name [options]
|
11
|
+
#
|
12
|
+
# Valid commands are::
|
13
|
+
# * create {name}:: Create a rubysync configuration directory
|
14
|
+
#
|
15
|
+
# * connector {name} -t {type} [--vault {name}] [--client {name}]
|
16
|
+
# ; Create a connector of the given name in
|
17
|
+
# ; the current rubysync configuration directory
|
18
|
+
#
|
19
|
+
# * fields {name} ; list the fields detected by the named connector
|
20
|
+
#
|
21
|
+
# * pipeline {name} ; Create a rubysync pipeline of the given name
|
22
|
+
# ; in the current rubysync configuration directory
|
23
|
+
#
|
24
|
+
# * once {name}::
|
25
|
+
# Execute the named pipeline within the current configuration directory once and then exit
|
26
|
+
#
|
27
|
+
# * example:: Show an example of how this command might be used
|
28
|
+
#
|
29
|
+
# == Example
|
30
|
+
#
|
31
|
+
# This sets up the skeleton of a configuration for importing comma delimeted
|
32
|
+
# text files into an xml file.
|
33
|
+
# <tt>
|
34
|
+
# $ rubysync create xml_demo
|
35
|
+
# $ cd xml_demo
|
36
|
+
# $ rubysync connector my_csv -t csv_file
|
37
|
+
# $ rubysync connector my_xml -t xml
|
38
|
+
# </tt>
|
39
|
+
#
|
40
|
+
# You would then edit the files::
|
41
|
+
#
|
42
|
+
# * +connectors/my_csv_connector.rb+:: where to get the CSV files, field names, etc
|
43
|
+
# * +connectors/my_xml_connector.rb+:: how to connect to your XML file.
|
44
|
+
#
|
45
|
+
# And enter::
|
46
|
+
# <tt>
|
47
|
+
# $ rubysync pipeline my_pipeline -C my_csv -V my_xml
|
48
|
+
# </tt>
|
49
|
+
#
|
50
|
+
# You would then edit the file +pipelines/my_pipeline.rb+ to configure the
|
51
|
+
# policy for synchronizing between the two connectors.
|
52
|
+
#
|
53
|
+
# You may then execute the pipeline in one-shot mode (daemon mode is coming)::
|
54
|
+
#
|
55
|
+
# <tt>
|
56
|
+
# $ rubysync once my_pipeline
|
57
|
+
# </tt>
|
58
|
+
#
|
59
|
+
# == Author
|
60
|
+
# Ritchie Young, 9 to 5 Magic (http://9to5magic.com.au)
|
61
|
+
#
|
62
|
+
# == Copyright
|
63
|
+
# Copyright (c) 2007 Ritchie Young. All rights reserved.
|
64
|
+
#
|
65
|
+
# This file is part of RubySync.
|
66
|
+
#
|
67
|
+
# RubySync is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
|
68
|
+
# as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
|
69
|
+
#
|
70
|
+
# RubySync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
71
|
+
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
72
|
+
#
|
73
|
+
# You should have received a copy of the GNU General Public License along with RubySync; if not, write to the
|
74
|
+
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
75
75
|
|
76
76
|
|
77
77
|
lib_path = File.dirname(__FILE__) + '/../lib'
|
@@ -87,12 +87,15 @@ class Controller < SimpleConsole::Controller
|
|
87
87
|
|
88
88
|
before_filter :configure_logging
|
89
89
|
|
90
|
-
params
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
90
|
+
params(
|
91
|
+
:int =>{:v => :verbose, :d => :delay},
|
92
|
+
:string => {:p => :pipe,
|
93
|
+
:t => :type,
|
94
|
+
:V => :vault,
|
95
|
+
:C => :client},
|
96
|
+
:bool =>{:n => :no_edit}
|
97
|
+
)
|
98
|
+
|
96
99
|
|
97
100
|
def default
|
98
101
|
#RDoc::usage 'Usage'
|
@@ -124,36 +127,41 @@ class Controller < SimpleConsole::Controller
|
|
124
127
|
end
|
125
128
|
end
|
126
129
|
|
127
|
-
|
128
|
-
|
129
130
|
# Create a Rubysync project directory
|
130
131
|
def create
|
131
132
|
config_path = params[:id]
|
132
133
|
ensure_dir_exists([
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
134
|
+
config_path,
|
135
|
+
"#{config_path}/pipelines",
|
136
|
+
"#{config_path}/connectors",
|
137
|
+
"#{config_path}/shared",
|
138
|
+
"#{config_path}/shared/pipelines",
|
139
|
+
"#{config_path}/shared/connectors",
|
140
|
+
"#{config_path}/shared/lib",
|
141
|
+
"#{config_path}/log",
|
142
|
+
"#{config_path}/db"
|
143
|
+
])
|
143
144
|
end
|
144
145
|
|
145
146
|
# Create a connector configuration file
|
146
147
|
def connector
|
147
148
|
name = params[:id]
|
148
149
|
type = params[:type]
|
149
|
-
unless name
|
150
|
-
puts "Usage: rubysync connector connector_name -t connector_type"
|
150
|
+
unless name
|
151
|
+
puts "Usage: rubysync connector connector_name [-t connector_type]"
|
151
152
|
return
|
152
153
|
end
|
153
|
-
if base_path
|
154
|
-
|
155
|
-
|
154
|
+
if base_path
|
155
|
+
filename = "#{base_path}/connectors/#{name}_connector.rb"
|
156
|
+
unless File.exists?(filename)
|
157
|
+
puts "Require -t connector_type when creating a connector" unless type
|
158
|
+
if template = connector_template(name, type)
|
159
|
+
File.open(filename, "w") do |file|
|
160
|
+
file.puts template
|
161
|
+
end
|
162
|
+
end
|
156
163
|
end
|
164
|
+
edit filename
|
157
165
|
else
|
158
166
|
puts 'Change into a config dir and try again or create a config dir with "rubysync create"'
|
159
167
|
end
|
@@ -167,6 +175,31 @@ class Controller < SimpleConsole::Controller
|
|
167
175
|
@field_names = connector && connector.fields || []
|
168
176
|
end
|
169
177
|
|
178
|
+
def show
|
179
|
+
params[:id] =~ /(.+?)\[(.+?)\]/o
|
180
|
+
connector_name = $1
|
181
|
+
path = $2
|
182
|
+
unless connector_name and path
|
183
|
+
puts "Usage: rubysync show connector[path]"
|
184
|
+
else
|
185
|
+
connector = connector_called(connector_name)
|
186
|
+
if connector
|
187
|
+
unless connector.respond_to?(:'[]')
|
188
|
+
puts "Connector '#{connector_name}' doesnt support random access."
|
189
|
+
else
|
190
|
+
connector.started
|
191
|
+
value = connector[path]
|
192
|
+
connector.stopped
|
193
|
+
unless value
|
194
|
+
puts "Path '#{path}' not found in connector '#{connector_name}'"
|
195
|
+
else
|
196
|
+
puts value.to_yaml
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
170
203
|
def pipeline
|
171
204
|
name = params[:id]
|
172
205
|
vault_name = params[:vault]
|
@@ -176,9 +209,15 @@ class Controller < SimpleConsole::Controller
|
|
176
209
|
return
|
177
210
|
end
|
178
211
|
if base_path
|
179
|
-
|
180
|
-
|
212
|
+
filename = "#{base_path}/pipelines/#{name}_pipeline.rb"
|
213
|
+
unless File.exists?(filename)
|
214
|
+
if template = pipeline_template(name, vault_name, client_name)
|
215
|
+
File.open(filename, "w") do |file|
|
216
|
+
file.puts template
|
217
|
+
end
|
218
|
+
end
|
181
219
|
end
|
220
|
+
edit filename
|
182
221
|
else
|
183
222
|
puts 'Change into a config dir and try again or create a config dir with "rubysync create"'
|
184
223
|
end
|
@@ -187,7 +226,17 @@ class Controller < SimpleConsole::Controller
|
|
187
226
|
|
188
227
|
|
189
228
|
private
|
190
|
-
|
229
|
+
|
230
|
+
def edit filename
|
231
|
+
unless params[:no_edit]
|
232
|
+
if ENV['EDITOR']
|
233
|
+
exec "#{ENV['EDITOR']} #{filename}"
|
234
|
+
else
|
235
|
+
log.warn "Set the EDITOR environment variable to enable automatic editing of config files."
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
191
240
|
def configure_logging
|
192
241
|
log_levels = [::Logger::WARN, ::Logger::INFO, ::Logger::DEBUG]
|
193
242
|
verbosity = [(params[:verbose]||0), log_levels.size-1].min
|
@@ -200,8 +249,8 @@ end
|
|
200
249
|
class View < SimpleConsole::View
|
201
250
|
|
202
251
|
|
203
|
-
def default
|
204
|
-
puts <<"END"
|
252
|
+
def default
|
253
|
+
puts <<"END"
|
205
254
|
Usage:
|
206
255
|
|
207
256
|
rubysync command name [options]
|
@@ -222,16 +271,20 @@ Usage:
|
|
222
271
|
; Execute the named pipeline within the current
|
223
272
|
; configuration directory once and then exit
|
224
273
|
|
274
|
+
* show {name}[{path}] ; Display the entry at path for connector specified
|
275
|
+
; name
|
276
|
+
|
225
277
|
* start {name} ; Execute the named pipeline
|
226
278
|
|
227
279
|
* example ; Show an example of how this command might be used
|
228
280
|
|
281
|
+
|
229
282
|
END
|
230
|
-
end
|
283
|
+
end
|
231
284
|
|
232
285
|
|
233
|
-
def example
|
234
|
-
puts <<"END"
|
286
|
+
def example
|
287
|
+
puts <<"END"
|
235
288
|
This sets up the skeleton of a configuration for importing comma delimeted
|
236
289
|
text files into an XML file.
|
237
290
|
|
@@ -259,101 +312,138 @@ puts <<"END"
|
|
259
312
|
|
260
313
|
$ rubysync start my
|
261
314
|
END
|
262
|
-
end
|
315
|
+
end
|
263
316
|
|
264
|
-
def fields
|
265
|
-
|
266
|
-
end
|
317
|
+
def fields
|
318
|
+
puts @field_names.join("\n")
|
319
|
+
end
|
267
320
|
|
268
321
|
end
|
269
322
|
|
270
323
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
sample_config
|
275
|
-
|
276
|
-
|
277
|
-
class #{name.to_s.camelize}Connector < #{type_class_name}
|
324
|
+
def connector_template name, type
|
325
|
+
return unless type_class = class_for_name("RubySync::Connectors::#{class_name_for type, 'connector'}")
|
326
|
+
sample_config = (type_class && type_class.respond_to?("sample_config")) ?
|
327
|
+
type_class.sample_config : ""
|
328
|
+
return <<-"end;"
|
329
|
+
class #{class_name_for(name, 'connector')} < #{type_class.name}
|
278
330
|
#{sample_config}
|
279
331
|
end
|
280
|
-
|
281
|
-
|
332
|
+
end;
|
333
|
+
end
|
282
334
|
|
283
335
|
|
284
|
-
|
285
|
-
|
286
|
-
|
336
|
+
def pipeline_template name, vault_name, client_name
|
337
|
+
vault = (vault_name)? class_for_name("RubySync::Connectors::#{class_name_for vault_name, 'connector'}") : nil
|
338
|
+
vault_fields = vault && vault.fields || []
|
287
339
|
|
288
|
-
|
289
|
-
|
340
|
+
client = (client_name)? class_for_name("RubySync::Connectors::#{class_name_for client_name, 'connector'}") : nil
|
341
|
+
client_fields = client && client.fields || []
|
290
342
|
|
291
|
-
|
292
|
-
|
293
|
-
|
343
|
+
return nil if vault_name && !vault or client_name && !client
|
344
|
+
|
345
|
+
vault_specifier = (vault_name)? "vault :#{vault_name}" : "#vault :vault_connector_name"
|
346
|
+
client_specifier = (client_name)? "client :#{client_name}" : "#client :client_connector_name"
|
347
|
+
return <<-"end;"
|
294
348
|
class #{name.to_s.camelize}Pipeline < RubySync::Pipelines::BasePipeline
|
295
349
|
|
296
350
|
#{client_specifier}
|
297
351
|
|
298
352
|
#{vault_specifier}
|
299
353
|
|
300
|
-
|
301
|
-
allow_out #{allow_through(vault_fields)}
|
302
|
-
|
303
|
-
# Remove any fields that you don't want to set in the vault from the client
|
354
|
+
# Remove any client fields that have no bearing on the final entry in the vault.
|
304
355
|
allow_in #{allow_through(client_fields)}
|
305
356
|
|
357
|
+
# "in" means going from client to vault
|
306
358
|
# If the client and vault have different names for the same field, define the
|
307
|
-
# the mapping here. For example, if the vault has a field called "first name"
|
308
|
-
# the client has a field called givenName you may
|
359
|
+
# the mapping here. For example, if the vault has a field called "first name"
|
360
|
+
# and the client has a field called 'givenName' you may write:
|
361
|
+
#
|
309
362
|
# map 'first name', 'givenName'
|
310
363
|
#
|
311
|
-
# You can also calculate the values for fields.
|
364
|
+
# You can also calculate the values for fields. If the vault has a 'fullname'
|
365
|
+
# and the client has 'givenName' and 'surname' attributes. You might write:
|
366
|
+
#
|
367
|
+
# map(:fullname) {value_of(:givenName) + " " + value_of(:surname) }
|
368
|
+
#
|
369
|
+
# For more info, see
|
312
370
|
# http://rubysync.org/docs/rubysync-transformations/
|
313
|
-
|
314
|
-
# "in" means going from client to vault
|
315
|
-
in_transform do
|
371
|
+
in_event_transform do
|
316
372
|
#{transform_fields(vault_fields, 'vault', 'client')}
|
317
373
|
end
|
318
374
|
|
319
|
-
#
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
375
|
+
# if the record has been successfully synchronized already, RubySync will
|
376
|
+
# already know about the association and will skip ahead to
|
377
|
+
# in_command_transform.
|
378
|
+
#
|
324
379
|
# if the vault doesn't already hold an association for this record
|
325
380
|
# from the client, perform a search here to see if a match can be found
|
381
|
+
# and if so, return its path.
|
382
|
+
# Default behaviour is to attempt to use the value returned by in_place.
|
326
383
|
# in_match do
|
327
384
|
# end
|
328
385
|
|
386
|
+
# If there was exactly one match, RubySync records the association and skips
|
387
|
+
# ahead to in_command_transform. Otherwise it considers creating the entry in
|
388
|
+
# the vault.
|
389
|
+
|
390
|
+
# If in_create evaluates to false, it will veto the creation of a record on
|
391
|
+
# the vault. This is good for checking you've got the required fields or only
|
392
|
+
# creating a subset of the possible records.
|
393
|
+
# in_create do
|
394
|
+
# #eg only create entries with a (somewhat) valid email address
|
395
|
+
# value_of(:email) =~ /%w+@%w+\.%w+/
|
396
|
+
# end
|
397
|
+
|
398
|
+
# Should evaluate to the path for placing a new record in the vault
|
399
|
+
# in_place do
|
400
|
+
# value_of(:vault_path_field)
|
401
|
+
# end
|
402
|
+
|
403
|
+
# in_command_transform is the same as in_event_transform but occurs after
|
404
|
+
# any in_match, in_create or in_place invocations.
|
405
|
+
# in_command_transform do
|
406
|
+
# drop_changes_to :any, :fields, :you, :dont, :want, :in, :the, :vault
|
407
|
+
# end
|
408
|
+
|
409
|
+
|
410
|
+
# End of client to vault processing
|
411
|
+
# -----------------------------------
|
412
|
+
# Start of vault to client processing
|
413
|
+
|
414
|
+
# Remove any client fields that have no bearing on the final entry in the client.
|
415
|
+
allow_out #{allow_through(vault_fields)}
|
416
|
+
|
417
|
+
|
418
|
+
# "out" means going from vault to client
|
419
|
+
# See comment for in_event_transform above
|
420
|
+
out_event_transform do
|
421
|
+
#{transform_fields(client_fields, 'client', 'vault')}
|
422
|
+
end
|
423
|
+
|
329
424
|
# if the vault doesn't have an association linking this record to one on
|
330
425
|
# the client, perform a search here to see if an existing client record
|
331
426
|
# matches this record
|
332
427
|
# out_match do
|
333
428
|
# end
|
334
429
|
|
335
|
-
|
336
|
-
# the vault. Good for checking you've got the required fields etc
|
337
|
-
# in_create_if do
|
338
|
-
# end
|
339
|
-
|
340
|
-
# if this evaluates to false for an outgoing event then it will
|
430
|
+
# if this evaluates to false for an outgoing event then it will
|
341
431
|
# veto the creation of a new record on the client
|
342
|
-
#
|
343
|
-
# end
|
344
|
-
|
345
|
-
# Should evaluate to the path for placing a new record on the vault
|
346
|
-
# in_place do
|
432
|
+
# out_create do
|
347
433
|
# end
|
348
434
|
|
349
|
-
|
435
|
+
# Should evaluate to the path for placing a new record on the client
|
350
436
|
# out_place do
|
351
437
|
# end
|
352
438
|
|
439
|
+
# See comment for in_command_transform above
|
440
|
+
out_command_transform do
|
441
|
+
# drop_changes_to :any, :fields, :you, :dont, :want, :in, :the, :client
|
442
|
+
end
|
353
443
|
|
354
444
|
end
|
355
|
-
|
356
|
-
|
445
|
+
end;
|
446
|
+
end
|
357
447
|
|
358
448
|
def allow_through fields
|
359
449
|
(fields.empty? ? %w{allow these fields through} : fields).to_ruby
|