treequel 1.9.1 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/treewhat DELETED
@@ -1,394 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'yaml'
4
- require 'abbrev'
5
- require 'trollop'
6
- require 'highline'
7
- require 'shellwords'
8
-
9
- # Work around MacOS X's vendored 'sysexits' that does the same thing,
10
- # but with a different API
11
- gem 'sysexits'
12
- require 'sysexits'
13
-
14
- require 'treequel'
15
- require 'treequel/mixins'
16
- require 'treequel/constants'
17
-
18
-
19
- # A tool for displaying information about a directory's records and schema artifacts.
20
- class Treequel::What
21
- extend Sysexits
22
- include Sysexits,
23
- Treequel::Loggable,
24
- Treequel::ANSIColorUtilities,
25
- Treequel::Constants::Patterns,
26
- Treequel::HashUtilities
27
-
28
- COLOR_SCHEME = HighLine::ColorScheme.new do |scheme|
29
- scheme[:header] = [ :bold, :yellow ]
30
- scheme[:subheader] = [ :bold, :white ]
31
- scheme[:key] = [ :white ]
32
- scheme[:value] = [ :bold, :white ]
33
- scheme[:error] = [ :red ]
34
- scheme[:warning] = [ :yellow ]
35
- scheme[:message] = [ :reset ]
36
- end
37
-
38
-
39
- ### Run the utility with the given +args+.
40
- def self::run( args )
41
- HighLine.color_scheme = COLOR_SCHEME
42
-
43
- oparser = self.make_option_parser
44
- opts = Trollop.with_standard_exception_handling( oparser ) do
45
- oparser.parse( args )
46
- end
47
-
48
- pattern = oparser.leftovers.join( ' ' ) if oparser.leftovers
49
-
50
- self.new( opts ).run( pattern )
51
- exit :ok
52
-
53
- rescue => err
54
- Treequel.logger.fatal "Oops: %s: %s" % [ err.class.name, err.message ]
55
- Treequel.logger.debug { ' ' + err.backtrace.join("\n ") }
56
-
57
- exit :software_error
58
- end
59
-
60
-
61
- ### Create and configure a command-line option parser (a Trollop::Parser) for the command.
62
- def self::make_option_parser
63
- progname = File.basename( $0 )
64
- default_directory = Treequel.directory_from_config
65
- loglevels = Treequel::LOG_LEVELS.
66
- sort_by {|name,lvl| lvl }.
67
- collect {|name,lvl| name.to_s }.
68
- join( ', ' )
69
-
70
- return Trollop::Parser.new do
71
- banner "Usage: #{progname} [OPTIONS] [PATTERN]"
72
-
73
- text ''
74
- text %{Search for an object in an LDAP directory that matches PATTERN and } +
75
- %{display some information about it.}
76
- text ''
77
- text %{The PATTERN can be the DN (or RDN relative to the base) of an entry, } +
78
- %{a search filter, or the name of an artifact in the directory's schema, } +
79
- %{such as an objectClass, matching rule, syntax, etc.}
80
- text ''
81
- text %{If no PATTERN is specified, general information about the directory is } +
82
- %{output instead.}
83
- text ''
84
-
85
- text 'Connection Options:'
86
- opt :ldapurl, "Specify the directory to connect to.",
87
- :default => default_directory.uri.to_s
88
- text ''
89
-
90
- text 'Display Options:'
91
- opt :attrtypes, "Show attribute types for objects that have them."
92
- opt :objectclasses, "Show objectclasses for objects that have them."
93
- opt :syntaxes, "Show syntaxes for objects that have them."
94
- opt :matching_rules, "Show matching rules for objects that have them."
95
- opt :matching_rule_uses, "Show matching rule uses for objects that have them."
96
- opt :all, "Show any of the above that are applicable."
97
- text ''
98
-
99
- text 'Other Options:'
100
- opt :debug, "Turn debugging on. Also sets the --loglevel to 'debug'."
101
- opt :loglevel, "Set the logging level. Must be one of: #{loglevels}",
102
- :default => Treequel::LOG_LEVEL_NAMES[ Treequel.logger.level ]
103
- opt :binddn, "The DN of the user to bind as. Defaults to anonymous binding.",
104
- :type => :string
105
- end
106
- end
107
-
108
-
109
- #################################################################
110
- ### I N S T A N C E M E T H O D S
111
- #################################################################
112
-
113
- ### Create a new instance of the command and set it up with the given
114
- ### +options+.
115
- def initialize( options )
116
- Treequel.logger.formatter = Treequel::ColorLogFormatter.new( Treequel.logger )
117
-
118
- if options.debug
119
- $DEBUG = true
120
- $VERBOSE = true
121
- Treequel.logger.level = Logger::DEBUG
122
- elsif options.loglevel
123
- Treequel.logger.level = Treequel::LOG_LEVELS[ options.loglevel ]
124
- end
125
-
126
- @options = options
127
- if @options.all?
128
- @options[:attrtypes] =
129
- @options[:objectclasses] =
130
- @options[:syntaxes] =
131
- @options[:matching_rules] =
132
- @options[:matching_rule_uses] =
133
- true
134
- end
135
-
136
- @directory = Treequel.directory( options.ldapurl )
137
- @prompt = HighLine.new
138
-
139
- @prompt.wrap_at = @prompt.output_cols - 10
140
-
141
- self.log.debug "Created new treewhat command object for %s" % [ @directory ]
142
- end
143
-
144
-
145
- ######
146
- public
147
- ######
148
-
149
- # The LDAP directory the command will connect to
150
- attr_reader :directory
151
-
152
- # The Trollop options hash the command will read its configuration from
153
- attr_reader :options
154
-
155
- # The HighLine object to use for prompting and displaying stuff
156
- attr_reader :prompt
157
-
158
-
159
- ### Display an +object+ highlighted as a header.
160
- def print_header( object )
161
- self.prompt.say( self.prompt.color(object.to_s, :header) )
162
- end
163
-
164
-
165
- ### Run the command with the specified +pattern+.
166
- def run( pattern=nil )
167
- self.log.debug "Running with pattern = %p" % [ pattern ]
168
-
169
- self.bind_to_directory if self.options.binddn
170
-
171
- case pattern
172
-
173
- # No argument
174
- when NilClass, ''
175
- self.show_directory_overview
176
-
177
- # DN/RDN or filter if it contains a '='
178
- when /=/
179
- self.show_entry( pattern )
180
-
181
- # Otherwise, try to find a schema item that matches
182
- else
183
- self.show_schema_artifact( pattern )
184
- end
185
-
186
- end
187
-
188
-
189
- ### Prompt for a password and then bind to the command's directory using the binddn in
190
- ### the options.
191
- def bind_to_directory
192
- binddn = self.options.binddn or
193
- raise ArgumentError, "no binddn in the options hash?!"
194
- self.log.debug "Attempting to bind to the directory as %s" % [ binddn ]
195
-
196
- pass = self.prompt.ask( "password: " ) {|q| q.echo = '*' }
197
- user = Treequel::Branch.new( self.directory, binddn )
198
-
199
- self.directory.bind_as( user, pass )
200
- self.log.debug " bound as %s" % [ user ]
201
-
202
- return true
203
- end
204
-
205
-
206
- ### Show general information about the directory if the user doesn't give a pattern on
207
- ### the command line.
208
- def show_directory_overview
209
- pr = self.prompt
210
- dir = self.directory
211
-
212
- self.print_header( dir.uri.to_s )
213
- pr.say( "\n" )
214
- pr.say( dir.schema.to_s )
215
-
216
- self.show_column_list( dir.schema.attribute_types.values, 'Attribute Types' ) if
217
- self.options.attrtypes
218
- self.show_column_list( dir.schema.object_classes.values, "Object Classes" ) if
219
- self.options.objectclasses
220
- self.show_column_list( dir.schema.ldap_syntaxes.values, "Syntaxes" ) if
221
- self.options.syntaxes
222
- self.show_column_list( dir.schema.matching_rules.values, "Matching Rules" ) if
223
- self.options.matching_rules
224
- self.show_column_list( dir.schema.matching_rule_uses.values, "Matching Rule Uses" ) if
225
- self.options.matching_rule_uses
226
- end
227
-
228
-
229
- ### Show the items from the given +enum+ under the specified +subheading+ in a columnized list.
230
- def show_column_list( enum, subheading )
231
- pr = self.prompt
232
- items = nil
233
-
234
- if enum.first.respond_to?( :name )
235
- items = enum.map( &:name ).map( &:to_s ).uniq
236
- else
237
- items = enum.map( &:oid ).uniq
238
- end
239
-
240
- pr.say( "\n" )
241
- pr.say( pr.color(subheading, :subheader) )
242
- pr.say( pr.list(items.sort_by(&:downcase), :columns_down) )
243
- end
244
-
245
-
246
- #
247
- # 'Show entry' mode
248
- #
249
-
250
- ### Fetch an entry from the directory and display it like Treequel's editing mode.
251
- def show_entry( pattern )
252
- dir = self.directory
253
- branch = Treequel::Branch.new( dir, pattern )
254
-
255
- if !branch.exists?
256
- branch = Treequel::Branch.new( dir, pattern + ',' + dir.base_dn )
257
- end
258
-
259
- if !branch.exists?
260
- branch = dir.filter( pattern ).first
261
- end
262
-
263
- if !branch
264
- self.prompt.say( self.prompt.color("No match.", :error) )
265
- end
266
-
267
- yaml = self.branch_as_yaml( branch )
268
- self.prompt.say( yaml )
269
- end
270
-
271
-
272
- ### Return the specified Treequel::Branch object as YAML. If +include_operational+ is true,
273
- ### include the entry's operational attributes. If +extra_objectclasses+ contains
274
- ### one or more objectClass OIDs, include their MUST and MAY attributes when building the
275
- ### YAML representation of the branch.
276
- def branch_as_yaml( object, include_operational=false )
277
- object.include_operational_attrs = include_operational
278
-
279
- # Make sure the displayed entry has the MUST attributes
280
- entryhash = stringify_keys( object.must_attributes_hash )
281
- entryhash.merge!( object.entry || {} )
282
- entryhash['objectClass'] ||= []
283
-
284
- entryhash.delete( 'dn' ) # Special attribute, can't be edited
285
-
286
- yaml = entryhash.to_yaml
287
- yaml[ 5, 0 ] = self.prompt.color( "# #{object.dn}\n", :header )
288
-
289
- # Make comments out of MAY attributes that are unset
290
- mayhash = stringify_keys( object.may_attributes_hash )
291
- self.log.debug "MAY hash is: %p" % [ mayhash ]
292
- mayhash.delete_if {|attrname,val| entryhash.key?(attrname) }
293
- yaml << mayhash.to_yaml[5..-1].gsub( /\n\n/, "\n" ).gsub( /^/, '# ' )
294
-
295
- return yaml
296
- end
297
-
298
-
299
-
300
-
301
- #
302
- # 'Show schema artifact' mode
303
- #
304
-
305
- SCHEMA_ARTIFACT_TYPES = [
306
- :object_classes,
307
- :attribute_types,
308
- :ldap_syntaxes,
309
- :matching_rules,
310
- :matching_rule_uses,
311
- ]
312
-
313
- ### Find an artifact in the directory's schema that matches +pattern+, and display it
314
- ### if it exists.
315
- def show_schema_artifact( pattern )
316
- pr = self.prompt
317
- schema = self.directory.schema
318
- artifacts = SCHEMA_ARTIFACT_TYPES.
319
- collect {|type| schema.send( type ).values.uniq }.flatten
320
-
321
- if match = find_exact_matching_artifact( artifacts, pattern )
322
- self.display_schema_artifact( match )
323
- elsif match = find_substring_matching_artifact( artifacts, pattern )
324
- pr.say( "No exact match. Falling back to substring match:" )
325
- self.display_schema_artifact( match )
326
- else
327
- pr.say( pr.color("No match.", :error) )
328
- end
329
- end
330
-
331
-
332
- ### Display a schema artifact in a readable way.
333
- def display_schema_artifact( artifact )
334
- self.prompt.say( self.prompt.color(artifact.class.name.sub(/.*::/, ''), :header) + ' ' )
335
- self.prompt.say( self.prompt.color(artifact.to_s, :subheader) )
336
-
337
- # Display some other stuff depending on what kind of thing it is
338
- case artifact
339
- when Treequel::Schema::AttributeType
340
- self.display_attrtype_details( artifact )
341
- end
342
- end
343
-
344
-
345
- ### Display additional details for the specified +attrtype+ (a Treequel::Schema::AttributeType).
346
- def display_attrtype_details( attrtype )
347
- ocs = self.directory.schema.object_classes.values.find_all do |oc|
348
- ( oc.must_oids | oc.may_oids ).include?( attrtype.name.to_sym )
349
- end
350
-
351
- if ocs.empty?
352
- self.prompt.say "No objectClasses with the '%s' attribute are in the current schema." %
353
- [ attrtype.name ]
354
- else
355
- ocnames = ocs.uniq.map( &:name ).map( &:to_s ).sort
356
-
357
- self.prompt.say "objectClasses with the '%s' attribute in the current schema:" %
358
- [ attrtype.name ]
359
- self.prompt.say( self.prompt.list(ocnames, :columns_across) )
360
- end
361
- end
362
-
363
-
364
- ### Try to find an artifact in +artifacts+ whose name or oid matches +pattern+ exactly.
365
- ### Returns the first matching artifact.
366
- def find_exact_matching_artifact( artifacts, pattern )
367
- self.log.debug "Trying to find an exact match for %p in %d artifacts." %
368
- [ pattern, artifacts.length ]
369
- return artifacts.find do |obj|
370
- (obj.respond_to?( :names ) && obj.names.map(&:to_s).include?(pattern) ) ||
371
- (obj.respond_to?( :name ) && obj.name.to_s == pattern ) ||
372
- (obj.respond_to?( :oid ) && obj.oid == pattern )
373
- end
374
- end
375
-
376
-
377
- ### Try to find an artifact in +artifacts+ whose name or oid contains +pattern+.
378
- ### Returns the first matching artifact.
379
- def find_substring_matching_artifact( artifacts, pattern )
380
- pattern = Regexp.new( Regexp.escape(pattern), Regexp::IGNORECASE )
381
-
382
- return artifacts.find do |obj|
383
- (obj.respond_to?( :names ) && obj.names.find {|name| name.to_s =~ pattern} ) ||
384
- (obj.respond_to?( :name ) && obj.name.to_s =~ pattern ) ||
385
- (obj.respond_to?( :oid ) && obj.oid =~ pattern )
386
- end
387
- end
388
-
389
-
390
- end # class Treequel::What
391
-
392
-
393
- Treequel::What.run( ARGV.dup )
394
-