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.tar.gz.sig +2 -1
- data/ChangeLog +61 -13
- data/History.rdoc +5 -0
- data/Manifest.txt +0 -3
- data/README.rdoc +10 -0
- data/Rakefile +6 -25
- data/lib/treequel.rb +2 -2
- data/lib/treequel/branch.rb +2 -2
- metadata +8 -79
- metadata.gz.sig +0 -0
- data/bin/treeirb +0 -18
- data/bin/treequel +0 -1276
- data/bin/treewhat +0 -394
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
|
-
|