automate-it 0.9.0
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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.hgignore +10 -0
- data/.loadpath +5 -0
- data/.project +17 -0
- data/CHANGES.txt +314 -0
- data/Hoe.rake +40 -0
- data/Manifest.txt +164 -0
- data/README.txt +40 -0
- data/Rakefile +256 -0
- data/TESTING.txt +57 -0
- data/TODO.txt +50 -0
- data/TUTORIAL.txt +391 -0
- data/automate-it.gemspec +25 -0
- data/bin/ai +3 -0
- data/bin/aifield +75 -0
- data/bin/aissh +93 -0
- data/bin/aitag +134 -0
- data/bin/automateit +133 -0
- data/docs/friendly_errors.txt +50 -0
- data/docs/previews.txt +86 -0
- data/examples/basic/Rakefile +26 -0
- data/examples/basic/config/automateit_env.rb +16 -0
- data/examples/basic/config/fields.yml +3 -0
- data/examples/basic/config/tags.yml +7 -0
- data/examples/basic/dist/README.txt +9 -0
- data/examples/basic/dist/myapp_server.erb +30 -0
- data/examples/basic/install.log +15 -0
- data/examples/basic/lib/README.txt +10 -0
- data/examples/basic/recipes/README.txt +4 -0
- data/examples/basic/recipes/install.rb +61 -0
- data/examples/basic/recipes/uninstall.rb +6 -0
- data/gpl.txt +674 -0
- data/helpers/cpan_wrapper.pl +220 -0
- data/helpers/which.cmd +7 -0
- data/lib/automateit.rb +55 -0
- data/lib/automateit/account_manager.rb +114 -0
- data/lib/automateit/account_manager/base.rb +138 -0
- data/lib/automateit/account_manager/etc.rb +128 -0
- data/lib/automateit/account_manager/nscd.rb +33 -0
- data/lib/automateit/account_manager/passwd_expect.rb +40 -0
- data/lib/automateit/account_manager/passwd_pty.rb +69 -0
- data/lib/automateit/account_manager/posix.rb +138 -0
- data/lib/automateit/address_manager.rb +88 -0
- data/lib/automateit/address_manager/base.rb +171 -0
- data/lib/automateit/address_manager/bsd.rb +28 -0
- data/lib/automateit/address_manager/freebsd.rb +59 -0
- data/lib/automateit/address_manager/linux.rb +42 -0
- data/lib/automateit/address_manager/openbsd.rb +66 -0
- data/lib/automateit/address_manager/portable.rb +37 -0
- data/lib/automateit/address_manager/sunos.rb +34 -0
- data/lib/automateit/cli.rb +85 -0
- data/lib/automateit/common.rb +65 -0
- data/lib/automateit/constants.rb +35 -0
- data/lib/automateit/download_manager.rb +48 -0
- data/lib/automateit/edit_manager.rb +321 -0
- data/lib/automateit/error.rb +10 -0
- data/lib/automateit/field_manager.rb +103 -0
- data/lib/automateit/interpreter.rb +631 -0
- data/lib/automateit/package_manager.rb +257 -0
- data/lib/automateit/package_manager/apt.rb +27 -0
- data/lib/automateit/package_manager/cpan.rb +101 -0
- data/lib/automateit/package_manager/dpkg.rb +54 -0
- data/lib/automateit/package_manager/egg.rb +64 -0
- data/lib/automateit/package_manager/gem.rb +201 -0
- data/lib/automateit/package_manager/pear.rb +95 -0
- data/lib/automateit/package_manager/pecl.rb +80 -0
- data/lib/automateit/package_manager/portage.rb +69 -0
- data/lib/automateit/package_manager/yum.rb +65 -0
- data/lib/automateit/platform_manager.rb +49 -0
- data/lib/automateit/platform_manager/darwin.rb +30 -0
- data/lib/automateit/platform_manager/debian.rb +26 -0
- data/lib/automateit/platform_manager/freebsd.rb +29 -0
- data/lib/automateit/platform_manager/gentoo.rb +26 -0
- data/lib/automateit/platform_manager/lsb.rb +44 -0
- data/lib/automateit/platform_manager/openbsd.rb +28 -0
- data/lib/automateit/platform_manager/struct.rb +80 -0
- data/lib/automateit/platform_manager/sunos.rb +39 -0
- data/lib/automateit/platform_manager/uname.rb +29 -0
- data/lib/automateit/platform_manager/windows.rb +40 -0
- data/lib/automateit/plugin.rb +7 -0
- data/lib/automateit/plugin/base.rb +32 -0
- data/lib/automateit/plugin/driver.rb +256 -0
- data/lib/automateit/plugin/manager.rb +224 -0
- data/lib/automateit/project.rb +493 -0
- data/lib/automateit/root.rb +17 -0
- data/lib/automateit/service_manager.rb +93 -0
- data/lib/automateit/service_manager/chkconfig.rb +39 -0
- data/lib/automateit/service_manager/rc_update.rb +37 -0
- data/lib/automateit/service_manager/sysv.rb +139 -0
- data/lib/automateit/service_manager/update_rcd.rb +35 -0
- data/lib/automateit/shell_manager.rb +316 -0
- data/lib/automateit/shell_manager/base_link.rb +67 -0
- data/lib/automateit/shell_manager/link.rb +24 -0
- data/lib/automateit/shell_manager/portable.rb +523 -0
- data/lib/automateit/shell_manager/symlink.rb +32 -0
- data/lib/automateit/shell_manager/which_base.rb +30 -0
- data/lib/automateit/shell_manager/which_unix.rb +16 -0
- data/lib/automateit/shell_manager/which_windows.rb +20 -0
- data/lib/automateit/tag_manager.rb +127 -0
- data/lib/automateit/tag_manager/struct.rb +121 -0
- data/lib/automateit/tag_manager/tag_parser.rb +93 -0
- data/lib/automateit/tag_manager/yaml.rb +29 -0
- data/lib/automateit/template_manager.rb +56 -0
- data/lib/automateit/template_manager/base.rb +181 -0
- data/lib/automateit/template_manager/erb.rb +17 -0
- data/lib/ext/metaclass.rb +17 -0
- data/lib/ext/object.rb +18 -0
- data/lib/ext/shell_escape.rb +7 -0
- data/lib/hashcache.rb +22 -0
- data/lib/helpful_erb.rb +63 -0
- data/lib/inactive_support.rb +53 -0
- data/lib/inactive_support/basic_object.rb +6 -0
- data/lib/inactive_support/clean_logger.rb +127 -0
- data/lib/inactive_support/core_ext/array/extract_options.rb +19 -0
- data/lib/inactive_support/core_ext/blank.rb +50 -0
- data/lib/inactive_support/core_ext/class/attribute_accessors.rb +48 -0
- data/lib/inactive_support/core_ext/class/inheritable_attributes.rb +140 -0
- data/lib/inactive_support/core_ext/enumerable.rb +63 -0
- data/lib/inactive_support/core_ext/hash/keys.rb +54 -0
- data/lib/inactive_support/core_ext/module/aliasing.rb +70 -0
- data/lib/inactive_support/core_ext/numeric/time.rb +91 -0
- data/lib/inactive_support/core_ext/string/inflections.rb +153 -0
- data/lib/inactive_support/core_ext/symbol.rb +14 -0
- data/lib/inactive_support/core_ext/time/conversions.rb +96 -0
- data/lib/inactive_support/duration.rb +96 -0
- data/lib/inactive_support/inflections.rb +53 -0
- data/lib/inactive_support/inflector.rb +282 -0
- data/lib/nested_error.rb +33 -0
- data/lib/nitpick.rb +33 -0
- data/lib/queued_logger.rb +68 -0
- data/lib/tempster.rb +250 -0
- data/misc/index_gem_repository.rb +304 -0
- data/misc/setup_egg.rb +12 -0
- data/misc/setup_gem_dependencies.sh +6 -0
- data/misc/setup_rubygems.sh +21 -0
- metadata +279 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
# == PlatformManager::Windows
|
2
|
+
#
|
3
|
+
# A PlatformManager driver for Windows systems.
|
4
|
+
class AutomateIt::PlatformManager::Windows < AutomateIt::PlatformManager::Struct
|
5
|
+
def available?
|
6
|
+
return RUBY_PLATFORM.match(/mswin/) ? true : false
|
7
|
+
end
|
8
|
+
|
9
|
+
def suitability(method, *args) # :nodoc:
|
10
|
+
# Must be higher than PlatformManager::Struct
|
11
|
+
return available? ? 3 : 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def _prepare
|
15
|
+
return if @struct[:release]
|
16
|
+
@struct[:os] = "windows"
|
17
|
+
@struct[:arch] = ENV["PROCESSOR_ARCHITECTURE"]
|
18
|
+
@struct[:distro] = "microsoft"
|
19
|
+
|
20
|
+
# VER values: http://www.ss64.com/nt/ver.html
|
21
|
+
ver = `ver`.strip
|
22
|
+
if match = ver.match(/Windows (\w+)/)
|
23
|
+
@struct[:release] = match[1].downcase
|
24
|
+
elsif match = ver.match(/Windows \[Version 6\.0\./)
|
25
|
+
@struct[:release] = "vista"
|
26
|
+
end
|
27
|
+
|
28
|
+
@struct
|
29
|
+
end
|
30
|
+
private :_prepare
|
31
|
+
|
32
|
+
def query(search)
|
33
|
+
_prepare
|
34
|
+
super(search)
|
35
|
+
end
|
36
|
+
|
37
|
+
def single_vendor?
|
38
|
+
return true
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module AutomateIt
|
2
|
+
class Plugin
|
3
|
+
# == Plugin::Base
|
4
|
+
#
|
5
|
+
# An AutomateIt Plugin provides the Interpreter with the functionality that
|
6
|
+
# users actually care about, such as installing packages or adding users.
|
7
|
+
# The Plugin::Base class isn't useful by itself but provides behavior
|
8
|
+
# that's inherited by the Plugin::Manager and Plugin::Driver classes.
|
9
|
+
class Base < Common
|
10
|
+
def setup(opts={}) # :nodoc:
|
11
|
+
super(opts)
|
12
|
+
@interpreter ||= opts[:interpreter] \
|
13
|
+
|| AutomateIt::Interpreter.new(:parent => self)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get token for the plugin. The token is a symbol that represents the
|
17
|
+
# classname of the underlying object.
|
18
|
+
#
|
19
|
+
# Example:
|
20
|
+
# AddressManager.token # => :address_manager
|
21
|
+
# AddressManager::Portable.token => :portable
|
22
|
+
def token
|
23
|
+
self.class.token
|
24
|
+
end
|
25
|
+
|
26
|
+
# See #token.
|
27
|
+
def self.token
|
28
|
+
return to_s.demodulize.underscore.to_sym
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
module AutomateIt
|
2
|
+
class Plugin
|
3
|
+
# == Plugin::Driver
|
4
|
+
#
|
5
|
+
# A driver provides the low-level functionality for features, e.g., the
|
6
|
+
# PackageManager::APT driver is responsible for installing a software
|
7
|
+
# package using the Debian <tt>apt-get</tt> command. Multiple drivers
|
8
|
+
# providing common functionality are managed by a single Manager class,
|
9
|
+
# e.g., drivers that install software packages are managed by the
|
10
|
+
# PackageManager.
|
11
|
+
#
|
12
|
+
# A driver may only be available on certain platforms and provides its
|
13
|
+
# manager with an idea of when it's suitable. For example, if a platform
|
14
|
+
# doesn't have the <tt>apt-get</tt> command, the PackageManager::APT driver
|
15
|
+
# must tell the PackageManager class that it's not suitable.
|
16
|
+
#
|
17
|
+
# === Writing your own drivers
|
18
|
+
#
|
19
|
+
# To write a driver, find the most similar driver available for a specific
|
20
|
+
# plugin, copy it, and rework its code. Save the code for the new driver in
|
21
|
+
# a file ending with .rb into the project’s +lib+ directory, it will be
|
22
|
+
# automatically loaded whenever the Interpreter for that project is run.
|
23
|
+
# Please test and contribute drivers so that others can benefit.
|
24
|
+
#
|
25
|
+
# IMPORTANT GOTCHA: You must prefix the AutomateIt module name with a "::"!
|
26
|
+
#
|
27
|
+
# Here's a minimalistic PackageManager that can be dropped into +lib+:
|
28
|
+
#
|
29
|
+
# class MyDriver < ::AutomateIt::PackageManager::BaseDriver
|
30
|
+
# depends_on :nothing
|
31
|
+
#
|
32
|
+
# def suitability(method, *args) # :nodoc:
|
33
|
+
# # Never select as default driver
|
34
|
+
# return 0
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
class Driver < Base
|
38
|
+
# Driver classes. Represented as a hash of manager tokens to arrays of
|
39
|
+
# their driver classes, for example:
|
40
|
+
#
|
41
|
+
# { :package_manager => [
|
42
|
+
# AutomateIt::PackageManager::APT,
|
43
|
+
# AutomateIt::PackageManager::YUM,
|
44
|
+
# ...
|
45
|
+
cattr_accessor :classes
|
46
|
+
self.classes = {}
|
47
|
+
|
48
|
+
# Returns the Plugin::Manager instance for this Driver.
|
49
|
+
attr_accessor :manager
|
50
|
+
|
51
|
+
# Setup a Driver.
|
52
|
+
#
|
53
|
+
# Options:
|
54
|
+
# * :manager -- The Plugin::Manager instance controlling this driver.
|
55
|
+
def setup(opts={})
|
56
|
+
self.manager = opts[:manager] if opts[:manager]
|
57
|
+
super(opts)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Retrieve the manager token for this driver
|
61
|
+
def self.manager_token
|
62
|
+
fragments = base_driver.to_s.split(/::/)
|
63
|
+
return fragments[fragments.size-2].underscore.to_sym
|
64
|
+
end
|
65
|
+
|
66
|
+
BASE_DRIVER_NAME = "BaseDriver"
|
67
|
+
|
68
|
+
# Is this a base driver?
|
69
|
+
def self.base_driver?
|
70
|
+
to_s =~ /::#{BASE_DRIVER_NAME}/
|
71
|
+
end
|
72
|
+
|
73
|
+
# Retrieve the base driver class for this driver.
|
74
|
+
def self.base_driver
|
75
|
+
ancestors.select{|t| t.to_s =~ /::#{BASE_DRIVER_NAME}/}.last
|
76
|
+
end
|
77
|
+
|
78
|
+
# Register drivers. Concrete drivers are added to a class-wide data
|
79
|
+
# structure which maps them to the manager they belong to.
|
80
|
+
def self.inherited(subclass) # :nodoc:
|
81
|
+
base_driver = subclass.base_driver
|
82
|
+
if subclass.base_driver?
|
83
|
+
# Ignore, base drivers should never be registered
|
84
|
+
elsif base_driver
|
85
|
+
manager_token = subclass.manager_token
|
86
|
+
classes[manager_token] ||= []
|
87
|
+
|
88
|
+
unless classes[manager_token].include?(subclass)
|
89
|
+
classes[manager_token] << subclass
|
90
|
+
end
|
91
|
+
|
92
|
+
### puts "manager_token %s / driver %s" % [manager_token, subclass]
|
93
|
+
else
|
94
|
+
# XXX Should this really raise an exception?
|
95
|
+
raise TypeError.new("Can't determine manager for driver: #{subclass}")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Declare that this driver class is abstract. It can be subclassed but
|
100
|
+
# will not be instantiated by the Interpreter's Managers. BaseDriver
|
101
|
+
# classes are automatically declared abstract.
|
102
|
+
def self.abstract_driver
|
103
|
+
if base_driver?
|
104
|
+
# Ignore, base drivers should never have been registered
|
105
|
+
elsif manager = manager_token
|
106
|
+
classes[manager].delete(self)
|
107
|
+
else
|
108
|
+
raise TypeError.new("Can't find manager for abstract plugin: #{self}")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Defines resources this driver depends on.
|
113
|
+
#
|
114
|
+
# Options:
|
115
|
+
# * :files -- Array of filenames that must exist.
|
116
|
+
# * :directories -- Array of directories that must exist.
|
117
|
+
# * :programs -- Array of programs, checked with +which+, that must exist.
|
118
|
+
# * :callbacks -- Array of lambdas that must return true.
|
119
|
+
# * :libraries -- Array of libraries to +require+.
|
120
|
+
#
|
121
|
+
# Example:
|
122
|
+
# class APT < Plugin::Driver
|
123
|
+
# depends_on :programs => %w(apt-get dpkg)
|
124
|
+
# # ...
|
125
|
+
# end
|
126
|
+
def self.depends_on(opts)
|
127
|
+
meta_eval do
|
128
|
+
attr_accessor :_depends_on_opts, :_is_available, :_missing_dependencies
|
129
|
+
end
|
130
|
+
self._depends_on_opts = opts
|
131
|
+
end
|
132
|
+
|
133
|
+
# Is this driver available on this platform? Queries the dependencies set
|
134
|
+
# by #depends_on to make sure that they're all present, otherwise raises
|
135
|
+
# a NotImplementedError. If a driver author needs to do some other kind
|
136
|
+
# of check, it's reasonable to override this method.
|
137
|
+
#
|
138
|
+
# For example, this method is used by the PackageManager driver for APT
|
139
|
+
# to determine if the "apt-get" program is installed.
|
140
|
+
#
|
141
|
+
# What's the difference between #available? and #suitability? The
|
142
|
+
# #available? method is used to determine if the driver *can* do the
|
143
|
+
# work, while the #suitability method determines if the driver *should*
|
144
|
+
# be automatically selected.
|
145
|
+
def available?
|
146
|
+
# Some drivers don't run +depends_on+, so assume they're available.
|
147
|
+
return true unless self.class.respond_to?(:_depends_on_opts)
|
148
|
+
opts = self.class._depends_on_opts
|
149
|
+
|
150
|
+
# Driver said that it +depends_on :nothing+, so it's available.
|
151
|
+
return true if opts == :nothing
|
152
|
+
|
153
|
+
is_available = self.class._is_available
|
154
|
+
if is_available.nil? and opts.nil?
|
155
|
+
#log.debug(PNOTE+"don't know if driver #{self.class} is available, maybe it doesn't state what it +depends_on+")
|
156
|
+
return false
|
157
|
+
elsif is_available.nil?
|
158
|
+
all_present = true
|
159
|
+
missing = {}
|
160
|
+
|
161
|
+
# Check callbacks last
|
162
|
+
kinds = opts.keys
|
163
|
+
callbacks = kinds.delete(:callbacks)
|
164
|
+
kinds << callbacks if callbacks
|
165
|
+
|
166
|
+
begin
|
167
|
+
for kind in kinds
|
168
|
+
next unless opts[kind]
|
169
|
+
for item in [opts[kind]].flatten
|
170
|
+
present = \
|
171
|
+
case kind
|
172
|
+
when :files
|
173
|
+
File.exists?(item)
|
174
|
+
when :directories
|
175
|
+
File.directory?(item)
|
176
|
+
when :programs
|
177
|
+
# TODO Driver#available? - Find better way to locate the platform's #which driver and use it to check if a program exists. This is tricky because this #available? method is the one that handles detection, yet we have to bypass it.
|
178
|
+
result = nil
|
179
|
+
for variant in %w(unix windows)
|
180
|
+
variant_token = "which_#{variant}".to_sym
|
181
|
+
begin
|
182
|
+
driver = interpreter.shell_manager[variant_token]
|
183
|
+
result = driver.which!(item)
|
184
|
+
### puts "%s : %s for %s" % [variant, result, item]
|
185
|
+
break
|
186
|
+
rescue ArgumentError, NotImplementedError, NoMethodError => e
|
187
|
+
# Exceptions are expected, only print for debugging
|
188
|
+
### puts e.inspect
|
189
|
+
end
|
190
|
+
end
|
191
|
+
result
|
192
|
+
when :requires, :libraries
|
193
|
+
begin
|
194
|
+
require item
|
195
|
+
true
|
196
|
+
rescue LoadError
|
197
|
+
false
|
198
|
+
end
|
199
|
+
when :callbacks
|
200
|
+
item.call() ? true : false
|
201
|
+
else
|
202
|
+
raise TypeError.new("Unknown kind: #{kind}")
|
203
|
+
end
|
204
|
+
unless present
|
205
|
+
all_present = false
|
206
|
+
missing[kind] ||= []
|
207
|
+
missing[kind] << item
|
208
|
+
|
209
|
+
# Do not continue scanning if dependency is missed
|
210
|
+
raise EOFError.new("break")
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
rescue EOFError => e
|
215
|
+
# Ignore expected "break" warning
|
216
|
+
raise e unless e.message == "break"
|
217
|
+
end
|
218
|
+
self.class._missing_dependencies = missing
|
219
|
+
self.class._is_available = all_present
|
220
|
+
log.debug(PNOTE+"Driver #{self.class} #{all_present ? "is" : "isn't"} available")
|
221
|
+
end
|
222
|
+
return self.class._is_available
|
223
|
+
end
|
224
|
+
|
225
|
+
# Raise a NotImplementedError if this driver is called but is not
|
226
|
+
# #available?.
|
227
|
+
def _raise_unless_available
|
228
|
+
unless available?
|
229
|
+
msg = ""
|
230
|
+
for kind, elements in self.class._missing_dependencies
|
231
|
+
msg << "; " unless msg == ""
|
232
|
+
msg << "%s: %s" % [kind, elements.sort.join(', ')]
|
233
|
+
end
|
234
|
+
raise NotImplementedError.new("Missing dependencies -- %s" % msg)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
protected :_raise_unless_available
|
238
|
+
|
239
|
+
# What is this driver's suitability for automatic detection? The Manager
|
240
|
+
# queries its drivers when there isn't a driver specified with a
|
241
|
+
# <tt>:with</tt> or #default so it can choose a suitable driver for the
|
242
|
+
# +method+ and +args+. Any driver that returns an integer 1 or greater
|
243
|
+
# claims to be suitable. The Manager will then select the driver with the
|
244
|
+
# highest suitability level. Drivers that return an integer less than 1
|
245
|
+
# are excluded from automatic detection.
|
246
|
+
#
|
247
|
+
# The <tt>available?</tt> method is used to determine if the driver can
|
248
|
+
# do the work, while the <tt>suitability</tt> method determines if the
|
249
|
+
# driver should be automatically selected.
|
250
|
+
def suitability(method, *args, &block)
|
251
|
+
log.debug("driver #{self.class} doesn't implement the +suitability+ method")
|
252
|
+
return -1
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
module AutomateIt
|
2
|
+
class Plugin
|
3
|
+
# == Plugin::Manager
|
4
|
+
#
|
5
|
+
# A manager provides high-level wrappers for features, e.g.
|
6
|
+
# installing software packages. It does not actually implement these
|
7
|
+
# features, but instead manages a set of drivers. When one of the manager's
|
8
|
+
# wrapper methods is called, it queries the drivers to find the most
|
9
|
+
# suitable one and dispatches the user's request to that driver.
|
10
|
+
#
|
11
|
+
# For example, the PlatformManager is a Manager class that manages a set of
|
12
|
+
# Driver instances, including PlatformManager::Uname and
|
13
|
+
# PlatformManager::LSB. When you invoke the high-level
|
14
|
+
# PlatformManager#query wrapper method, it interrogates the drivers to find
|
15
|
+
# which one is best-suited for this method and then dispatches the request
|
16
|
+
# to that driver's low-level implementation of this method.
|
17
|
+
#
|
18
|
+
# The manager subclasses typically have no functionality of their own and
|
19
|
+
# just contain wrapper methods and documentation.
|
20
|
+
class Manager < Base
|
21
|
+
# Array of all managers.
|
22
|
+
cattr_accessor :classes
|
23
|
+
self.classes = []
|
24
|
+
|
25
|
+
# Register managers.
|
26
|
+
def self.inherited(subclass) # :nodoc:
|
27
|
+
classes << subclass unless classes.include?(subclass)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Declare that this manager class is abstract. It can be subclassed but
|
31
|
+
# will not be instantiated by the Interpreter.
|
32
|
+
def self.abstract_manager
|
33
|
+
classes.delete(self)
|
34
|
+
end
|
35
|
+
|
36
|
+
# List of aliased methods for this manager, populated by ::alias_methods.
|
37
|
+
class_inheritable_accessor :aliased_methods
|
38
|
+
|
39
|
+
# Methods to alias into the Interpreter, specified as an array of symbols.
|
40
|
+
def self.alias_methods(*methods)
|
41
|
+
self.aliased_methods ||= Set.new
|
42
|
+
self.aliased_methods.merge(methods.flatten)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Drivers for this manager as a hash of driver tokens to driver
|
46
|
+
# instances.
|
47
|
+
attr_accessor :drivers
|
48
|
+
|
49
|
+
# Driver classes used by this Manager.
|
50
|
+
def self.driver_classes
|
51
|
+
Driver.classes[token] || []
|
52
|
+
end
|
53
|
+
|
54
|
+
# Options:
|
55
|
+
# * :default -- The token of the driver to set as the default.
|
56
|
+
def setup(opts={})
|
57
|
+
super(opts)
|
58
|
+
|
59
|
+
@default ||= nil
|
60
|
+
|
61
|
+
instantiate_drivers
|
62
|
+
|
63
|
+
if driver = opts[:default] || opts[:driver]
|
64
|
+
default(opts[:default]) if opts[:default]
|
65
|
+
@drivers[driver].setup(opts)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Instantiate drivers for this manager. This method is smart enough that
|
70
|
+
# it can be called multiple times and will only instantiate drivers it
|
71
|
+
# hasn't instantiated yet. All drivers will share an instance of the
|
72
|
+
# Interpreter, thus providing common state storage.
|
73
|
+
def instantiate_drivers
|
74
|
+
@drivers ||= {}
|
75
|
+
self.class.driver_classes.each do |driver_class|
|
76
|
+
driver_token = driver_class.token
|
77
|
+
unless @drivers[driver_token]
|
78
|
+
@drivers[driver_token] = driver_class.allocate
|
79
|
+
end
|
80
|
+
end
|
81
|
+
self.class.driver_classes.each do |driver_class|
|
82
|
+
driver_token = driver_class.token
|
83
|
+
@drivers[driver_token].setup(
|
84
|
+
:interpreter => @interpreter, :manager => self)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns the Driver with the specified token. E.g., +:apt+ will return
|
89
|
+
# the +APT+ driver.
|
90
|
+
def [](token)
|
91
|
+
return @drivers[token]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Manipulate the default driver. Without arguments, gets the driver token
|
95
|
+
# as a symbol. With argument, sets the default driver to the +token+,
|
96
|
+
# e.g., the argument <tt>:apt</tt> will make the +APT+ driver the default.
|
97
|
+
def default(token=nil)
|
98
|
+
if token.nil?
|
99
|
+
@default
|
100
|
+
else
|
101
|
+
@default = token
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Set the default driver to the +token+. See also #default.
|
106
|
+
def default=(token)
|
107
|
+
default(token)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Dispatch a method by guessing its name. This is the recommended way to
|
111
|
+
# write wrappers for a Manager methods.
|
112
|
+
#
|
113
|
+
# Example:
|
114
|
+
# class MyManager < Plugin::Manager
|
115
|
+
# # Your RDoc here
|
116
|
+
# def my_method(*args)
|
117
|
+
# # Will guess that you want to +dispatch_to+ the +my_method+ by
|
118
|
+
# # introspecting the name of the wrapper method.
|
119
|
+
# dispatch(*args)
|
120
|
+
# end
|
121
|
+
# ...
|
122
|
+
# end
|
123
|
+
def dispatch(*args, &block)
|
124
|
+
# Extract caller's method as symbol to save user from having to specify it
|
125
|
+
method = caller[0].match(/:in `(.+?)'/)[1].to_sym
|
126
|
+
dispatch_to(method, *args, &block)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Dispatch the +method+ with +args+ and +block+ to the appropriate
|
130
|
+
# driver. If the arguments include an option of the form <tt>:with =>
|
131
|
+
# :mytoken</tt> argument, then the method will be dispatched to the
|
132
|
+
# driver represented by <tt>:mytoken</tt>. If no :with argument is
|
133
|
+
# specified, the most-suitable driver will be automatically selected. If
|
134
|
+
# no suitable driver is available, a NotImplementedError will be raised.
|
135
|
+
#
|
136
|
+
# Examples:
|
137
|
+
# # Run 'hostnames' method on most appropriate AddressManager driver:
|
138
|
+
# address_manager.dispatch_to(:hostnames)
|
139
|
+
#
|
140
|
+
# # Run AddressManager::Portable#hostnames
|
141
|
+
# address_manager.dispatch_to(:hostnames, :with => :portable)
|
142
|
+
#
|
143
|
+
# You will typically not want to use this method directly and instead
|
144
|
+
# write wrappers that call #dispatch because it can guess the name of
|
145
|
+
# the +method+ argument for you.
|
146
|
+
def dispatch_to(method, *args, &block)
|
147
|
+
list, options = args_and_opts(*args)
|
148
|
+
driver = \
|
149
|
+
if options and options[:with]
|
150
|
+
@drivers[options[:with].to_sym]
|
151
|
+
elsif default
|
152
|
+
@drivers[default.to_sym]
|
153
|
+
else
|
154
|
+
driver_for(method, *args, &block)
|
155
|
+
end
|
156
|
+
driver.send(method, *args, &block)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Same as #dispatch_to but returns nil if couldn't dispatch, rather than
|
160
|
+
# raising an exception.
|
161
|
+
def dispatch_safely_to(method, *args, &block)
|
162
|
+
begin
|
163
|
+
dispatch_to(method, *args, &block)
|
164
|
+
rescue NotImplementedError
|
165
|
+
nil
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Same as #dispatch but returns nil if couldn't dispatch, rather than
|
170
|
+
# raising an exception.
|
171
|
+
def dispatch_safely(*args, &block)
|
172
|
+
method = caller[0].match(/:in `(.+?)'/)[1].to_sym
|
173
|
+
dispatch_safely_to(method, *args, &block)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Returns structure which helps choose a suitable driver for the +method+
|
177
|
+
# and +args+. Result is a hash of driver tokens and their suitability
|
178
|
+
# levels.
|
179
|
+
#
|
180
|
+
# For example, if we ask the AddressManager for suitability levels for
|
181
|
+
# the AddressManager#hostnames method, we might find that there are two
|
182
|
+
# drivers (:portable is the token for AddressManager::Portable) and that
|
183
|
+
# the :linux driver is most appropriate because it has the highest
|
184
|
+
# suitability level:
|
185
|
+
#
|
186
|
+
# address_manager.driver_suitability_levels_for(:hostnames)
|
187
|
+
# # => {:portable=>1, :linux=>2}
|
188
|
+
def driver_suitability_levels_for(method, *args, &block)
|
189
|
+
results = {}
|
190
|
+
@drivers.each_pair do |name, driver|
|
191
|
+
next unless driver.respond_to?(method)
|
192
|
+
results[name] = driver.suitability(method, *args, &block)
|
193
|
+
end
|
194
|
+
return results
|
195
|
+
end
|
196
|
+
|
197
|
+
# Get the most suitable driver for this +method+ and +args+. Uses
|
198
|
+
# automatic-detection routines and returns the most suitable driver
|
199
|
+
# instance found, else raises a NotImplementedError if no suitable driver
|
200
|
+
# is found.
|
201
|
+
def driver_for(method, *args, &block)
|
202
|
+
driver, level = driver_suitability_levels_for(method, *args, &block).sort_by{|k,v| v}.last
|
203
|
+
if driver and level > 0
|
204
|
+
return @drivers[driver]
|
205
|
+
else
|
206
|
+
raise NotImplementedError.new("can't find driver for method '#{method}' with arguments: #{args.inspect}")
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Is a driver available for this +method+ and +args+? Uses
|
211
|
+
# automatic-detection routines and returns a boolean to indicate if a
|
212
|
+
# suitable driver is available. Unlike #driver_for, this will not raise
|
213
|
+
# an exception.
|
214
|
+
def available?(method, *args, &block)
|
215
|
+
begin
|
216
|
+
driver_for(method, *args, &block)
|
217
|
+
true
|
218
|
+
rescue NotImplementedError
|
219
|
+
false
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|