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 CHANGED
Binary file
@@ -1,3 +1,14 @@
1
+ == 0.2.1 / 2008-06-11
2
+ * Deprecated in_transform and out_transform. Replaced in_trasform with in_event_transform (before other rules) and in_command_transform (after other rules). Likewise, out_transform is replaced by out_event_transform and out_command_transform. The command_transforms are useful for dropping attributes from the final record that were required for rule processing.
3
+ * Improved pipeline template.
4
+ * Various bug fixes.
5
+ == 0.2.0 / 2008-03-17
6
+ * Added show command to do simple record lookups from a connector
7
+ * Extracted out the dbm functionality from base_connector into dbm_change_tracking and dbm_association_tracking
8
+ * Added memory_association and memory_change_tracking modules for testing purposes
9
+ * More pipeline streamlining
10
+ * Tests now pass under jruby 1.1R2 as well as ruby 1.8.6
11
+
1
12
  == 0.1.1 / 2007-10-29
2
13
 
3
14
  * Some streamlining of the base_pipeline in_handler and out_handler methods
@@ -122,11 +122,14 @@ lib/ruby_sync/connectors/base_connector.rb
122
122
  lib/ruby_sync/connectors/connector_event_processing.rb
123
123
  lib/ruby_sync/connectors/csv_file_connector.rb
124
124
  lib/ruby_sync/connectors/file_connector.rb
125
- lib/ruby_sync/connectors/ldap_associations.rb
126
125
  lib/ruby_sync/connectors/ldap_changelog_connector.rb
127
126
  lib/ruby_sync/connectors/ldap_connector.rb
128
127
  lib/ruby_sync/connectors/memory_connector.rb
129
128
  lib/ruby_sync/connectors/xml_connector.rb
129
+ lib/ruby_sync/connectors/dbm_association_tracking.rb
130
+ lib/ruby_sync/connectors/dbm_change_tracking.rb
131
+ lib/ruby_sync/connectors/memory_association_tracking.rb
132
+ lib/ruby_sync/connectors/memory_change_tracking.rb
130
133
  lib/ruby_sync/event.rb
131
134
  lib/ruby_sync/operation.rb
132
135
  lib/ruby_sync/pipelines/base_pipeline.rb
data/Rakefile CHANGED
@@ -22,7 +22,7 @@ Hoe.new('rubysync', RubySync::VERSION) do |p|
22
22
  p.author = 'Ritchie Young'
23
23
  p.email = 'ritchiey@gmail.com'
24
24
  p.summary = "Event driven identity synchronization engine"
25
- p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
25
+ p.description = p.paragraphs_of('README.txt', 1..5).join("\n\n")
26
26
  p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
27
27
  p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
28
28
  p.remote_rdoc_dir = ""
@@ -30,8 +30,7 @@ Hoe.new('rubysync', RubySync::VERSION) do |p|
30
30
  ["ruby-net-ldap", ">=0.0.4"],
31
31
  ["activesupport", ">=1.4.0"],
32
32
  ["activerecord", ">=1.15.3"],
33
- ["simpleconsole", ">=0.1.1"]
33
+ ["simpleconsole", ">=0.1.1"],
34
+ ["contacts", ">=1.0.7"]
34
35
  ]
35
36
  end
36
-
37
- # vim: syntax=Ruby
@@ -1,77 +1,77 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
 
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
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 :string => {:p => :pipe,
91
- :t => :type,
92
- :V => :vault,
93
- :C => :client},
94
- :int =>{:v => :verbose,
95
- :d => :delay}
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
- config_path,
134
- "#{config_path}/pipelines",
135
- "#{config_path}/connectors",
136
- "#{config_path}/shared",
137
- "#{config_path}/shared/pipelines",
138
- "#{config_path}/shared/connectors",
139
- "#{config_path}/shared/lib",
140
- "#{config_path}/log",
141
- "#{config_path}/db"
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 and type
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
- File.open("#{base_path}/connectors/#{name}_connector.rb", "w") do |file|
155
- file.puts connector_template(name, type)
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
- File.open("#{base_path}/pipelines/#{name}_pipeline.rb", "w") do |file|
180
- file.puts pipeline_template(name, vault_name, client_name)
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
- puts @field_names.join("\n")
266
- end
317
+ def fields
318
+ puts @field_names.join("\n")
319
+ end
267
320
 
268
321
  end
269
322
 
270
323
 
271
- def connector_template name, type
272
- type_class_name = "RubySync::Connectors::#{type.to_s.camelize}Connector"
273
- type_class = eval(type_class_name)
274
- sample_config = (type_class && type_class.respond_to?("sample_config")) ?
275
- type_class.sample_config : ""
276
- return <<-"end;"
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
- end;
281
- end
332
+ end;
333
+ end
282
334
 
283
335
 
284
- def pipeline_template name, vault_name, client_name
285
- vault = (vault_name)? ::RubySync::Connectors::BaseConnector.class_for(vault_name) : nil
286
- vault_fields = vault && vault.fields || []
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
- client = (client_name)? ::RubySync::Connectors::BaseConnector.class_for(client_name) : nil
289
- client_fields = client && client.fields || []
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
- vault_specifier = (vault_name)? "vault :#{vault_name}" : "#vault :vault_connector_name"
292
- client_specifier = (client_name)? "client :#{client_name}" : "#client :client_connector_name"
293
- return <<-"end;"
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
- # Remove any fields that you don't want to set in the client from the vault
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" and
308
- # the client has a field called givenName you may put:
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. For more info, see
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
- # "out" means going from vault to client
320
- out_transform do
321
- #{transform_fields(client_fields, 'client', 'vault')}
322
- end
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
- # If this evaluates to false, it will veto the creation of a record on
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
- # out_create_if do
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
- # Should evaluate to the path for placing a new record on the client
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
- end;
356
- end
445
+ end;
446
+ end
357
447
 
358
448
  def allow_through fields
359
449
  (fields.empty? ? %w{allow these fields through} : fields).to_ruby