rubysync 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/rubysync +3 -3
- data/examples/ims2/connectors/hr_db_connector.rb +3 -5
- data/examples/my_ims/connectors/my_csv_connector.rb +10 -0
- data/examples/my_ims/connectors/my_db_connector.rb +7 -0
- data/examples/my_ims/pipelines/{finance_pipeline.rb → my_pipeline.rb} +9 -9
- data/lib/net/ldif.rb +302 -0
- data/lib/ruby_sync/connectors/active_record_connector.rb +33 -32
- data/lib/ruby_sync/connectors/base_connector.rb +21 -10
- data/lib/ruby_sync/connectors/csv_file_connector.rb +17 -22
- data/lib/ruby_sync/connectors/file_connector.rb +11 -11
- data/lib/ruby_sync/connectors/ldap_connector.rb +206 -53
- data/lib/ruby_sync/connectors/memory_connector.rb +5 -8
- data/lib/ruby_sync/event.rb +11 -3
- data/lib/ruby_sync/operation.rb +1 -1
- data/lib/ruby_sync/pipelines/base_pipeline.rb +6 -0
- data/lib/ruby_sync/util/utilities.rb +22 -3
- data/lib/ruby_sync.rb +25 -2
- data/test/data/example1.ldif +20 -0
- data/test/data/example2.ldif +14 -0
- data/test/data/example3.ldif +13 -0
- data/test/data/example4.ldif +55 -0
- data/test/data/example5.ldif +12 -0
- data/test/data/example6.ldif +62 -0
- data/test/data/example7.ldif +8 -0
- data/test/test_active_record_vault.rb +3 -4
- data/test/test_base_pipeline.rb +58 -0
- data/test/test_csv_file_connector.rb +7 -7
- data/test/test_ldap_connector.rb +71 -13
- data/test/test_ldap_vault.rb +91 -0
- data/test/test_ldif.rb +122 -0
- data/test/test_utilities.rb +63 -0
- metadata +19 -7
- data/examples/my_ims/connectors/corp_directory_connector.rb +0 -12
- data/examples/my_ims/connectors/finance_connector.rb +0 -7
- data/examples/my_ims/connectors/hr_db_connector.rb +0 -7
- data/examples/my_ims/pipelines/hr_import_pipeline.rb +0 -29
@@ -16,21 +16,17 @@ module RubySync
|
|
16
16
|
# This Connector can't act as an identity vault.
|
17
17
|
class CsvFileConnector < RubySync::Connectors::FileConnector
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
option :field_names, # A list of names representing the namesspace for this connector
|
20
|
+
:path_field # The name of the field to use as the source_path
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
@out_extension ||= '.csv'
|
27
|
-
@field_names ||= []
|
28
|
-
@path_field ||= (@field_names.empty?)? 'field_0': @field_names[0]
|
29
|
-
end
|
22
|
+
in_glob '*.csv'
|
23
|
+
out_extension '.csv'
|
24
|
+
field_names []
|
25
|
+
path_field (get_field_names.empty?)? 'field_0': @field_names[0]
|
30
26
|
|
31
27
|
# Called for each filename matching in_glob in in_path
|
32
28
|
# Yields a modify event for each row found in the file.
|
33
|
-
def
|
29
|
+
def each_file_change(filename)
|
34
30
|
CSV.open(filename, 'r') do |row|
|
35
31
|
if defined? field_name &&row.length > field_names.length
|
36
32
|
log.warn "#{name}: Row in file #{filename} exceeds defined field_names"
|
@@ -48,14 +44,13 @@ module RubySync
|
|
48
44
|
|
49
45
|
def self.sample_config
|
50
46
|
return <<END
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
)
|
47
|
+
|
48
|
+
field_names ['names', 'of', 'the', 'columns']
|
49
|
+
path_field 'name_of_field_to_use_as_the_id'
|
50
|
+
in_path '/directory/to/read/files/from'
|
51
|
+
out_path '/directory/to/write/files/to'
|
52
|
+
in_glob '*.csv'
|
53
|
+
out_extension '.csv'
|
59
54
|
END
|
60
55
|
end
|
61
56
|
|
@@ -68,7 +63,7 @@ END
|
|
68
63
|
|
69
64
|
def write_record file, path, operations
|
70
65
|
record = perform_operations operations
|
71
|
-
line = CSV.generate_line(
|
66
|
+
line = CSV.generate_line(field_names.map {|f| record[f]})
|
72
67
|
file.puts line
|
73
68
|
end
|
74
69
|
|
@@ -77,8 +72,8 @@ END
|
|
77
72
|
# Return the value to be used as the source_path for the event given the
|
78
73
|
# supplied row data.
|
79
74
|
def path_for(data)
|
80
|
-
if defined?
|
81
|
-
return data[
|
75
|
+
if defined? path_field
|
76
|
+
return data[path_field]
|
82
77
|
end
|
83
78
|
return nil
|
84
79
|
end
|
@@ -14,19 +14,19 @@ module RubySync
|
|
14
14
|
# and/or write received events to a file.
|
15
15
|
class FileConnector < RubySync::Connectors::BaseConnector
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
option :in_path, # scan this directory for suitable files
|
18
|
+
:out_path, # write received events to this directory
|
19
|
+
:out_extension, # the file extension of files written to out_path
|
20
|
+
:in_glob # The filename glob for incoming files
|
21
21
|
|
22
|
+
out_extension ".out"
|
22
23
|
|
23
24
|
def started
|
24
|
-
ensure_dir_exists
|
25
|
-
ensure_dir_exists
|
26
|
-
@out_extension ||= ".out"
|
25
|
+
ensure_dir_exists in_path
|
26
|
+
ensure_dir_exists out_path
|
27
27
|
end
|
28
28
|
|
29
|
-
def
|
29
|
+
def each_change(&blk)
|
30
30
|
unless in_glob
|
31
31
|
log.error "in_glob not set on file connector. No files will be processed"
|
32
32
|
return
|
@@ -35,14 +35,14 @@ module RubySync
|
|
35
35
|
Dir.chdir(in_path) do |path|
|
36
36
|
Dir.glob(in_glob) do |filename|
|
37
37
|
log.info "#{name}: Processing '#{filename}'"
|
38
|
-
|
38
|
+
each_file_change filename, &blk
|
39
39
|
FileUtils.mv filename, "#{filename}.bak"
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
44
|
# Called for each filename matching in_glob in in_path
|
45
|
-
def
|
45
|
+
def each_file_change(filename,&blk)
|
46
46
|
end
|
47
47
|
|
48
48
|
|
@@ -66,7 +66,7 @@ module RubySync
|
|
66
66
|
|
67
67
|
# Generate a unique and appropriate filename within the given path
|
68
68
|
def output_file_name
|
69
|
-
File.join(
|
69
|
+
File.join(out_path, Time.now.strftime('%Y%m%d%H%M%S') + out_extension)
|
70
70
|
end
|
71
71
|
|
72
72
|
end
|
@@ -18,11 +18,14 @@ lib_path = File.dirname(__FILE__) + '/..'
|
|
18
18
|
$:.unshift lib_path unless $:.include?(lib_path) || $:.include?(File.expand_path(lib_path))
|
19
19
|
|
20
20
|
require 'ruby_sync'
|
21
|
-
|
21
|
+
require 'net/ldif'
|
22
22
|
$VERBOSE = false
|
23
23
|
require 'net/ldap'
|
24
24
|
#$VERBOSE = true
|
25
25
|
|
26
|
+
RUBYSYNC_ASSOCIATION_ATTRIBUTE = "RubySyncAssociation"
|
27
|
+
RUBYSYNC_ASSOCIATION_CLASS = "RubySyncSynchable"
|
28
|
+
|
26
29
|
class Net::LDAP::Entry
|
27
30
|
def to_hash
|
28
31
|
return @myhash.dup
|
@@ -32,18 +35,94 @@ end
|
|
32
35
|
module RubySync::Connectors
|
33
36
|
class LdapConnector < RubySync::Connectors::BaseConnector
|
34
37
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
option :host,
|
39
|
+
:port,
|
40
|
+
:bind_method,
|
41
|
+
:username,
|
42
|
+
:password,
|
43
|
+
:search_filter,
|
44
|
+
:search_base,
|
45
|
+
:association_attribute, # name of the attribute in which to store the association key(s)
|
46
|
+
:changelog_dn
|
47
|
+
|
48
|
+
association_attribute 'RubySyncAssociation'
|
49
|
+
bind_method :simple
|
50
|
+
host 'localhost'
|
51
|
+
port 389
|
52
|
+
search_filter "cn=*"
|
53
|
+
changelog_dn "cn=changelog"
|
54
|
+
|
55
|
+
def initialize options={}
|
56
|
+
super options
|
57
|
+
@last_change_number = 1
|
58
|
+
# TODO: Persist the current CSN, for now we'll just skip to the end of the changelog
|
59
|
+
skip_existing_changelog_entries
|
60
|
+
end
|
61
|
+
|
62
|
+
|
39
63
|
def started
|
40
64
|
#TODO: If vault, check the schema to make sure that the association_attribute is there
|
41
|
-
@association_attribute ||= 'RubySyncAssociation'
|
42
65
|
end
|
43
66
|
|
44
|
-
|
45
|
-
|
46
|
-
|
67
|
+
|
68
|
+
# Look for changelog entries. This is not supported by all LDAP servers
|
69
|
+
# you may need to subclass for OpenLDAP and Active Directory
|
70
|
+
# Changelog entries have these attributes
|
71
|
+
# targetdn
|
72
|
+
# changenumber
|
73
|
+
# objectclass
|
74
|
+
# changes
|
75
|
+
# changetime
|
76
|
+
# changetype
|
77
|
+
# dn
|
78
|
+
#
|
79
|
+
# TODO: Detect presence/location of changelog from root DSE
|
80
|
+
def each_change
|
81
|
+
with_ldap do |ldap|
|
82
|
+
log.debug "@last_change_number = #{@last_change_number}"
|
83
|
+
filter = "(changenumber>=#{@last_change_number})"
|
84
|
+
first = true
|
85
|
+
@full_refresh_required = false
|
86
|
+
ldap.search :base => changelog_dn, :filter =>filter do |change|
|
87
|
+
change_number = change.changenumber[0].to_i
|
88
|
+
if first
|
89
|
+
first = false
|
90
|
+
# TODO: Persist the change_number so that we don't do a full resync everytime rubysync starts
|
91
|
+
if change_number != @last_change_number
|
92
|
+
log.warn "Earliest change number (#{change_number}) differs from that recorded (#{@last_change_number})."
|
93
|
+
log.warn "A full refresh is required."
|
94
|
+
@full_refresh_required = true
|
95
|
+
break
|
96
|
+
end
|
97
|
+
else
|
98
|
+
@last_change_number = change_number if change_number > @last_change_number
|
99
|
+
# todo: A proper DN object would be nice instead of string manipulation
|
100
|
+
target_dn = change.targetdn[0].gsub(/\s*,\s*/,',')
|
101
|
+
if target_dn =~ /#{search_base}$/oi
|
102
|
+
change_type = change.changetype[0]
|
103
|
+
event = event_for_changelog_entry(change)
|
104
|
+
yield event
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
def skip_existing_changelog_entries
|
113
|
+
with_ldap do |ldap|
|
114
|
+
filter = "(changenumber>=#{@last_change_number})"
|
115
|
+
@full_refresh_required = false
|
116
|
+
ldap.search :base => changelog_dn, :filter =>filter do |change|
|
117
|
+
change_number = change.changenumber[0].to_i
|
118
|
+
@last_change_number = change_number if change_number > @last_change_number
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def each_entry
|
124
|
+
Net::LDAP.open(:host=>host, :port=>port, :auth=>auth) do |ldap|
|
125
|
+
ldap.search :base => search_base, :filter => search_filter do |entry|
|
47
126
|
operations = operations_for_entry(entry)
|
48
127
|
association_key = (is_vault?)? nil : entry.dn
|
49
128
|
yield RubySync::Event.add(self, entry.dn, association_key, operations)
|
@@ -63,26 +142,18 @@ module RubySync::Connectors
|
|
63
142
|
def stopped
|
64
143
|
end
|
65
144
|
|
66
|
-
def initialize options
|
67
|
-
super options
|
68
|
-
@bind_method ||= :simple
|
69
|
-
@host ||= 'localhost'
|
70
|
-
@port ||= 389
|
71
|
-
@search_filter ||= "cn=*"
|
72
|
-
end
|
73
145
|
|
74
146
|
|
75
147
|
def self.sample_config
|
76
148
|
return <<END
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
)
|
149
|
+
|
150
|
+
host 'localhost'
|
151
|
+
port 10389
|
152
|
+
username 'uid=admin,ou=system'
|
153
|
+
password 'secret'
|
154
|
+
search_filter "cn=*"
|
155
|
+
search_base "dc=example,dc=com"
|
156
|
+
#:bind_method :simple
|
86
157
|
END
|
87
158
|
end
|
88
159
|
|
@@ -90,7 +161,7 @@ END
|
|
90
161
|
|
91
162
|
def add(path, operations)
|
92
163
|
with_ldap do |ldap|
|
93
|
-
|
164
|
+
ldap.add :dn=>path, :attributes=>perform_operations(operations)
|
94
165
|
end
|
95
166
|
return true
|
96
167
|
rescue Net::LdapException
|
@@ -111,50 +182,132 @@ END
|
|
111
182
|
with_ldap do |ldap|
|
112
183
|
result = ldap.search :base=>path, :scope=>Net::LDAP::SearchScope_BaseObject, :filter=>'objectclass=*'
|
113
184
|
return nil if !result or result.size == 0
|
114
|
-
|
185
|
+
answer = {}
|
186
|
+
result[0].attribute_names.each do |name|
|
187
|
+
answer[name.to_s] = result[0][name]
|
188
|
+
end
|
189
|
+
answer
|
115
190
|
end
|
116
191
|
end
|
117
192
|
|
193
|
+
# Called by unit tests to inject data
|
194
|
+
def test_add id, details
|
195
|
+
details << RubySync::Operation.new(:add, "objectclass", ['inetOrgPerson', 'organizationalPerson', 'person', 'top', 'RubySyncSynchable'])
|
196
|
+
add id, details
|
197
|
+
end
|
198
|
+
|
118
199
|
def target_transform event
|
119
|
-
event.add_default 'objectclass', 'inetOrgUser'
|
120
|
-
#
|
200
|
+
#event.add_default 'objectclass', 'inetOrgUser'
|
201
|
+
#is_vault? and event.add_value 'objectclass', RUBYSYNC_ASSOCIATION_CLASS
|
121
202
|
end
|
122
203
|
|
123
|
-
def
|
204
|
+
def associate association, path
|
124
205
|
with_ldap do |ldap|
|
125
|
-
|
206
|
+
# todo: check and warn if path is outside of search_base
|
207
|
+
ldap.modify :dn=>path, :operations=>[
|
208
|
+
[:add, RUBYSYNC_ASSOCIATION_ATTRIBUTE, association.to_s]
|
209
|
+
]
|
126
210
|
end
|
127
211
|
end
|
128
212
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
213
|
+
def path_for_association association
|
214
|
+
with_ldap do |ldap|
|
215
|
+
filter = "#{RUBYSYNC_ASSOCIATION_ATTRIBUTE}=#{association.to_s}"
|
216
|
+
log.debug "Searching with filter: #{filter}"
|
217
|
+
results = ldap.search :base=>@search_base,
|
218
|
+
:filter=>filter,
|
219
|
+
:attributes=>[]
|
220
|
+
results or return nil
|
221
|
+
case results.length
|
222
|
+
when 0: return nil
|
223
|
+
when 1: return results[0].dn
|
224
|
+
else
|
225
|
+
raise Exception.new("Duplicate association found for #{association.to_s}")
|
226
|
+
end
|
227
|
+
end
|
132
228
|
end
|
133
229
|
|
134
|
-
def
|
135
|
-
entry = self[path]
|
136
|
-
(entry)? entry.dn : nil # TODO: That doesn't look right. Should return an association key, not a path.
|
137
|
-
end
|
138
|
-
|
139
|
-
def remove_foreign_key key
|
230
|
+
def associations_for path
|
140
231
|
with_ldap do |ldap|
|
141
|
-
|
142
|
-
|
143
|
-
|
232
|
+
results = ldap.search :base=>path,
|
233
|
+
:scope=>Net::LDAP::SearchScope_BaseObject,
|
234
|
+
:attributes=>[RUBYSYNC_ASSOCIATION_ATTRIBUTE]
|
235
|
+
unless results and results.length > 0
|
236
|
+
log.warn "Attempted association lookup on non-existent LDAP entry '#{path}'"
|
237
|
+
return []
|
144
238
|
end
|
239
|
+
associations = results[0][RUBYSYNC_ASSOCIATION_ATTRIBUTE]
|
240
|
+
return (associations)? associations.as_array : []
|
145
241
|
end
|
146
242
|
end
|
147
|
-
|
148
|
-
def find_associated foreign_key
|
149
|
-
entry = entry_for_foreign_key key
|
150
|
-
(entry)? operations_for_entry(entry) : nil
|
151
|
-
end
|
152
243
|
|
244
|
+
def remove_association association
|
245
|
+
path = path_for_association association
|
246
|
+
with_ldap do |ldap|
|
247
|
+
ldap.modify :dn=>path, :modifications=>[
|
248
|
+
[:delete, RUBYSYNC_ASSOCIATION_ATTRIBUTE, association.to_s]
|
249
|
+
]
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
# def associate_with_foreign_key key, path
|
255
|
+
# with_ldap do |ldap|
|
256
|
+
# ldap.add_attribute(path, association_attribute, key.to_s)
|
257
|
+
# end
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
# def path_for_foreign_key key
|
261
|
+
# entry = entry_for_foreign_key key
|
262
|
+
# (entry)? entry.dn : nil
|
263
|
+
# end
|
264
|
+
#
|
265
|
+
# def foreign_key_for path
|
266
|
+
# entry = self[path]
|
267
|
+
# (entry)? entry.dn : nil # TODO: That doesn't look right. Should return an association key, not a path.
|
268
|
+
# end
|
269
|
+
#
|
270
|
+
# def remove_foreign_key key
|
271
|
+
# with_ldap do |ldap|
|
272
|
+
# entry = entry_for_foreign_key key
|
273
|
+
# if entry
|
274
|
+
# modify :dn=>entry.dn, :operations=>[ [:delete, association_attribute, key] ]
|
275
|
+
# end
|
276
|
+
# end
|
277
|
+
# end
|
278
|
+
#
|
279
|
+
# def find_associated foreign_key
|
280
|
+
# entry = entry_for_foreign_key key
|
281
|
+
# (entry)? operations_for_entry(entry) : nil
|
282
|
+
# end
|
283
|
+
|
153
284
|
|
154
285
|
private
|
155
286
|
|
287
|
+
def event_for_changelog_entry cle
|
288
|
+
payload = nil
|
289
|
+
dn = cle.targetdn[0]
|
290
|
+
changetype = cle.changetype[0]
|
291
|
+
if cle.attribute_names.include? :changes
|
292
|
+
payload = []
|
293
|
+
cr = Net::LDIF.parse("dn: #{dn}\nchangetype: #{changetype}\n#{cle.changes[0]}")[0]
|
294
|
+
if changetype.to_sym == :add
|
295
|
+
# cr.data will be a hash of arrays or strings (attr-name=>[value1, value2, ...])
|
296
|
+
cr.data.each do |name, values|
|
297
|
+
payload << RubySync::Operation.add(name, values)
|
298
|
+
end
|
299
|
+
else
|
300
|
+
# cr.data will be an array of arrays of form [:action, :subject, [values]]
|
301
|
+
cr.data.each do |record|
|
302
|
+
payload << RubySync::Operation.new(record[0], record[1], record[2])
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
RubySync::Event.new(changetype, self, dn, nil, payload)
|
307
|
+
end
|
308
|
+
|
309
|
+
|
156
310
|
def operations_for_entry entry
|
157
|
-
# TODO: This could probably be done better by mixing Enumerable into Entry and then calling collect
|
158
311
|
ops = []
|
159
312
|
entry.each do |name, values|
|
160
313
|
ops << RubySync::Operation.add(name, values)
|
@@ -164,7 +317,7 @@ private
|
|
164
317
|
|
165
318
|
def entry_for_foreign_key key
|
166
319
|
with_ldap do |ldap|
|
167
|
-
result = ldap.search :base
|
320
|
+
result = ldap.search :base=>search_base, :filter=>"#{association_attribute}=#{key}"
|
168
321
|
return nil if !result or result.size == 0
|
169
322
|
result[0]
|
170
323
|
end
|
@@ -173,14 +326,14 @@ private
|
|
173
326
|
|
174
327
|
def with_ldap
|
175
328
|
result = nil
|
176
|
-
Net::LDAP.open(:host
|
329
|
+
Net::LDAP.open(:host=>host, :port=>port, :auth=>auth) do |ldap|
|
177
330
|
result = yield ldap
|
178
331
|
end
|
179
332
|
result
|
180
333
|
end
|
181
334
|
|
182
335
|
def auth
|
183
|
-
{:method
|
336
|
+
{:method=>bind_method, :username=>username, :password=>password}
|
184
337
|
end
|
185
338
|
|
186
339
|
# Produce an array of operation arrays suitable for the LDAP library
|
@@ -189,4 +342,4 @@ private
|
|
189
342
|
end
|
190
343
|
|
191
344
|
end
|
192
|
-
end
|
345
|
+
end
|
@@ -16,23 +16,20 @@
|
|
16
16
|
|
17
17
|
require "yaml"
|
18
18
|
|
19
|
-
class Object
|
20
|
-
|
21
|
-
# If not already an array, slip into one
|
22
|
-
def as_array
|
23
|
-
(instance_of? Array)? self : [self]
|
24
|
-
end
|
25
|
-
end
|
26
19
|
|
27
20
|
module RubySync::Connectors
|
28
21
|
class MemoryConnector < RubySync::Connectors::BaseConnector
|
29
22
|
|
30
|
-
def
|
23
|
+
def each_change
|
31
24
|
while event = @events.shift
|
32
25
|
yield event
|
33
26
|
end
|
34
27
|
end
|
35
28
|
|
29
|
+
def each_entry
|
30
|
+
# todo implement
|
31
|
+
end
|
32
|
+
|
36
33
|
def is_echo? event
|
37
34
|
event.sets_value?(:modifier, 'rubysync')
|
38
35
|
end
|
data/lib/ruby_sync/event.rb
CHANGED
@@ -17,23 +17,31 @@
|
|
17
17
|
|
18
18
|
module RubySync
|
19
19
|
|
20
|
-
|
20
|
+
class Association
|
21
21
|
attr_accessor :context, # many associations will share the same context
|
22
22
|
# it is a function of pipeline and the client connector
|
23
23
|
# to which the association applies
|
24
24
|
:key # the key is unique within the context and vault
|
25
25
|
|
26
|
+
|
27
|
+
def self.delimiter; '$'; end
|
28
|
+
|
26
29
|
def initialize(context, key)
|
27
30
|
@context = context
|
28
31
|
@key = key
|
29
32
|
end
|
30
33
|
|
31
34
|
def to_s
|
32
|
-
"#{context}
|
35
|
+
"#{context}#{self.class.delimiter}#{key}"
|
33
36
|
end
|
34
37
|
|
35
38
|
end
|
36
39
|
|
40
|
+
|
41
|
+
# Represents a change of some type to a record in the source datastore.
|
42
|
+
# If the event type is :add or :modify then the payload will be an
|
43
|
+
# array of RubySync::Operations describing changes to the attributes of the
|
44
|
+
# record.
|
37
45
|
class Event
|
38
46
|
|
39
47
|
attr_accessor :type, # delete, add, modify ...
|
@@ -199,7 +207,7 @@ module RubySync
|
|
199
207
|
|
200
208
|
|
201
209
|
|
202
|
-
# Yield to block for each operation in the payload for which the
|
210
|
+
# Yield to block for each operation in the payload for which the subject is
|
203
211
|
# the specified subject
|
204
212
|
def each_operation_on subject
|
205
213
|
return unless payload
|
data/lib/ruby_sync/operation.rb
CHANGED
@@ -43,7 +43,7 @@ module RubySync
|
|
43
43
|
remove_method :type=
|
44
44
|
def type=(type)
|
45
45
|
unless [:add, :delete, :replace].include? type.to_sym
|
46
|
-
raise Exception.new("Invalid operation type '#{
|
46
|
+
raise Exception.new("Invalid operation type '#{type}'")
|
47
47
|
end
|
48
48
|
@type = type
|
49
49
|
end
|
@@ -104,6 +104,12 @@ module RubySync
|
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
+
def self.in_transform &blk
|
108
|
+
define_method :in_transform do |event|
|
109
|
+
event.meta_def :transform, &blk
|
110
|
+
event.transform
|
111
|
+
end
|
112
|
+
end
|
107
113
|
|
108
114
|
|
109
115
|
# Called by the identity-vault connector in the 'out' thread to process events generated
|
@@ -108,8 +108,27 @@ module RubySync
|
|
108
108
|
end
|
109
109
|
return false
|
110
110
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
111
|
+
|
112
|
+
# Make and instance method _name_ that returns the value set by the
|
113
|
+
# class method _name_.
|
114
|
+
# def self.class_option name
|
115
|
+
# self.class_eval "def #{name}() self.class.instance_variable_get :#{name}; end"
|
116
|
+
# self.instance_eval "def #{name}(value) @#{name}=value; end"
|
117
|
+
# end
|
118
|
+
|
119
|
+
def get_preference(name, file_name=nil)
|
120
|
+
class_name ||= get_preference_file
|
121
|
+
end
|
122
|
+
|
123
|
+
def set_preference(name)
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
def get_preference_file_path name
|
128
|
+
dir = "#{ENV[HOME]}/.rubysync"
|
129
|
+
Dir.mkdir(dir)
|
130
|
+
"#{dir}#{file}"
|
131
|
+
end
|
132
|
+
|
114
133
|
end
|
115
134
|
end
|
data/lib/ruby_sync.rb
CHANGED
@@ -19,14 +19,20 @@ $:.unshift lib_path unless $:.include?(lib_path) || $:.include?(File.expand_path
|
|
19
19
|
require 'rubygems'
|
20
20
|
require 'active_support'
|
21
21
|
require 'ruby_sync/util/utilities'
|
22
|
+
require 'ruby_sync/util/metaid'
|
22
23
|
#require 'ruby_sync/connectors/base_connector'
|
23
24
|
#require 'ruby_sync/pipelines/base_pipeline'
|
24
25
|
require 'ruby_sync/operation'
|
25
26
|
require 'ruby_sync/event'
|
26
27
|
|
27
|
-
# Make the log method globally available
|
28
28
|
class Object
|
29
29
|
|
30
|
+
# If not already an array, slip into one
|
31
|
+
def as_array
|
32
|
+
(instance_of? Array)? self : [self]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Make the log method globally available
|
30
36
|
def log
|
31
37
|
unless defined? @@log
|
32
38
|
@@log = Logger.new(STDOUT)
|
@@ -35,7 +41,24 @@ class Object
|
|
35
41
|
end
|
36
42
|
@@log
|
37
43
|
end
|
38
|
-
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Module
|
47
|
+
# Add an option that will be defined by a class method, stored in a class variable
|
48
|
+
# and accessible as an instance method
|
49
|
+
def option *names
|
50
|
+
names.each do |name|
|
51
|
+
meta_def name do |value|
|
52
|
+
class_def name do
|
53
|
+
value
|
54
|
+
end
|
55
|
+
meta_def "get_#{name}" do
|
56
|
+
value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
39
62
|
|
40
63
|
class Configuration
|
41
64
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
version: 1
|
2
|
+
dn: cn=Barbara Jensen, ou=Product Development, dc=airius, dc=com
|
3
|
+
objectclass: top
|
4
|
+
objectclass: person
|
5
|
+
objectclass: organizationalPerson
|
6
|
+
cn: Barbara Jensen
|
7
|
+
cn: Barbara J Jensen
|
8
|
+
cn: Babs Jensen
|
9
|
+
sn: Jensen
|
10
|
+
uid: bjensen
|
11
|
+
telephonenumber: +1 408 555 1212
|
12
|
+
description: A big sailing fan.
|
13
|
+
|
14
|
+
dn: cn=Bjorn Jensen, ou=Accounting, dc=airius, dc=com
|
15
|
+
objectclass: top
|
16
|
+
objectclass: person
|
17
|
+
objectclass: organizationalPerson
|
18
|
+
cn: Bjorn Jensen
|
19
|
+
sn: Jensen
|
20
|
+
telephonenumber: +1 408 555 1212
|
@@ -0,0 +1,14 @@
|
|
1
|
+
version: 1
|
2
|
+
dn:cn=Barbara Jensen, ou=Product Development, dc=airius, dc=com
|
3
|
+
objectclass:top
|
4
|
+
objectclass:person
|
5
|
+
objectclass:organizationalPerson
|
6
|
+
cn:Barbara Jensen
|
7
|
+
cn:Barbara J Jensen
|
8
|
+
cn:Babs Jensen
|
9
|
+
sn:Jensen
|
10
|
+
uid:bjensen
|
11
|
+
telephonenumber:+1 408 555 1212
|
12
|
+
description:Babs is a big sailing fan, and travels extensively in sea
|
13
|
+
rch of perfect sailing conditions.
|
14
|
+
title:Product Manager, Rod and Reel Division
|