treequel 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/ChangeLog +354 -0
  2. data/LICENSE +27 -0
  3. data/README +66 -0
  4. data/Rakefile +345 -0
  5. data/Rakefile.local +43 -0
  6. data/bin/treeirb +14 -0
  7. data/bin/treequel +229 -0
  8. data/examples/company-directory.rb +112 -0
  9. data/examples/ldap-monitor.rb +143 -0
  10. data/examples/ldap-monitor/public/css/master.css +328 -0
  11. data/examples/ldap-monitor/public/images/card_small.png +0 -0
  12. data/examples/ldap-monitor/public/images/chain_small.png +0 -0
  13. data/examples/ldap-monitor/public/images/globe_small.png +0 -0
  14. data/examples/ldap-monitor/public/images/globe_small_green.png +0 -0
  15. data/examples/ldap-monitor/public/images/plug.png +0 -0
  16. data/examples/ldap-monitor/public/images/shadows/large-30-down.png +0 -0
  17. data/examples/ldap-monitor/public/images/tick.png +0 -0
  18. data/examples/ldap-monitor/public/images/tick_circle.png +0 -0
  19. data/examples/ldap-monitor/public/images/treequel-favicon.png +0 -0
  20. data/examples/ldap-monitor/views/backends.erb +41 -0
  21. data/examples/ldap-monitor/views/connections.erb +74 -0
  22. data/examples/ldap-monitor/views/databases.erb +39 -0
  23. data/examples/ldap-monitor/views/dump_subsystem.erb +14 -0
  24. data/examples/ldap-monitor/views/index.erb +14 -0
  25. data/examples/ldap-monitor/views/layout.erb +35 -0
  26. data/examples/ldap-monitor/views/listeners.erb +30 -0
  27. data/examples/ldap_state.rb +62 -0
  28. data/lib/treequel.rb +145 -0
  29. data/lib/treequel/branch.rb +589 -0
  30. data/lib/treequel/branchcollection.rb +204 -0
  31. data/lib/treequel/branchset.rb +360 -0
  32. data/lib/treequel/constants.rb +604 -0
  33. data/lib/treequel/directory.rb +541 -0
  34. data/lib/treequel/exceptions.rb +32 -0
  35. data/lib/treequel/filter.rb +704 -0
  36. data/lib/treequel/mixins.rb +325 -0
  37. data/lib/treequel/schema.rb +245 -0
  38. data/lib/treequel/schema/attributetype.rb +252 -0
  39. data/lib/treequel/schema/ldapsyntax.rb +96 -0
  40. data/lib/treequel/schema/matchingrule.rb +124 -0
  41. data/lib/treequel/schema/matchingruleuse.rb +124 -0
  42. data/lib/treequel/schema/objectclass.rb +289 -0
  43. data/lib/treequel/sequel_integration.rb +26 -0
  44. data/lib/treequel/utils.rb +169 -0
  45. data/rake/191_compat.rb +26 -0
  46. data/rake/dependencies.rb +76 -0
  47. data/rake/helpers.rb +434 -0
  48. data/rake/hg.rb +261 -0
  49. data/rake/manual.rb +782 -0
  50. data/rake/packaging.rb +135 -0
  51. data/rake/publishing.rb +318 -0
  52. data/rake/rdoc.rb +30 -0
  53. data/rake/style.rb +62 -0
  54. data/rake/svn.rb +668 -0
  55. data/rake/testing.rb +187 -0
  56. data/rake/verifytask.rb +64 -0
  57. data/rake/win32.rb +190 -0
  58. data/spec/lib/constants.rb +93 -0
  59. data/spec/lib/helpers.rb +100 -0
  60. data/spec/treequel/branch_spec.rb +569 -0
  61. data/spec/treequel/branchcollection_spec.rb +213 -0
  62. data/spec/treequel/branchset_spec.rb +376 -0
  63. data/spec/treequel/directory_spec.rb +487 -0
  64. data/spec/treequel/filter_spec.rb +482 -0
  65. data/spec/treequel/mixins_spec.rb +330 -0
  66. data/spec/treequel/schema/attributetype_spec.rb +237 -0
  67. data/spec/treequel/schema/ldapsyntax_spec.rb +83 -0
  68. data/spec/treequel/schema/matchingrule_spec.rb +158 -0
  69. data/spec/treequel/schema/matchingruleuse_spec.rb +137 -0
  70. data/spec/treequel/schema/objectclass_spec.rb +262 -0
  71. data/spec/treequel/schema_spec.rb +118 -0
  72. data/spec/treequel/utils_spec.rb +49 -0
  73. data/spec/treequel_spec.rb +179 -0
  74. metadata +169 -0
@@ -0,0 +1,43 @@
1
+ #!rake
2
+ # coding: utf-8
3
+
4
+ require 'pathname'
5
+ require 'English'
6
+ require 'pp'
7
+
8
+ # Project-specific tasks
9
+
10
+ #####################################################################
11
+ ### T A S K S
12
+ #####################################################################
13
+
14
+ MANUAL_PAGES = Pathname.glob( MANUALDIR + '**/*.page' )
15
+
16
+ task :local => :check_manual
17
+
18
+ # Check the manual for laika references before committing
19
+ Rake::Task[ 'checkin' ].prerequisites.unshift( 'check_manual' )
20
+
21
+ desc "Check the manual for various problems and/or inconsistencies"
22
+ task :check_manual => MANUAL_PAGES do
23
+ MANUAL_PAGES.each do |page|
24
+ lines = page.readlines
25
+ lines.each_with_index do |line, i|
26
+ if line =~ /\blaika\b/i
27
+ msg = [
28
+ ' ',
29
+ i.zero? ? "" : lines[ i - 1 ],
30
+ ' ',
31
+ $PREMATCH,
32
+ colorize( :bold, :yellow ) { $MATCH },
33
+ $POSTMATCH,
34
+ ' ',
35
+ lines[ i + 1 ]
36
+ ].join
37
+ abort "Page #{page} has a LAIKA reference on line #{i + 1}:\n#{msg}"
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'irb'
4
+ require 'irb/extend-command'
5
+ require 'irb/cmd/nop'
6
+ require 'treequel'
7
+
8
+
9
+ uri = ARGV.shift or raise "usage: #$0 [LDAPURI]"
10
+ $dir = Treequel.directory( uri )
11
+ $stderr.puts "Directory is in $dir:", ' ' + $dir.inspect
12
+
13
+ IRB.start( $0 )
14
+
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'readline'
5
+ require 'logger'
6
+ require 'shellwords'
7
+ require 'tempfile'
8
+ require 'digest/sha1'
9
+ require 'abbrev'
10
+ require 'treequel'
11
+ require 'treequel/mixins'
12
+ require 'treequel/constants'
13
+
14
+
15
+ class Shell
16
+ include Treequel::Loggable,
17
+ Treequel::Constants::Patterns
18
+
19
+
20
+ ### Create a new shell that will traverse the directory at the specified +uri+.
21
+ def initialize( uri )
22
+ Treequel.logger.level = Logger::WARN
23
+
24
+ @uri = uri
25
+ @quit = false
26
+ @dir = Treequel.directory( @uri )
27
+ @currbranch = @dir
28
+
29
+ @commands = self.find_commands
30
+ @completions = @commands.abbrev
31
+ @command_table = make_command_table( @commands )
32
+ end
33
+
34
+
35
+ ### The command loop: run the shell until the user wants to quit
36
+ def run
37
+ $stderr.puts "Connected to %s" % [ @uri ]
38
+
39
+ self.setup_completion
40
+
41
+ until @quit
42
+ input = Readline.readline( @currbranch.dn + '> ', true )
43
+ self.log.debug "Input is: %p" % [ input ]
44
+
45
+ # EOL makes the shell quit
46
+ if input.nil?
47
+ @quit = true
48
+
49
+ elsif input == ''
50
+ self.log.debug "No command. Re-displaying the prompt."
51
+
52
+ # Parse everything else into command + everything else
53
+ else
54
+ command, *args = Shellwords.shellwords( input )
55
+
56
+ begin
57
+ if meth = @command_table[ command ]
58
+ meth.call( *args )
59
+ else
60
+ self.handle_missing_command( command )
61
+ end
62
+ rescue => err
63
+ $stderr.puts "Error: %s" % [ err.message ]
64
+ err.backtrace.each do |frame|
65
+ self.log.debug " " + frame
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ $stderr.puts "done."
72
+ end
73
+
74
+
75
+ #########
76
+ protected
77
+ #########
78
+
79
+ ### Set up Readline completion
80
+ def setup_completion
81
+ Readline.completion_proc = self.method( :completion_callback ).to_proc
82
+ Readline.completer_word_break_characters = ''
83
+ end
84
+
85
+
86
+ ### Handle completion requests from Readline.
87
+ def completion_callback( input )
88
+ if command = @completions[ input ]
89
+ return []
90
+ end
91
+ end
92
+
93
+
94
+ ### Quit the shell.
95
+ def quit_command( *args )
96
+ $stderr.puts "Okay, exiting."
97
+ @quit = true
98
+ end
99
+
100
+
101
+ LOG_LEVELS = {
102
+ 'debug' => Logger::DEBUG,
103
+ 'info' => Logger::INFO,
104
+ 'warn' => Logger::WARN,
105
+ 'error' => Logger::ERROR,
106
+ 'fatal' => Logger::FATAL,
107
+ }.freeze
108
+ LOG_LEVEL_NAMES = LOG_LEVELS.invert.freeze
109
+
110
+ ### Set the logging level (if invoked with an argument) or display the current
111
+ ### level (with no argument).
112
+ def log_command( *args )
113
+ newlevel = args.shift
114
+ if newlevel
115
+ if LOG_LEVELS.key?( newlevel )
116
+ Treequel.logger.level = LOG_LEVELS[ newlevel ]
117
+ $stderr.puts "Set log level to: %s" % [ newlevel ]
118
+ else
119
+ levelnames = LOG_LEVEL_NAMES.keys.sort.join(', ')
120
+ raise "Invalid log level %p: valid values are:\n %s" % [ newlevel, levelnames ]
121
+ end
122
+ else
123
+ $stderr.puts "Log level is currently: %s" %
124
+ [ LOG_LEVEL_NAMES[Treequel.logger.level] ]
125
+ end
126
+ end
127
+
128
+
129
+ ### Show the completions hash
130
+ def show_completions_command
131
+ $stderr.puts "Completions:",
132
+ @completions.inspect
133
+ end
134
+
135
+
136
+ ### Display LDIF for the specified RDNs.
137
+ def cat_command( *args )
138
+ args.each do |rdn|
139
+ branch = rdn.split( /\s*,\s*/ ).inject( @currbranch ) do |branch, dnpair|
140
+ attribute, value = dnpair.split( /\s*=\s*/, 2 )
141
+ branch.send( attribute, value )
142
+ end
143
+
144
+ $stdout.puts( branch.to_ldif )
145
+ end
146
+ end
147
+
148
+
149
+ ### List the children of the current branch.
150
+ def ls_command( *args )
151
+ $stdout.puts *@currbranch.children.collect {|b| b.rdn }.sort
152
+ end
153
+
154
+
155
+ ### Change the current working DN to +rdn+.
156
+ def cdn_command( rdn, *args )
157
+ raise "invalid RDN %p" % [ rdn ] unless RELATIVE_DISTINGUISHED_NAME.match( rdn )
158
+
159
+ pairs = rdn.split( /\s*,\s*/ )
160
+ pairs.each do |dnpair|
161
+ self.log.debug " cd to %p" % [ dnpair ]
162
+ attribute, value = dnpair.split( /=/, 2 )
163
+ self.log.debug " changing to %s( %p )" % [ attribute, value ]
164
+ @currbranch = @currbranch.send( attribute, value )
165
+ end
166
+ end
167
+
168
+
169
+ ### Change the current working DN to the current entry's parent.
170
+ def parent_command( *args )
171
+ parent = @currbranch.parent or raise "%s is the root DN" % [ @currbranch.dn ]
172
+
173
+ self.log.debug " changing to %s" % [ parent.dn ]
174
+ @currbranch = parent
175
+ end
176
+
177
+
178
+ ### Edit the entry specified by +rdn+.
179
+ def edit_command( rdn, *args )
180
+ branch = @currbranch.get_child( rdn )
181
+
182
+ fn = Digest::SHA1.hexdigest( rdn )
183
+ tf = Tempfile.new( fn )
184
+ if branch.exists?
185
+ tf.print( )
186
+ end
187
+
188
+
189
+ ### Handle a command from the user that doesn't exist.
190
+ def handle_missing_command( *args )
191
+ command = args.shift || '(testing?)'
192
+ $stderr.puts "Unknown command %p" % [ command ]
193
+ $stderr.puts "Known commands: ", ' ' + @commands.join(', ')
194
+ end
195
+
196
+
197
+ ### Find methods that implement commands and return them in a sorted Array.
198
+ def find_commands
199
+ return self.methods.
200
+ grep( /^(\w+)_command$/ ).
201
+ collect {|mname| mname[/^(\w+)_command$/, 1] }.
202
+ sort
203
+ end
204
+
205
+
206
+ #######
207
+ private
208
+ #######
209
+
210
+ ### Create a command table that maps command abbreviations to the Method object that
211
+ ### implements it.
212
+ def make_command_table( commands )
213
+ table = commands.abbrev
214
+ table.keys.each do |abbrev|
215
+ mname = table.delete( abbrev )
216
+ table[ abbrev ] = self.method( mname + '_command' )
217
+ end
218
+
219
+ return table
220
+ end
221
+
222
+ end
223
+
224
+
225
+ if __FILE__ == $0
226
+ ldapuri = URI( ARGV.shift || 'ldap://localhost' )
227
+ Shell.new( ldapuri ).run
228
+ end
229
+
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'sinatra'
5
+ require 'treequel'
6
+ require 'pathname'
7
+
8
+ # A barebones web-based company directory
9
+
10
+ LDAP_URL = "ldap://ldap.yourcompany.com/dc=yourcompany,dc=com"
11
+
12
+ configure do
13
+ # Borrow the CSS and images from the 'ldap-monitor' example
14
+ set :root, Pathname( __FILE__ ).dirname + 'ldap-monitor'
15
+ end
16
+
17
+ before do
18
+ $stderr.puts "Connecting to #{LDAP_URL}"
19
+ @ldap ||= Treequel.directory( LDAP_URL )
20
+ end
21
+
22
+
23
+ ### GET /
24
+ get '/' do
25
+
26
+ # Get every entry under ou=people that has an email address and sort them
27
+ # by last name, first name, and UID.
28
+ people = @ldap.ou( :people ).filter( :mail ).sort_by do |person|
29
+ [ person[:sn], person[:givenName], person[:uid] ]
30
+ end
31
+
32
+ erb :index,
33
+ :locals => {
34
+ :people => people
35
+ }
36
+ end
37
+
38
+
39
+ ### GET /uid
40
+ get '/:uid' do
41
+
42
+ # Look up the person associated with the given UID, returning NOT FOUND if
43
+ # there isn't any such entry
44
+ uid = params[:uid]
45
+ person = @ldap.ou( :people ).uid( uid )
46
+ halt 404, "No such person" unless person.exists?
47
+
48
+ erb :details,
49
+ :locals => {
50
+ :person => person
51
+ }
52
+ end
53
+
54
+
55
+
56
+ __END__
57
+
58
+ @@layout
59
+ <?xml version="1.0" encoding="UTF-8"?>
60
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
61
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
62
+
63
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
64
+ <head>
65
+ <title>Company Directory</title>
66
+ <link rel="stylesheet" href="/css/master.css" type="text/css" media="screen" title="no title" charset="utf-8" />
67
+ </head>
68
+
69
+ <body>
70
+
71
+ <div id="content">
72
+ <h1>Company Directory</h1>
73
+
74
+ <%= yield %>
75
+
76
+ </div>
77
+
78
+ <div id="footer">Treequel Company Directory Example</div>
79
+ </body>
80
+ </html>
81
+
82
+
83
+ @@index
84
+ <table>
85
+ <thead>
86
+ <tr>
87
+ <th class="odd">Name</th>
88
+ <th class="even">Email</th>
89
+ <th class="odd">Badge #</th>
90
+ </tr>
91
+ </thead>
92
+ <tbody>
93
+ <% people.each_with_index do |person, i| %>
94
+ <% rowclass = i.divmod(2).last.zero? ? "even" : "odd" %>
95
+ <tr class="<%= rowclass %>">
96
+ <td class="odd"><a href="/<%= person[:uid] %>"><%= person[:cn] %></p></td>
97
+ <td class="even"><a href="/<%= person[:uid] %>"><%= person[:mail] %></a></td>
98
+ <td class="odd"><%= person[:employeeNumber] %></td>
99
+ </tr>
100
+ <% end %>
101
+ </tbody>
102
+ </table>
103
+
104
+ @@details
105
+
106
+ <h2>Details for <%= person[:cn] %> <%= person[:sn] %>
107
+ &lt;<%= person[:mail] %>&gt;</h2>
108
+
109
+ <pre>
110
+ <%= person.to_ldif %>
111
+ </pre>
112
+
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ BEGIN {
4
+ require 'pathname'
5
+ basedir = Pathname( __FILE__ ).dirname.parent
6
+ libdir = basedir + 'lib'
7
+
8
+ $LOAD_PATH.unshift( libdir.to_s )
9
+ }
10
+
11
+ require 'rubygems'
12
+ require 'sinatra'
13
+ require 'treequel'
14
+ require 'erb'
15
+
16
+ include ERB::Util
17
+
18
+ # The real data is all in operational attributes, so fetch them by default
19
+ Treequel::Branch.include_operational_attrs = true
20
+
21
+ configure do
22
+ set :root, Pathname( __FILE__ ).dirname + 'ldap-monitor'
23
+ end
24
+
25
+ before do
26
+ @monitor ||= Treequel.directory( 'ldap://localhost/cn=Monitor',
27
+ :bind_dn => 'cn=admin,cn=Monitor', :pass => 'monitor' )
28
+ end
29
+
30
+ helpers do
31
+
32
+ ### Return a string describing the amount of time in the given number of
33
+ ### seconds in terms a human can understand easily.
34
+ def time_delta_string( start_time )
35
+ start = Time.parse( start_time ) or return "some time"
36
+ seconds = Time.now - start
37
+
38
+ return 'less than a minute' if seconds < 60
39
+
40
+ if seconds < 50 * 60
41
+ return "%d minute%s" % [seconds / 60, seconds/60 == 1 ? '' : 's']
42
+ end
43
+
44
+ return 'about an hour' if seconds < 90 * MINUTES
45
+ return "%d hours" % [seconds / HOURS] if seconds < 18 * HOURS
46
+ return 'one day' if seconds < 1 * DAYS
47
+ return 'about a day' if seconds < 2 * DAYS
48
+ return "%d days" % [seconds / DAYS] if seconds < 1 * WEEKS
49
+ return 'about a week' if seconds < 2 * WEEKS
50
+ return "%d weeks" % [seconds / WEEKS] if seconds < 3 * MONTHS
51
+ return "%d months" % [seconds / MONTHS] if seconds < 2 * YEARS
52
+ return "%d years" % [seconds / YEARS]
53
+ end
54
+
55
+ end
56
+
57
+ ### GET /
58
+ get '/' do
59
+ erb :index,
60
+ :locals => {
61
+ :server_info => @monitor.base['monitoredInfo'],
62
+ :datapoints => @monitor.children,
63
+ }
64
+ end
65
+
66
+
67
+ #
68
+ # Subsystems
69
+ #
70
+
71
+ get '/backends' do
72
+ subsystem = @monitor.cn( :backends )
73
+ backends = subsystem.
74
+ filter( :objectClass => :monitoredObject ).
75
+ select( :+, :* )
76
+
77
+ erb :backends,
78
+ :locals => {
79
+ :subsystem => subsystem,
80
+ :backends => backends,
81
+ }
82
+ end
83
+
84
+
85
+ get '/connections' do
86
+ subsystem = @monitor.cn( :connections )
87
+ connections = subsystem.
88
+ filter( :objectClass => :monitorConnection ).
89
+ select( :+, :* )
90
+
91
+ erb :connections,
92
+ :locals => {
93
+ :subsystem => subsystem,
94
+ :total => subsystem.cn( :total ),
95
+ :current => subsystem.cn( :current ),
96
+ :connections => connections.all,
97
+ }
98
+ end
99
+
100
+
101
+ get '/databases' do
102
+ subsystem = @monitor.cn( :databases )
103
+ databases = subsystem.
104
+ filter( :objectClass => :monitoredObject ).
105
+ select( :+, :* )
106
+
107
+ erb :databases,
108
+ :locals => {
109
+ :subsystem => subsystem,
110
+ :databases => databases,
111
+ }
112
+ end
113
+
114
+
115
+ get '/listeners' do
116
+ subsystem = @monitor.cn( :listeners )
117
+ listeners = subsystem.
118
+ filter( :objectClass => :monitoredObject ).
119
+ select( :+, :* )
120
+
121
+ erb :listeners,
122
+ :locals => {
123
+ :subsystem => subsystem,
124
+ :listeners => listeners,
125
+ }
126
+ end
127
+
128
+
129
+ ### Fallback handler for subsystems
130
+ get '/:subsystem' do
131
+ subsystem = @monitor.cn( params[:subsystem] )
132
+ contents = subsystem.
133
+ filter( :objectClass => :monitoredObject ).
134
+ select( :+, :* )
135
+
136
+ erb :dump_subsystem,
137
+ :locals => {
138
+ :subsystem => subsystem,
139
+ :contents => contents,
140
+ }
141
+ end
142
+
143
+