powcloud-sprinkle 0.3.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.
Files changed (106) hide show
  1. data/.gitignore +5 -0
  2. data/CREDITS +33 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.markdown +241 -0
  5. data/Rakefile +50 -0
  6. data/VERSION +1 -0
  7. data/bin/sprinkle +90 -0
  8. data/examples/packages/build_essential.rb +9 -0
  9. data/examples/packages/databases/mysql.rb +13 -0
  10. data/examples/packages/databases/sqlite3.rb +16 -0
  11. data/examples/packages/phusion.rb +55 -0
  12. data/examples/packages/ruby/rails.rb +9 -0
  13. data/examples/packages/ruby/ruby.rb +17 -0
  14. data/examples/packages/ruby/rubygems.rb +17 -0
  15. data/examples/packages/scm/git.rb +11 -0
  16. data/examples/packages/scm/subversion.rb +4 -0
  17. data/examples/packages/servers/apache.rb +15 -0
  18. data/examples/rails/README +15 -0
  19. data/examples/rails/deploy.rb +2 -0
  20. data/examples/rails/packages/database.rb +9 -0
  21. data/examples/rails/packages/essential.rb +9 -0
  22. data/examples/rails/packages/rails.rb +29 -0
  23. data/examples/rails/packages/scm.rb +11 -0
  24. data/examples/rails/packages/search.rb +11 -0
  25. data/examples/rails/packages/server.rb +28 -0
  26. data/examples/rails/rails.rb +73 -0
  27. data/examples/sprinkle/sprinkle.rb +38 -0
  28. data/lib/sprinkle/actors/actors.rb +17 -0
  29. data/lib/sprinkle/actors/capistrano.rb +140 -0
  30. data/lib/sprinkle/actors/local.rb +37 -0
  31. data/lib/sprinkle/actors/ssh.rb +123 -0
  32. data/lib/sprinkle/actors/vlad.rb +78 -0
  33. data/lib/sprinkle/configurable.rb +31 -0
  34. data/lib/sprinkle/deployment.rb +73 -0
  35. data/lib/sprinkle/extensions/arbitrary_options.rb +10 -0
  36. data/lib/sprinkle/extensions/array.rb +5 -0
  37. data/lib/sprinkle/extensions/blank_slate.rb +5 -0
  38. data/lib/sprinkle/extensions/dsl_accessor.rb +15 -0
  39. data/lib/sprinkle/extensions/string.rb +10 -0
  40. data/lib/sprinkle/extensions/symbol.rb +7 -0
  41. data/lib/sprinkle/installers/apt.rb +52 -0
  42. data/lib/sprinkle/installers/binary.rb +46 -0
  43. data/lib/sprinkle/installers/bsd_port.rb +33 -0
  44. data/lib/sprinkle/installers/deb.rb +38 -0
  45. data/lib/sprinkle/installers/freebsd_pkg.rb +37 -0
  46. data/lib/sprinkle/installers/freebsd_portinstall.rb +36 -0
  47. data/lib/sprinkle/installers/gem.rb +64 -0
  48. data/lib/sprinkle/installers/install_package.rb +79 -0
  49. data/lib/sprinkle/installers/installer.rb +120 -0
  50. data/lib/sprinkle/installers/mac_port.rb +38 -0
  51. data/lib/sprinkle/installers/noop.rb +20 -0
  52. data/lib/sprinkle/installers/openbsd_pkg.rb +47 -0
  53. data/lib/sprinkle/installers/opensolaris_pkg.rb +43 -0
  54. data/lib/sprinkle/installers/push_text.rb +45 -0
  55. data/lib/sprinkle/installers/rake.rb +37 -0
  56. data/lib/sprinkle/installers/rpm.rb +37 -0
  57. data/lib/sprinkle/installers/smart.rb +29 -0
  58. data/lib/sprinkle/installers/source.rb +190 -0
  59. data/lib/sprinkle/installers/transfer.rb +164 -0
  60. data/lib/sprinkle/installers/yum.rb +37 -0
  61. data/lib/sprinkle/package.rb +309 -0
  62. data/lib/sprinkle/policy.rb +125 -0
  63. data/lib/sprinkle/script.rb +23 -0
  64. data/lib/sprinkle/verifiers/directory.rb +16 -0
  65. data/lib/sprinkle/verifiers/executable.rb +53 -0
  66. data/lib/sprinkle/verifiers/file.rb +26 -0
  67. data/lib/sprinkle/verifiers/package.rb +26 -0
  68. data/lib/sprinkle/verifiers/process.rb +21 -0
  69. data/lib/sprinkle/verifiers/rpm.rb +21 -0
  70. data/lib/sprinkle/verifiers/ruby.rb +25 -0
  71. data/lib/sprinkle/verifiers/symlink.rb +30 -0
  72. data/lib/sprinkle/verify.rb +114 -0
  73. data/lib/sprinkle.rb +32 -0
  74. data/script/console +8 -0
  75. data/script/destroy +14 -0
  76. data/script/generate +14 -0
  77. data/spec/spec.opts +1 -0
  78. data/spec/spec_helper.rb +17 -0
  79. data/spec/sprinkle/actors/capistrano_spec.rb +265 -0
  80. data/spec/sprinkle/actors/local_spec.rb +29 -0
  81. data/spec/sprinkle/configurable_spec.rb +46 -0
  82. data/spec/sprinkle/deployment_spec.rb +80 -0
  83. data/spec/sprinkle/extensions/array_spec.rb +19 -0
  84. data/spec/sprinkle/extensions/string_spec.rb +21 -0
  85. data/spec/sprinkle/installers/apt_spec.rb +70 -0
  86. data/spec/sprinkle/installers/bsd_port_spec.rb +42 -0
  87. data/spec/sprinkle/installers/freebsd_pkg_spec.rb +49 -0
  88. data/spec/sprinkle/installers/freebsd_portinstall_spec.rb +42 -0
  89. data/spec/sprinkle/installers/gem_spec.rb +107 -0
  90. data/spec/sprinkle/installers/installer_spec.rb +151 -0
  91. data/spec/sprinkle/installers/mac_port_spec.rb +42 -0
  92. data/spec/sprinkle/installers/noop_spec.rb +23 -0
  93. data/spec/sprinkle/installers/openbsd_pkg_spec.rb +49 -0
  94. data/spec/sprinkle/installers/opensolaris_pkg_spec.rb +49 -0
  95. data/spec/sprinkle/installers/push_text_spec.rb +66 -0
  96. data/spec/sprinkle/installers/rake_spec.rb +29 -0
  97. data/spec/sprinkle/installers/rpm_spec.rb +50 -0
  98. data/spec/sprinkle/installers/source_spec.rb +371 -0
  99. data/spec/sprinkle/installers/transfer_spec.rb +98 -0
  100. data/spec/sprinkle/installers/yum_spec.rb +49 -0
  101. data/spec/sprinkle/package_spec.rb +466 -0
  102. data/spec/sprinkle/policy_spec.rb +126 -0
  103. data/spec/sprinkle/script_spec.rb +51 -0
  104. data/spec/sprinkle/sprinkle_spec.rb +25 -0
  105. data/spec/sprinkle/verify_spec.rb +174 -0
  106. metadata +244 -0
@@ -0,0 +1,164 @@
1
+ # Blatantly stole this from Chef
2
+ class TemplateError < RuntimeError
3
+ attr_reader :original_exception, :context
4
+ SOURCE_CONTEXT_WINDOW = 2 unless defined? SOURCE_CONTEXT_WINDOW
5
+
6
+ def initialize(original_exception, template, context)
7
+ @original_exception, @template, @context = original_exception, template, context
8
+ end
9
+
10
+ def message
11
+ @original_exception.message
12
+ end
13
+
14
+ def line_number
15
+ @line_number ||= $1.to_i if original_exception.backtrace.find {|line| line =~ /\(erubis\):(\d+)/ }
16
+ end
17
+
18
+ def source_location
19
+ "on line ##{line_number}"
20
+ end
21
+
22
+ def source_listing
23
+ return nil if line_number.nil?
24
+
25
+ @source_listing ||= begin
26
+ line_index = line_number - 1
27
+ beginning_line = line_index <= SOURCE_CONTEXT_WINDOW ? 0 : line_index - SOURCE_CONTEXT_WINDOW
28
+ source_size = SOURCE_CONTEXT_WINDOW * 2 + 1
29
+ lines = @template.split(/\n/)
30
+ contextual_lines = lines[beginning_line, source_size]
31
+ output = []
32
+ contextual_lines.each_with_index do |line, index|
33
+ line_number = (index+beginning_line+1).to_s.rjust(3)
34
+ output << "#{line_number}: #{line}"
35
+ end
36
+ output.join("\n")
37
+ end
38
+ end
39
+
40
+ def to_s
41
+ "\n\n#{self.class} (#{message}) #{source_location}:\n\n" +
42
+ "#{source_listing}\n\n #{original_exception.backtrace.join("\n ")}\n\n"
43
+ end
44
+ end
45
+
46
+ module Sprinkle
47
+ module Installers
48
+ # Beware, another strange "installer" coming your way.
49
+ #
50
+ # = File transfer installer
51
+ #
52
+ # This installer pushes files from the local disk to remote servers.
53
+ #
54
+ # == Example Usage
55
+ #
56
+ # Installing a nginx.conf onto remote servers
57
+ #
58
+ # package :nginx_conf do
59
+ # transfer 'files/nginx.conf', '/etc/nginx.conf'
60
+ # end
61
+ #
62
+ # If you user has access to 'sudo' and theres a file that requires
63
+ # priveledges, you can pass :sudo => true
64
+ #
65
+ # package :nginx_conf do
66
+ # transfer 'files/nginx.conf', '/etc/nginx.conf', :sudo => true
67
+ # end
68
+ #
69
+ # By default, transfers are recursive and you can move whole directories
70
+ # via this method. If you wish to disable recursive transfers, you can pass
71
+ # recursive => false, although it will not be obeyed when using the Vlad actor.
72
+ #
73
+ # If you pass the option :render => true, this tells transfer that the source file
74
+ # is an ERB template to be rendered locally before being transferred (you can declare
75
+ # variables in the package scope). When render is true, recursive is turned off.
76
+ #
77
+ # package :nginx_conf do
78
+ # nginx_port = 8080
79
+ # transfer 'files/nginx.conf', '/etc/nginx.conf', :render => true
80
+ # end
81
+ #
82
+ # Finally, should you need to run commands before or after the file transfer (making
83
+ # directories or changing permissions), you can use the pre/post :install directives
84
+ # and they will be run.
85
+ class Transfer < Installer
86
+ attr_accessor :source, :destination #:nodoc:
87
+
88
+ def initialize(parent, source, destination, options={}, &block) #:nodoc:
89
+ super parent, options, &block
90
+ @source = source
91
+ @destination = destination
92
+ end
93
+
94
+ def install_commands
95
+ nil
96
+ end
97
+
98
+ def self.render_template(template, context, prefix)
99
+ require 'tempfile'
100
+ require 'erubis'
101
+
102
+ begin
103
+ eruby = Erubis::Eruby.new(template)
104
+ output = eruby.result(context)
105
+ rescue Object => e
106
+ raise TemplateError.new(e, template, context)
107
+ end
108
+
109
+ final_tempfile = Tempfile.new(prefix)
110
+ final_tempfile.print(output)
111
+ final_tempfile.close
112
+ final_tempfile
113
+ end
114
+
115
+ def render_template(template, context, prefix)
116
+ self.class.render_template(template, context, prefix)
117
+ end
118
+
119
+ def render_template_file(path, context, prefix)
120
+ template = File.read(path)
121
+ tempfile = render_template(template, binding(), @package.name)
122
+ tempfile
123
+ end
124
+
125
+ def process(roles) #:nodoc:
126
+ assert_delivery
127
+
128
+ if logger.debug?
129
+ logger.debug "transfer: #{@source} -> #{@destination} for roles: #{roles}\n"
130
+ end
131
+
132
+ unless Sprinkle::OPTIONS[:testing]
133
+ pre = pre_commands(:install)
134
+ unless pre.empty?
135
+ sequence = pre; sequence = sequence.join('; ') if sequence.is_a? Array
136
+ logger.info "#{@package.name} pre-transfer commands: #{sequence} for roles: #{roles}\n"
137
+ @delivery.process @package.name, sequence, roles
138
+ end
139
+
140
+ recursive = @options[:recursive]
141
+
142
+ if options[:render]
143
+ tempfile = render_template_file(@source, binding(), @package.name)
144
+ sourcepath = tempfile.path
145
+ logger.info "Rendering template #{@source} to temporary file #{sourcepath}"
146
+ recursive = false
147
+ else
148
+ sourcepath = @source
149
+ end
150
+
151
+ logger.info "--> Transferring #{sourcepath} to #{@destination} for roles: #{roles}"
152
+ @delivery.transfer(@package.name, sourcepath, @destination, roles, recursive)
153
+
154
+ post = post_commands(:install)
155
+ unless post.empty?
156
+ sequence = post; sequence = sequence.join('; ') if sequence.is_a? Array
157
+ logger.info "#{@package.name} post-transfer commands: #{sequence} for roles: #{roles}\n"
158
+ @delivery.process @package.name, sequence, roles
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,37 @@
1
+ module Sprinkle
2
+ module Installers
3
+ # = Yum Package Installer
4
+ #
5
+ # The Yum package installer installs RPM packages.
6
+ #
7
+ # == Example Usage
8
+ #
9
+ # Installing the magic_beans RPM via Yum. Its all the craze these days.
10
+ #
11
+ # package :magic_beans do
12
+ # yum 'magic_beans'
13
+ # end
14
+ #
15
+ # You may also specify multiple rpms as an array:
16
+ #
17
+ # package :magic_beans do
18
+ # yum %w(magic_beans magic_sauce)
19
+ # end
20
+ class Yum < Installer
21
+ attr_accessor :packages #:nodoc:
22
+
23
+ def initialize(parent, packages, &block) #:nodoc:
24
+ super parent, &block
25
+ packages = [packages] unless packages.is_a? Array
26
+ @packages = packages
27
+ end
28
+
29
+ protected
30
+
31
+ def install_commands #:nodoc:
32
+ "yum install #{@packages.join(' ')} -y"
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,309 @@
1
+ module Sprinkle
2
+ # = Packages
3
+ #
4
+ # A package defines one or more things to provision onto the server.
5
+ # There is a lot of flexibility in a way a package is defined but
6
+ # let me give you a basic example:
7
+ #
8
+ # package :ruby do
9
+ # description 'Ruby MRI'
10
+ # version '1.8.6'
11
+ # apt 'ruby'
12
+ #
13
+ # verify { has_executable 'ruby' }
14
+ # end
15
+ #
16
+ # The above would define a package named 'ruby' and give it a description
17
+ # and explicitly say its version. It is installed via apt and to verify
18
+ # the installation was successful sprinkle will check for the executable
19
+ # 'ruby' being availble. Pretty simple, right?
20
+ #
21
+ # <b>Note:</b> Defining a package does not INSTALL it. To install a
22
+ # package, you must require it in a Sprinkle::Policy block.
23
+ #
24
+ # == Pre-Requirements
25
+ #
26
+ # Most packages have some sort of pre-requisites in order to be installed.
27
+ # Sprinkle allows you to define the requirements of the package, which
28
+ # will be installed before the package itself. An example below:
29
+ #
30
+ # package :rubygems do
31
+ # source 'http://rubyforge.org/rubygems.tgz'
32
+ # requires :ruby
33
+ # end
34
+ #
35
+ # In this case, when rubygems is being installed, Sprinkle will first
36
+ # provision the server with Ruby to make sure the requirements are met.
37
+ # In turn, if ruby has requirements, it installs those first, and so on.
38
+ #
39
+ # == Verifications
40
+ #
41
+ # Most of the time its important to know whether the software you're
42
+ # attempting to install was installed successfully or not. For this,
43
+ # Sprinkle provides verifications. Verifications are one or more blocks
44
+ # which define rules with which Sprinkle can check if it installed
45
+ # the package successfully. If these verification blocks fail, then
46
+ # Sprinkle will gracefully stop the entire process. An example below:
47
+ #
48
+ # package :rubygems do
49
+ # source 'http://rubyforge.org/rubygems.tgz'
50
+ # requires :ruby
51
+ #
52
+ # verify { has_executable 'gem' }
53
+ # end
54
+ #
55
+ # In addition to verifying an installation was successfully, by default
56
+ # Sprinkle runs these verifications <em>before</em> the installation to
57
+ # check if the package is already installed. If the verifications pass
58
+ # before installing the package, it skips the package. To override this
59
+ # behavior, set the -f flag on the sprinkle script or set the
60
+ # :force option to true in Sprinkle::OPTIONS
61
+ #
62
+ # For more information on verifications and to see all the available
63
+ # verifications, see Sprinkle::Verify
64
+ #
65
+ # == Virtual Packages
66
+ #
67
+ # Sometimes, there are multiple packages available for a single task. An
68
+ # example is a database package. It can contain mySQL, postgres, or sqlite!
69
+ # This is where virtual packages come in handy. They are defined as follows:
70
+ #
71
+ # package :sqlite3, :provides => :database do
72
+ # apt 'sqlite3'
73
+ # end
74
+ #
75
+ # The :provides option allows you to reference this package either by :sqlite3
76
+ # or by :database. But whereas the package name is unique, multiple packages may
77
+ # share the same provision. If this is the case, when running Sprinkle, the
78
+ # script will ask you which provision you want to install. At this time, you
79
+ # can only install one.
80
+ #
81
+ # == Meta-Packages
82
+ #
83
+ # A package doesn't require an installer. If you want to define a package which
84
+ # merely encompasses other packages, that is fine too. Example:
85
+ #
86
+ # package :meta do
87
+ # requires :magic_beans
88
+ # requires :magic_sauce
89
+ # end
90
+ #
91
+ #--
92
+ # FIXME: Should probably document recommendations.
93
+ #++
94
+ module Package
95
+ PACKAGES = {}
96
+
97
+ def package(name, metadata = {}, &block)
98
+ package = Package.new(name, metadata, &block)
99
+ PACKAGES[name] = package
100
+
101
+ if package.provides
102
+ (PACKAGES[package.provides] ||= []) << package
103
+ end
104
+
105
+ package
106
+ end
107
+
108
+ class Package #:nodoc:
109
+ include ArbitraryOptions
110
+ attr_accessor :name, :provides, :installers, :dependencies, :recommends, :verifications
111
+
112
+ def initialize(name, metadata = {}, &block)
113
+ raise 'No package name supplied' unless name
114
+
115
+ @name = name
116
+ @provides = metadata[:provides]
117
+ @dependencies = []
118
+ @recommends = []
119
+ @optional = []
120
+ @verifications = []
121
+ @installers = []
122
+ self.instance_eval &block
123
+ end
124
+
125
+ def freebsd_pkg(*names, &block)
126
+ @installers << Sprinkle::Installers::FreebsdPkg.new(self, *names, &block)
127
+ end
128
+
129
+ def freebsd_portinstall(port, &block)
130
+ @installers << Sprinkle::Installers::FreebsdPortinstall.new(self, port, &block)
131
+ end
132
+
133
+ def openbsd_pkg(*names, &block)
134
+ @installers << Sprinkle::Installers::OpenbsdPkg.new(self, *names, &block)
135
+ end
136
+
137
+ def opensolaris_pkg(*names, &block)
138
+ @installers << Sprinkle::Installers::OpensolarisPkg.new(self, *names, &block)
139
+ end
140
+
141
+ def bsd_port(port, &block)
142
+ @installers << Sprinkle::Installers::BsdPort.new(self, port, &block)
143
+ end
144
+
145
+ def mac_port(port, &block)
146
+ @installers << Sprinkle::Installers::MacPort.new(self, port, &block)
147
+ end
148
+
149
+ def apt(*names, &block)
150
+ @installers << Sprinkle::Installers::Apt.new(self, *names, &block)
151
+ end
152
+
153
+ def deb(*names, &block)
154
+ @installers << Sprinkle::Installers::Deb.new(self, *names, &block)
155
+ end
156
+
157
+ def rpm(*names, &block)
158
+ @installers << Sprinkle::Installers::Rpm.new(self, *names, &block)
159
+ end
160
+
161
+ def yum(*names, &block)
162
+ @installers << Sprinkle::Installers::Yum.new(self, *names, &block)
163
+ end
164
+
165
+ def gem(name, options = {}, &block)
166
+ @recommends << :rubygems
167
+ @installers << Sprinkle::Installers::Gem.new(self, name, options, &block)
168
+ end
169
+
170
+ def source(source, options = {}, &block)
171
+ @recommends << :build_essential # Ubuntu/Debian
172
+ @installers << Sprinkle::Installers::Source.new(self, source, options, &block)
173
+ end
174
+
175
+ def binary(source, options = {}, &block)
176
+ @installers << Sprinkle::Installers::Binary.new(self, source, options, &block)
177
+ end
178
+
179
+ def rake(name, options = {}, &block)
180
+ @installers << Sprinkle::Installers::Rake.new(self, name, options, &block)
181
+ end
182
+
183
+ def noop(&block)
184
+ @installers << Sprinkle::Installers::Noop.new(self, name, options, &block)
185
+ end
186
+
187
+ def push_text(text, path, options = {}, &block)
188
+ @installers << Sprinkle::Installers::PushText.new(self, text, path, options, &block)
189
+ end
190
+
191
+ def transfer(source, destination, options = {}, &block)
192
+ @installers << Sprinkle::Installers::Transfer.new(self, source, destination, options, &block)
193
+ end
194
+
195
+ def verify(description = '', &block)
196
+ @verifications << Sprinkle::Verify.new(self, description, &block)
197
+ end
198
+
199
+ def process(deployment, roles)
200
+ return if meta_package?
201
+
202
+ # Run a pre-test to see if the software is already installed. If so,
203
+ # we can skip it, unless we have the force option turned on!
204
+ unless @verifications.empty? || Sprinkle::OPTIONS[:force]
205
+ begin
206
+ process_verifications(deployment, roles, true)
207
+
208
+ logger.info "--> #{self.name} already installed for roles: #{roles}"
209
+ return
210
+ rescue Sprinkle::VerificationFailed => e
211
+ # Continue
212
+ end
213
+ end
214
+
215
+ @installers.each do |installer|
216
+ installer.defaults(deployment)
217
+ installer.process(roles)
218
+ end
219
+
220
+ process_verifications(deployment, roles)
221
+ end
222
+
223
+ def process_verifications(deployment, roles, pre = false)
224
+ return if @verifications.blank?
225
+
226
+ if pre
227
+ logger.info "--> Checking if #{self.name} is already installed for roles: #{roles}"
228
+ else
229
+ logger.info "--> Verifying #{self.name} was properly installed for roles: #{roles}"
230
+ end
231
+
232
+ @verifications.each do |v|
233
+ v.defaults(deployment)
234
+ v.process(roles)
235
+ end
236
+ end
237
+
238
+ def requires(*packages)
239
+ @dependencies << packages
240
+ @dependencies.flatten!
241
+ end
242
+
243
+ def recommends(*packages)
244
+ @recommends << packages
245
+ @recommends.flatten!
246
+ end
247
+
248
+ def optional(*packages)
249
+ @optional << packages
250
+ @optional.flatten!
251
+ end
252
+
253
+ def tree(depth = 1, &block)
254
+ packages = []
255
+
256
+ @recommends.each do |dep|
257
+ package = PACKAGES[dep]
258
+ next unless package # skip missing recommended packages as they're allowed to not exist
259
+ block.call(self, package, depth) if block
260
+ packages << package.tree(depth + 1, &block)
261
+ end
262
+
263
+ @dependencies.each do |dep|
264
+ package = PACKAGES[dep]
265
+ package = select_package(dep, package) if package.is_a? Array
266
+
267
+ raise "Package definition not found for key: #{dep}" unless package
268
+ block.call(self, package, depth) if block
269
+ packages << package.tree(depth + 1, &block)
270
+ end
271
+
272
+ packages << self
273
+
274
+ @optional.each do |dep|
275
+ package = PACKAGES[dep]
276
+ next unless package # skip missing optional packages as they're allow to not exist
277
+ block.call(self, package, depth) if block
278
+ packages << package.tree(depth + 1, &block)
279
+ end
280
+
281
+ packages
282
+ end
283
+
284
+ def to_s; @name; end
285
+
286
+ private
287
+
288
+ def select_package(name, packages)
289
+ if packages.size <= 1
290
+ package = packages.first
291
+ else
292
+ package = choose do |menu|
293
+ menu.prompt = "Multiple choices exist for virtual package #{name}"
294
+ menu.choices *packages.collect(&:to_s)
295
+ end
296
+ package = Sprinkle::Package::PACKAGES[package]
297
+ end
298
+
299
+ cloud_info "Selecting #{package.to_s} for virtual package #{name}"
300
+
301
+ package
302
+ end
303
+
304
+ def meta_package?
305
+ @installers.blank?
306
+ end
307
+ end
308
+ end
309
+ end