treequel 1.2.2 → 1.3.0pre384
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/ChangeLog +3374 -0
- data/History.md +39 -0
- data/LICENSE +27 -0
- data/README.md +25 -2
- data/Rakefile +64 -29
- data/bin/treequel +16 -25
- data/bin/treewhat +318 -0
- data/lib/treequel.rb +13 -3
- data/lib/treequel/behavior/control.rb +40 -0
- data/lib/treequel/branch.rb +56 -28
- data/lib/treequel/branchset.rb +10 -2
- data/lib/treequel/controls/pagedresults.rb +4 -2
- data/lib/treequel/directory.rb +40 -50
- data/lib/treequel/exceptions.rb +18 -0
- data/lib/treequel/mixins.rb +44 -14
- data/lib/treequel/model.rb +338 -21
- data/lib/treequel/model/errors.rb +79 -0
- data/lib/treequel/model/objectclass.rb +26 -2
- data/lib/treequel/model/schemavalidations.rb +69 -0
- data/lib/treequel/monkeypatches.rb +99 -17
- data/lib/treequel/schema.rb +19 -5
- data/spec/lib/constants.rb +20 -2
- data/spec/lib/helpers.rb +25 -8
- data/spec/treequel/branch_spec.rb +73 -10
- data/spec/treequel/controls/contentsync_spec.rb +2 -11
- data/spec/treequel/controls/pagedresults_spec.rb +25 -9
- data/spec/treequel/controls/sortedresults_spec.rb +8 -10
- data/spec/treequel/directory_spec.rb +74 -63
- data/spec/treequel/model/errors_spec.rb +77 -0
- data/spec/treequel/model/objectclass_spec.rb +107 -35
- data/spec/treequel/model/schemavalidations_spec.rb +112 -0
- data/spec/treequel/model_spec.rb +294 -81
- data/spec/treequel/monkeypatches_spec.rb +49 -3
- metadata +28 -16
- metadata.gz.sig +0 -0
- data/spec/lib/control_behavior.rb +0 -47
data/History.md
CHANGED
@@ -1,3 +1,42 @@
|
|
1
|
+
## 1.3.0 [2011-01-07] Michael Granger <ged@FaerieMUD.org>
|
2
|
+
|
3
|
+
Enhancements:
|
4
|
+
|
5
|
+
* Made Treequel::Model act more like an ORM -- changes made to the object aren't synced
|
6
|
+
with the directory until #save is called. New methods:
|
7
|
+
- Treequel::Model#save
|
8
|
+
- Treequel::Model#modifications
|
9
|
+
- Treequel::Model#modifications_ldif
|
10
|
+
- Treequel::Model#validate
|
11
|
+
- Treequel::Model#valid?
|
12
|
+
- Treequel::Model#errors
|
13
|
+
- Treequel::Model#revert
|
14
|
+
- Treequel::Model#modified?
|
15
|
+
New classes:
|
16
|
+
- Treequel::Model::Errors
|
17
|
+
- Treequel::ValidationFailed
|
18
|
+
* Extracted the controls behavior and rewrote the control specs to use it. This is
|
19
|
+
so people who may wish to implement their own controls can ensure that it's
|
20
|
+
compatible with Treequel.
|
21
|
+
* Added a directory-introspection tool (treewhat)
|
22
|
+
* Added Treequel::Model::ObjectClass.create for easy creation of entries that conform
|
23
|
+
to an objectClass mixin's criteria
|
24
|
+
* Treequel::Directory.root_dse now returns Treequel::Branches
|
25
|
+
* Added Treequel::Directory#reconnect.
|
26
|
+
|
27
|
+
Bugfixes:
|
28
|
+
|
29
|
+
* Fixed a bug in Treequel::Branch#merge for values that need conversion
|
30
|
+
* Simplified and removed duplication from the logging code
|
31
|
+
* Fixed a bug in the proxy method for single-letter attribute names.
|
32
|
+
* Monkeypatched Date for LDAP time type conversions
|
33
|
+
* Change the return values of unset attributes to distinguish between SINGLE and non-SINGLE
|
34
|
+
attributes
|
35
|
+
* Treequel::Branch
|
36
|
+
- Check for explicit nil DN in .new
|
37
|
+
- Check for nil parent_dn in #parent
|
38
|
+
- Use 'top' instead of :top as objectClass default
|
39
|
+
|
1
40
|
|
2
41
|
## 1.2.2 [2010-12-14] Michael Granger <ged@FaerieMUD.org>
|
3
42
|
|
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2008-2010, Michael Granger and Mahlon E. Smith
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
* Neither the name of the author/s, nor the names of the project's
|
15
|
+
contributors may be used to endorse or promote products derived from this
|
16
|
+
software without specific prior written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
CHANGED
@@ -75,11 +75,34 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
75
75
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
76
76
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
77
77
|
|
78
|
+
This software includes some code from the Sequel database toolkit, used under
|
79
|
+
the following license terms:
|
80
|
+
|
81
|
+
Copyright (c) 2007-2008 Sharon Rosner
|
82
|
+
Copyright (c) 2008-2010 Jeremy Evans
|
83
|
+
|
84
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
85
|
+
of this software and associated documentation files (the "Software"), to
|
86
|
+
deal in the Software without restriction, including without limitation the
|
87
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
88
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
89
|
+
furnished to do so, subject to the following conditions:
|
90
|
+
|
91
|
+
The above copyright notice and this permission notice shall be included in
|
92
|
+
all copies or substantial portions of the Software.
|
93
|
+
|
94
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
95
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
96
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
97
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
98
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
99
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
100
|
+
|
78
101
|
|
79
102
|
## Authors
|
80
103
|
|
81
|
-
* Michael Granger
|
82
|
-
* Mahlon E. Smith
|
104
|
+
* Michael Granger <ged@FaerieMUD.org>
|
105
|
+
* Mahlon E. Smith <mahlon@martini.nu>
|
83
106
|
|
84
107
|
|
85
108
|
## Contributors
|
data/Rakefile
CHANGED
@@ -2,9 +2,10 @@
|
|
2
2
|
|
3
3
|
require 'hoe'
|
4
4
|
|
5
|
-
Hoe.plugin :
|
5
|
+
Hoe.plugin :mercurial
|
6
6
|
Hoe.plugin :yard
|
7
7
|
Hoe.plugin :signing
|
8
|
+
Hoe.plugin :manualgen
|
8
9
|
|
9
10
|
Hoe.plugins.delete :rubyforge
|
10
11
|
|
@@ -15,47 +16,81 @@ hoespec = Hoe.spec 'treequel' do
|
|
15
16
|
self.developer 'Michael Granger', 'ged@FaerieMUD.org'
|
16
17
|
self.developer 'Mahlon E. Smith', 'mahlon@martini.nu'
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
self.extra_dev_deps
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
self.extra_deps.push *{
|
20
|
+
'ruby-ldap' => '~> 0.9.11'
|
21
|
+
}
|
22
|
+
self.extra_dev_deps.push *{
|
23
|
+
'rspec' => '~> 2.4.0',
|
24
|
+
'ruby-termios' => '~> 0.9.6',
|
25
|
+
'ruby-terminfo' => '~> 0.1.1',
|
26
|
+
'columnize' => '~> 0.3.1',
|
27
|
+
}
|
26
28
|
|
27
29
|
self.spec_extras[:licenses] = ["BSD"]
|
28
30
|
self.spec_extras[:post_install_message] = [
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
"If you want to use the included 'treequel' LDAP shell, you'll need to install",
|
32
|
+
"the following libraries as well:",
|
33
|
+
'',
|
34
|
+
" - termios",
|
35
|
+
" - ruby-terminfo",
|
36
|
+
" - columnize",
|
37
|
+
'',
|
38
|
+
"You can install those automatically if you use the --development flag when",
|
39
|
+
"installing Treequel."
|
40
|
+
].join( "\n" )
|
41
|
+
self.spec_extras[:signing_key] = '/Volumes/Keys/ged-private_gem_key.pem'
|
36
42
|
|
37
43
|
self.require_ruby_version( '>=1.8.7' )
|
38
44
|
|
39
|
-
self.hg_sign_tags = true
|
45
|
+
self.hg_sign_tags = true if self.respond_to?( :hg_sign_tags= )
|
46
|
+
self.manual_source_dir = 'src' if self.respond_to?( :manual_source_dir= )
|
47
|
+
self.yard_opts = [ '--use-cache', '--protected', '--verbose' ] if
|
48
|
+
self.respond_to?( :yard_opts= )
|
40
49
|
|
41
|
-
self.
|
50
|
+
self.rdoc_locations << "deveiate:/usr/local/www/public/code/#{remote_rdoc_dir}"
|
42
51
|
end
|
43
52
|
|
44
53
|
ENV['VERSION'] ||= hoespec.spec.version.to_s
|
45
54
|
|
46
|
-
|
55
|
+
begin
|
56
|
+
include Hoe::MercurialHelpers
|
57
|
+
|
58
|
+
task 'hg:precheckin' => :spec
|
47
59
|
|
48
|
-
### Task: prerelease
|
49
|
-
desc "Append the package build number to package versions"
|
50
|
-
task :pre do
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
60
|
+
### Task: prerelease
|
61
|
+
desc "Append the package build number to package versions"
|
62
|
+
task :pre do
|
63
|
+
rev = get_numeric_rev()
|
64
|
+
trace "Current rev is: %p" % [ rev ]
|
65
|
+
hoespec.spec.version.version << "pre#{rev}"
|
66
|
+
Rake::Task[:gem].clear
|
55
67
|
|
56
|
-
|
57
|
-
|
58
|
-
|
68
|
+
Gem::PackageTask.new( hoespec.spec ) do |pkg|
|
69
|
+
pkg.need_zip = true
|
70
|
+
pkg.need_tar = true
|
71
|
+
end
|
59
72
|
end
|
73
|
+
|
74
|
+
### Make the ChangeLog update if the repo has changed since it was last built
|
75
|
+
file '.hg/branch'
|
76
|
+
file 'ChangeLog' => '.hg/branch' do |task|
|
77
|
+
$stderr.puts "Updating the changelog..."
|
78
|
+
content = make_changelog()
|
79
|
+
File.open( task.name, 'w', 0644 ) do |fh|
|
80
|
+
fh.print( content )
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Rebuild the ChangeLog immediately before release
|
85
|
+
task :prerelease => 'ChangeLog'
|
86
|
+
|
87
|
+
rescue NameError => err
|
88
|
+
task :no_hg_helpers do
|
89
|
+
fail "Couldn't define the :pre task: %s: %s" % [ err.class.name, err.message ]
|
90
|
+
end
|
91
|
+
|
92
|
+
task :pre => :no_hg_helpers
|
93
|
+
task 'ChangeLog' => :no_hg_helpers
|
94
|
+
|
60
95
|
end
|
61
96
|
|
data/bin/treequel
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require 'rubygems'
|
4
|
-
|
5
3
|
require 'abbrev'
|
6
4
|
require 'columnize'
|
7
5
|
require 'diff/lcs'
|
@@ -44,9 +42,9 @@ module IRB # :nodoc:
|
|
44
42
|
def self.start_session( obj )
|
45
43
|
unless @__initialized
|
46
44
|
args = ARGV
|
47
|
-
ARGV.replace(ARGV.dup)
|
48
|
-
IRB.setup(nil)
|
49
|
-
ARGV.replace(args)
|
45
|
+
ARGV.replace( ARGV.dup )
|
46
|
+
IRB.setup( nil )
|
47
|
+
ARGV.replace( args )
|
50
48
|
@__initialized = true
|
51
49
|
end
|
52
50
|
|
@@ -91,16 +89,6 @@ class Treequel::Shell
|
|
91
89
|
CLEAR_TO_EOL = "\e[K"
|
92
90
|
CLEAR_CURRENT_LINE = "\e[2K"
|
93
91
|
|
94
|
-
# Log levels
|
95
|
-
LOG_LEVELS = {
|
96
|
-
'debug' => Logger::DEBUG,
|
97
|
-
'info' => Logger::INFO,
|
98
|
-
'warn' => Logger::WARN,
|
99
|
-
'error' => Logger::ERROR,
|
100
|
-
'fatal' => Logger::FATAL,
|
101
|
-
}.freeze
|
102
|
-
LOG_LEVEL_NAMES = LOG_LEVELS.invert.freeze
|
103
|
-
|
104
92
|
# Valid connect-type arguments
|
105
93
|
VALID_CONNECT_TYPES = %w[tls ssl plain]
|
106
94
|
|
@@ -137,6 +125,10 @@ class Treequel::Shell
|
|
137
125
|
### the LDAP URI.
|
138
126
|
def self::parse_options( argv )
|
139
127
|
progname = File.basename( $0 )
|
128
|
+
loglevels = Treequel::LOG_LEVELS.
|
129
|
+
sort_by {|_,lvl| lvl }.
|
130
|
+
collect {|name,lvl| name.to_s }.
|
131
|
+
join(', ')
|
140
132
|
bind_as = nil
|
141
133
|
|
142
134
|
oparser = OptionParser.new( "Usage: #{progname} [OPTIONS] [LDAPURL]" ) do |oparser|
|
@@ -146,10 +138,9 @@ class Treequel::Shell
|
|
146
138
|
bind_as = dn
|
147
139
|
end
|
148
140
|
|
149
|
-
oparser.on( '--loglevel=LEVEL', '-l LEVEL', Treequel::
|
150
|
-
"Set the logging level. Should be one of:",
|
151
|
-
Treequel
|
152
|
-
Treequel.logger.level = Treequel::Loggable::LEVEL[ lvl.to_sym ] or
|
141
|
+
oparser.on( '--loglevel=LEVEL', '-l LEVEL', Treequel::LOG_LEVELS.keys,
|
142
|
+
"Set the logging level. Should be one of:", loglevels ) do |lvl|
|
143
|
+
Treequel.logger.level = Treequel::LOG_LEVELS[ lvl ] or
|
153
144
|
raise "Invalid logging level %p" % [ lvl ]
|
154
145
|
end
|
155
146
|
|
@@ -234,7 +225,7 @@ class Treequel::Shell
|
|
234
225
|
# If the user said to bind as someone on the command line, invoke a
|
235
226
|
# 'bind' command before dropping into the command line
|
236
227
|
if bind_as
|
237
|
-
options = OpenStruct.new
|
228
|
+
options = OpenStruct.new # dummy options object
|
238
229
|
self.bind_command( options, bind_as )
|
239
230
|
end
|
240
231
|
|
@@ -454,23 +445,23 @@ class Treequel::Shell
|
|
454
445
|
def log_command( options, *args )
|
455
446
|
newlevel = args.shift
|
456
447
|
if newlevel
|
457
|
-
if LOG_LEVELS.key?( newlevel )
|
458
|
-
Treequel.logger.level = LOG_LEVELS[ newlevel ]
|
448
|
+
if Treequel::LOG_LEVELS.key?( newlevel )
|
449
|
+
Treequel.logger.level = Treequel::LOG_LEVELS[ newlevel ]
|
459
450
|
message "Set log level to: %s" % [ newlevel ]
|
460
451
|
else
|
461
|
-
levelnames = LOG_LEVEL_NAMES.keys.sort.join(', ')
|
452
|
+
levelnames = Treequel::LOG_LEVEL_NAMES.keys.sort.join(', ')
|
462
453
|
raise "Invalid log level %p: valid values are:\n %s" % [ newlevel, levelnames ]
|
463
454
|
end
|
464
455
|
else
|
465
456
|
message "Log level is currently: %s" %
|
466
|
-
[ LOG_LEVEL_NAMES[Treequel.logger.level] ]
|
457
|
+
[ Treequel::LOG_LEVEL_NAMES[Treequel.logger.level] ]
|
467
458
|
end
|
468
459
|
end
|
469
460
|
set_options :log do |oparser, options|
|
470
461
|
oparser.banner = "log [LEVEL]"
|
471
462
|
oparser.separator 'Set the logging level, or display the current level if no level ' +
|
472
463
|
"is given. Valid log levels are: %s" %
|
473
|
-
LOG_LEVEL_NAMES.keys.sort.join(', ')
|
464
|
+
Treequel::LOG_LEVEL_NAMES.keys.sort.join(', ')
|
474
465
|
end
|
475
466
|
|
476
467
|
|
data/bin/treewhat
ADDED
@@ -0,0 +1,318 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'abbrev'
|
5
|
+
require 'trollop'
|
6
|
+
require 'highline'
|
7
|
+
require 'shellwords'
|
8
|
+
require 'sysexits'
|
9
|
+
|
10
|
+
require 'treequel'
|
11
|
+
require 'treequel/mixins'
|
12
|
+
require 'treequel/constants'
|
13
|
+
|
14
|
+
|
15
|
+
# A tool for displaying information about a directory's records and schema artifacts.
|
16
|
+
class Treequel::What
|
17
|
+
extend Sysexits
|
18
|
+
include Sysexits,
|
19
|
+
Treequel::Loggable,
|
20
|
+
Treequel::ANSIColorUtilities,
|
21
|
+
Treequel::Constants::Patterns,
|
22
|
+
Treequel::HashUtilities
|
23
|
+
|
24
|
+
COLOR_SCHEME = HighLine::ColorScheme.new do |scheme|
|
25
|
+
scheme[:header] = [ :bold, :yellow ]
|
26
|
+
scheme[:subheader] = [ :bold, :white ]
|
27
|
+
scheme[:key] = [ :white ]
|
28
|
+
scheme[:value] = [ :bold, :white ]
|
29
|
+
scheme[:error] = [ :red ]
|
30
|
+
scheme[:warning] = [ :yellow ]
|
31
|
+
scheme[:message] = [ :reset ]
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
### Run the utility with the given +args+.
|
36
|
+
def self::run( args )
|
37
|
+
HighLine.color_scheme = COLOR_SCHEME
|
38
|
+
|
39
|
+
oparser = self.make_option_parser
|
40
|
+
opts = Trollop.with_standard_exception_handling( oparser ) do
|
41
|
+
oparser.parse( args )
|
42
|
+
end
|
43
|
+
|
44
|
+
pattern = oparser.leftovers.join( ' ' ) if oparser.leftovers
|
45
|
+
|
46
|
+
self.new( opts ).run( pattern )
|
47
|
+
exit :ok
|
48
|
+
|
49
|
+
rescue => err
|
50
|
+
Treequel.logger.fatal "Oops: %s: %s" % [ err.class.name, err.message ]
|
51
|
+
Treequel.logger.debug { ' ' + err.backtrace.join("\n ") }
|
52
|
+
|
53
|
+
exit :software_error
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
### Create and configure a command-line option parser for the command.
|
58
|
+
### @return [Trollop::Parser] the option parser
|
59
|
+
def self::make_option_parser
|
60
|
+
progname = File.basename( $0 )
|
61
|
+
default_directory = Treequel.directory_from_config
|
62
|
+
loglevels = Treequel::LOG_LEVELS.
|
63
|
+
sort_by {|name,lvl| lvl }.
|
64
|
+
collect {|name,lvl| name.to_s }.
|
65
|
+
join( ', ' )
|
66
|
+
|
67
|
+
return Trollop::Parser.new do
|
68
|
+
banner "Usage: #{progname} [OPTIONS] [PATTERN]"
|
69
|
+
|
70
|
+
text ''
|
71
|
+
text %{Search for an object in an LDAP directory that matches PATTERN and } +
|
72
|
+
%{display some information about it.}
|
73
|
+
text ''
|
74
|
+
text %{The PATTERN can be the DN (or RDN relative to the base) of an entry, } +
|
75
|
+
%{a search filter, or the name of an artifact in the directory's schema, } +
|
76
|
+
%{such as an objectClass, matching rule, syntax, etc.}
|
77
|
+
text ''
|
78
|
+
text %{If no PATTERN is specified, general information about the directory is } +
|
79
|
+
%{output instead.}
|
80
|
+
text ''
|
81
|
+
|
82
|
+
text 'Options:'
|
83
|
+
opt :ldapurl, "Specify the directory to connect to.",
|
84
|
+
:default => default_directory.uri.to_s
|
85
|
+
opt :debug, "Turn debugging on. Also sets the --loglevel to 'debug'."
|
86
|
+
opt :loglevel, "Set the logging level. Must be one of: #{loglevels}",
|
87
|
+
:default => Treequel::LOG_LEVEL_NAMES[ Treequel.logger.level ]
|
88
|
+
opt :binddn, "The DN of the user to bind as. Defaults to anonymous binding.",
|
89
|
+
:type => :string
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
#################################################################
|
95
|
+
### I N S T A N C E M E T H O D S
|
96
|
+
#################################################################
|
97
|
+
|
98
|
+
### Create a new instance of the command and set it up with the given
|
99
|
+
### +options+.
|
100
|
+
def initialize( options )
|
101
|
+
Treequel.logger.formatter = Treequel::ColorLogFormatter.new( Treequel.logger )
|
102
|
+
|
103
|
+
if options.debug
|
104
|
+
$DEBUG = true
|
105
|
+
$VERBOSE = true
|
106
|
+
Treequel.logger.level = Logger::DEBUG
|
107
|
+
elsif options.loglevel
|
108
|
+
Treequel.logger.level = Treequel::LOG_LEVELS[ options.loglevel ]
|
109
|
+
end
|
110
|
+
|
111
|
+
@options = options
|
112
|
+
@prompt = HighLine.new
|
113
|
+
@directory = Treequel.directory( options.ldapurl )
|
114
|
+
|
115
|
+
self.log.debug "Created new treewhat command object for %s" % [ @directory ]
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
######
|
120
|
+
public
|
121
|
+
######
|
122
|
+
|
123
|
+
# The LDAP directory the command will connect to
|
124
|
+
attr_reader :directory
|
125
|
+
|
126
|
+
# The Trollop options hash the command will read its configuration from
|
127
|
+
attr_reader :options
|
128
|
+
|
129
|
+
# The HighLine object to use for prompting and displaying stuff
|
130
|
+
attr_reader :prompt
|
131
|
+
|
132
|
+
|
133
|
+
### Display an +object+ highlighted as a header.
|
134
|
+
def print_header( object )
|
135
|
+
self.prompt.say( self.prompt.color(object.to_s, :header) )
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
|
140
|
+
### Run the command with the specified +pattern+.
|
141
|
+
def run( pattern=nil )
|
142
|
+
self.log.debug "Running with pattern = %p" % [ pattern ]
|
143
|
+
|
144
|
+
self.bind_to_directory if self.options.binddn
|
145
|
+
|
146
|
+
case pattern
|
147
|
+
|
148
|
+
# No argument
|
149
|
+
when NilClass, ''
|
150
|
+
self.show_directory_overview
|
151
|
+
|
152
|
+
# DN/RDN or filter if it contains a '='
|
153
|
+
when /=/
|
154
|
+
self.show_entry( pattern )
|
155
|
+
|
156
|
+
# Otherwise, try to find a schema item that matches
|
157
|
+
else
|
158
|
+
self.show_schema_artifact( pattern )
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
### Prompt for a password and then bind to the command's directory using the binddn in
|
165
|
+
### the options.
|
166
|
+
def bind_to_directory
|
167
|
+
binddn = self.options.binddn or
|
168
|
+
raise ArgumentError, "no binddn in the options hash?!"
|
169
|
+
self.log.debug "Attempting to bind to the directory as %s" % [ binddn ]
|
170
|
+
|
171
|
+
pass = self.prompt.ask( "password: " ) {|q| q.echo = '*' }
|
172
|
+
user = Treequel::Branch.new( self.directory, binddn )
|
173
|
+
|
174
|
+
self.directory.bind_as( user, pass )
|
175
|
+
self.log.debug " bound as %s" % [ user ]
|
176
|
+
|
177
|
+
return true
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
### Show general information about the directory if the user doesn't give a pattern on
|
182
|
+
### the command line.
|
183
|
+
def show_directory_overview
|
184
|
+
pr = self.prompt
|
185
|
+
dir = self.directory
|
186
|
+
|
187
|
+
self.print_header( dir.uri.to_s )
|
188
|
+
pr.say( "\n" )
|
189
|
+
pr.say( dir.schema.to_s )
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
|
195
|
+
#
|
196
|
+
# 'Show entry' mode
|
197
|
+
#
|
198
|
+
|
199
|
+
### Fetch an entry from the directory and display it like Treequel's editing mode.
|
200
|
+
def show_entry( pattern )
|
201
|
+
dir = self.directory
|
202
|
+
branch = Treequel::Branch.new( dir, pattern )
|
203
|
+
|
204
|
+
if !branch.exists?
|
205
|
+
branch = Treequel::Branch.new( dir, pattern + ',' + dir.base_dn )
|
206
|
+
end
|
207
|
+
|
208
|
+
if !branch.exists?
|
209
|
+
branch = dir.filter( pattern ).first
|
210
|
+
end
|
211
|
+
|
212
|
+
if !branch
|
213
|
+
self.prompt.say( self.prompt.color("No match.", :error) )
|
214
|
+
end
|
215
|
+
|
216
|
+
yaml = self.branch_as_yaml( branch )
|
217
|
+
self.prompt.say( yaml )
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
### Return the specified Treequel::Branch object as YAML. If +include_operational+ is true,
|
222
|
+
### include the entry's operational attributes. If +extra_objectclasses+ contains
|
223
|
+
### one or more objectClass OIDs, include their MUST and MAY attributes when building the
|
224
|
+
### YAML representation of the branch.
|
225
|
+
def branch_as_yaml( object, include_operational=false )
|
226
|
+
object.include_operational_attrs = include_operational
|
227
|
+
|
228
|
+
# Make sure the displayed entry has the MUST attributes
|
229
|
+
entryhash = stringify_keys( object.must_attributes_hash )
|
230
|
+
entryhash.merge!( object.entry || {} )
|
231
|
+
entryhash['objectClass'] ||= []
|
232
|
+
|
233
|
+
entryhash.delete( 'dn' ) # Special attribute, can't be edited
|
234
|
+
|
235
|
+
yaml = entryhash.to_yaml
|
236
|
+
yaml[ 5, 0 ] = self.prompt.color( "# #{object.dn}\n", :header )
|
237
|
+
|
238
|
+
# Make comments out of MAY attributes that are unset
|
239
|
+
mayhash = stringify_keys( object.may_attributes_hash )
|
240
|
+
self.log.debug "MAY hash is: %p" % [ mayhash ]
|
241
|
+
mayhash.delete_if {|attrname,val| entryhash.key?(attrname) }
|
242
|
+
yaml << mayhash.to_yaml[5..-1].gsub( /\n\n/, "\n" ).gsub( /^/, '# ' )
|
243
|
+
|
244
|
+
return yaml
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
|
249
|
+
|
250
|
+
#
|
251
|
+
# 'Show schema artifact' mode
|
252
|
+
#
|
253
|
+
|
254
|
+
SCHEMA_ARTIFACT_TYPES = [
|
255
|
+
:object_classes,
|
256
|
+
:attribute_types,
|
257
|
+
:ldap_syntaxes,
|
258
|
+
:matching_rules,
|
259
|
+
:matching_rule_uses,
|
260
|
+
]
|
261
|
+
|
262
|
+
### Find an artifact in the directory's schema that matches +pattern+, and display it
|
263
|
+
### if it exists.
|
264
|
+
def show_schema_artifact( pattern )
|
265
|
+
pr = self.prompt
|
266
|
+
schema = self.directory.schema
|
267
|
+
artifacts = SCHEMA_ARTIFACT_TYPES.
|
268
|
+
collect {|type| schema.send( type ).values.uniq }.flatten
|
269
|
+
|
270
|
+
if match = find_exact_matching_artifact( artifacts, pattern )
|
271
|
+
self.display_schema_artifact( match )
|
272
|
+
elsif match = find_substring_matching_artifact( artifacts, pattern )
|
273
|
+
pr.say( "No exact match. Falling back to substring match:" )
|
274
|
+
self.display_schema_artifact( match )
|
275
|
+
else
|
276
|
+
pr.say( pr.color("No match.", :error) )
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
|
281
|
+
### Display a schema artifact in a readable way.
|
282
|
+
def display_schema_artifact( artifact )
|
283
|
+
self.prompt.say( artifact.class.name.sub(/.*::/, '') + ' ' )
|
284
|
+
self.prompt.say( artifact.to_s )
|
285
|
+
end
|
286
|
+
|
287
|
+
|
288
|
+
### Try to find an artifact in +artifacts+ whose name or oid matches +pattern+ exactly.
|
289
|
+
### Returns the first matching artifact.
|
290
|
+
def find_exact_matching_artifact( artifacts, pattern )
|
291
|
+
self.log.debug "Trying to find an exact match for %p in %d artifacts." %
|
292
|
+
[ pattern, artifacts.length ]
|
293
|
+
return artifacts.find do |obj|
|
294
|
+
(obj.respond_to?( :names ) && obj.names.map(&:to_s).include?(pattern) ) ||
|
295
|
+
(obj.respond_to?( :name ) && obj.name.to_s == pattern ) ||
|
296
|
+
(obj.respond_to?( :oid ) && obj.oid == pattern )
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
### Try to find an artifact in +artifacts+ whose name or oid contains +pattern+.
|
302
|
+
### Returns the first matching artifact.
|
303
|
+
def find_substring_matching_artifact( artifacts, pattern )
|
304
|
+
pattern = Regexp.new( Regexp.escape(pattern), Regexp::IGNORECASE )
|
305
|
+
|
306
|
+
return artifacts.find do |obj|
|
307
|
+
(obj.respond_to?( :names ) && obj.names.find {|name| name.to_s =~ pattern} ) ||
|
308
|
+
(obj.respond_to?( :name ) && obj.name.to_s =~ pattern ) ||
|
309
|
+
(obj.respond_to?( :oid ) && obj.oid =~ pattern )
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
|
314
|
+
end # class Treequel::What
|
315
|
+
|
316
|
+
|
317
|
+
Treequel::What.run( ARGV.dup )
|
318
|
+
|