treequel 1.2.2 → 1.3.0pre384
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.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
|
+
|