pot 0.1.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 (47) hide show
  1. checksums.yaml +15 -0
  2. data/CREDITS +32 -0
  3. data/LICENSE +20 -0
  4. data/README.md +21 -0
  5. data/bin/pot-console +9 -0
  6. data/lib/pot.rb +17 -0
  7. data/lib/pot/actor.rb +34 -0
  8. data/lib/pot/bundle.rb +66 -0
  9. data/lib/pot/config.rb +33 -0
  10. data/lib/pot/console.rb +7 -0
  11. data/lib/pot/deployment.rb +91 -0
  12. data/lib/pot/dsl.rb +39 -0
  13. data/lib/pot/installer.rb +131 -0
  14. data/lib/pot/installers/apt.rb +52 -0
  15. data/lib/pot/installers/binary.rb +46 -0
  16. data/lib/pot/installers/brew.rb +34 -0
  17. data/lib/pot/installers/deb.rb +41 -0
  18. data/lib/pot/installers/gem.rb +64 -0
  19. data/lib/pot/installers/group.rb +15 -0
  20. data/lib/pot/installers/npm.rb +19 -0
  21. data/lib/pot/installers/push_text.rb +49 -0
  22. data/lib/pot/installers/rake.rb +37 -0
  23. data/lib/pot/installers/replace_text.rb +45 -0
  24. data/lib/pot/installers/runner.rb +20 -0
  25. data/lib/pot/installers/source.rb +202 -0
  26. data/lib/pot/installers/transfer.rb +184 -0
  27. data/lib/pot/installers/user.rb +15 -0
  28. data/lib/pot/instance.rb +21 -0
  29. data/lib/pot/logger.rb +41 -0
  30. data/lib/pot/package.rb +352 -0
  31. data/lib/pot/policy.rb +74 -0
  32. data/lib/pot/role.rb +16 -0
  33. data/lib/pot/template.rb +20 -0
  34. data/lib/pot/transports/local.rb +31 -0
  35. data/lib/pot/transports/ssh.rb +81 -0
  36. data/lib/pot/verifiers/apt.rb +21 -0
  37. data/lib/pot/verifiers/brew.rb +21 -0
  38. data/lib/pot/verifiers/directory.rb +16 -0
  39. data/lib/pot/verifiers/executable.rb +53 -0
  40. data/lib/pot/verifiers/file.rb +34 -0
  41. data/lib/pot/verifiers/process.rb +21 -0
  42. data/lib/pot/verifiers/ruby.rb +25 -0
  43. data/lib/pot/verifiers/symlink.rb +30 -0
  44. data/lib/pot/verifiers/users_groups.rb +33 -0
  45. data/lib/pot/verify.rb +112 -0
  46. data/lib/pot/version.rb +13 -0
  47. metadata +118 -0
@@ -0,0 +1,184 @@
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 Pot
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. Note
76
+ # you can also explicitly pass locals in to render with the :locals option.
77
+ #
78
+ # package :nginx_conf do
79
+ # nginx_port = 8080
80
+ # transfer 'files/nginx.conf', '/etc/nginx.conf', :render => true
81
+ # end
82
+ #
83
+ # Finally, should you need to run commands before or after the file transfer (making
84
+ # directories or changing permissions), you can use the pre/post :install directives
85
+ # and they will be run.
86
+ class Transfer < Installer
87
+ attr_accessor :source, :destination #:nodoc:
88
+
89
+ def initialize(parent, source, destination, options={}, &block) #:nodoc:
90
+ super parent, options, &block
91
+ @source = source
92
+ @destination = destination
93
+ # perform the transfer in two steps if we're using sudo
94
+ if options[:sudo]
95
+ final = @destination
96
+ @destination = "/tmp/pod_#{File.basename(@destination)}"
97
+ post :install, "sudo mv #{@destination} #{final}"
98
+ end
99
+ end
100
+
101
+ def install_commands
102
+ nil
103
+ end
104
+
105
+ def self.render_template(template, context, prefix)
106
+ require 'tempfile'
107
+ require 'erubis'
108
+
109
+ begin
110
+ eruby = Erubis::Eruby.new(template)
111
+ output = eruby.result(context)
112
+ rescue Object => e
113
+ raise TemplateError.new(e, template, context)
114
+ end
115
+
116
+ final_tempfile = Tempfile.new(prefix.to_s)
117
+ final_tempfile.print(output)
118
+ final_tempfile.close
119
+ final_tempfile
120
+ end
121
+
122
+ def render_template(template, context, prefix)
123
+ self.class.render_template(template, context, prefix)
124
+ end
125
+
126
+ def render_template_file(path, context, prefix)
127
+ template = File.read(path)
128
+ tempfile = render_template(template, context, @package.name)
129
+ tempfile
130
+ end
131
+
132
+ def process(actor) #:nodoc:
133
+ if Pot.logger.debug?
134
+ Pot.logger.debug "transfer: #{@source} -> #{@destination}\n"
135
+ end
136
+
137
+ unless Pot.config.testing?
138
+ pre = pre_commands(:install)
139
+ unless pre.empty?
140
+ sequence = pre
141
+ sequence = sequence.join('; ') if sequence.is_a? Array
142
+ Pot.logger.info "#{@package.name} pre-transfer commands: #{sequence}\n"
143
+ actor.execute [pre].flatten
144
+ end
145
+
146
+ recursive = @options[:recursive]
147
+
148
+ if options[:render]
149
+ if options[:locals]
150
+ context = {}
151
+ options[:locals].each_pair do |k,v|
152
+ if v.respond_to?(:call)
153
+ context[k] = v.call
154
+ else
155
+ context[k] = v
156
+ end
157
+ end
158
+ else
159
+ context = binding()
160
+ end
161
+
162
+ tempfile = render_template_file(@source, context, @package.name)
163
+ sourcepath = tempfile.path
164
+ Pot.logger.info "Rendering template #{@source} to temporary file #{sourcepath}"
165
+ recursive = false
166
+ else
167
+ sourcepath = @source
168
+ end
169
+
170
+ Pot.logger.info "--> Transferring #{sourcepath} to #{@destination}"
171
+ actor.transfer(sourcepath, @destination, recursive)
172
+
173
+ post = post_commands(:install)
174
+ unless post.empty?
175
+ sequence = post;
176
+ sequence = sequence.join('; ') if sequence.is_a? Array
177
+ Pot.logger.info "#{@package.name} post-transfer commands: #{sequence}\n"
178
+ actor.execute [post].flatten
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,15 @@
1
+ module Pot
2
+ module Installers
3
+ class User < Installer
4
+ def initialize(package, username, options, &block)
5
+ super package, &block
6
+ @username = username
7
+ @options = options
8
+ end
9
+ protected
10
+ def install_commands
11
+ "adduser #{@options[:flags]} #{@username}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ module Pot
2
+ class Instance
3
+ REGISTER = {}
4
+
5
+ attr_reader :name,
6
+ :host,
7
+ :user,
8
+ :password,
9
+ :port
10
+
11
+ def initialize(name, host, options = {})
12
+ @name = name
13
+ @host = host
14
+ @user = options[:user]
15
+ @password = options[:password]
16
+ @port = options[:port]
17
+
18
+ REGISTER[name] = self
19
+ end
20
+ end
21
+ end
data/lib/pot/logger.rb ADDED
@@ -0,0 +1,41 @@
1
+ require 'logger'
2
+
3
+ module Pot
4
+
5
+ def self.logger
6
+ @logger ||= Pot::Logger.new
7
+ end
8
+
9
+ class Logger < ::Logger
10
+
11
+ def initialize
12
+ super $stdout
13
+ self.formatter = proc do |severity, datetime, progname, msg|
14
+ "#{msg}\n"
15
+ end
16
+ # self.level = Logger::ERROR
17
+ end
18
+
19
+ private
20
+
21
+ def color(code, s)
22
+ "\033[%sm%s\033[0m"%[code,s]
23
+ end
24
+
25
+ def red(s)
26
+ color(31, s)
27
+ end
28
+
29
+ def yellow(s)
30
+ color(33, s)
31
+ end
32
+
33
+ def green(s)
34
+ color(32, s)
35
+ end
36
+
37
+ def blue(s)
38
+ color(34, s)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,352 @@
1
+ module Pot
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 pot 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 Pot::Policy block.
23
+ #
24
+ # == Pre-Requirements
25
+ #
26
+ # Most packages have some sort of pre-requisites in order to be installed.
27
+ # Pot 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, Pot 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
+ # Pot provides verifications. Verifications are one or more blocks
44
+ # which define rules with which Pot can check if it installed
45
+ # the package successfully. If these verification blocks fail, then
46
+ # Pot 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
+ # Pot 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 Pot.config
61
+ #
62
+ # For more information on verifications and to see all the available
63
+ # verifications, see Pot::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 Pot, 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
+ class Package
95
+ REGISTER = {}
96
+
97
+ attr_accessor :name,
98
+ :provides,
99
+ :installers,
100
+ :dependencies,
101
+ :recommends,
102
+ :verifications
103
+
104
+ def initialize(name, metadata = {}, &block)
105
+ raise 'No package name supplied' unless name
106
+
107
+ @name = name
108
+ @provides = metadata[:provides]
109
+ @dependencies = []
110
+ @recommends = []
111
+ @optional = []
112
+ @verifications = []
113
+ @installers = []
114
+
115
+ REGISTER[name] = self
116
+
117
+ if @provides
118
+ (REGISTER[@provides] ||= []) << self
119
+ end
120
+
121
+ self.instance_eval &block
122
+ end
123
+
124
+ def description(desc = nil)
125
+ @description = desc if desc
126
+ @description
127
+ end
128
+
129
+ def version(ver = nil)
130
+ @version = ver if ver
131
+ @version
132
+ end
133
+
134
+ def template(name)
135
+ Pot::Template.new(self, name)
136
+ end
137
+
138
+ def add_user(username, options={}, &block)
139
+ @installers << Pot::Installers::User.new(self, username, options, &block)
140
+ end
141
+
142
+ def add_group(group, options={}, &block)
143
+ @installers << Pot::Installers::Group.new(self, group, options, &block)
144
+ end
145
+
146
+ def freebsd_pkg(*names, &block)
147
+ @installers << Pot::Installers::FreebsdPkg.new(self, *names, &block)
148
+ end
149
+
150
+ def freebsd_portinstall(port, &block)
151
+ @installers << Pot::Installers::FreebsdPortinstall.new(self, port, &block)
152
+ end
153
+
154
+ def openbsd_pkg(*names, &block)
155
+ @installers << Pot::Installers::OpenbsdPkg.new(self, *names, &block)
156
+ end
157
+
158
+ def opensolaris_pkg(*names, &block)
159
+ @installers << Pot::Installers::OpensolarisPkg.new(self, *names, &block)
160
+ end
161
+
162
+ def bsd_port(port, &block)
163
+ @installers << Pot::Installers::BsdPort.new(self, port, &block)
164
+ end
165
+
166
+ def mac_port(port, &block)
167
+ @installers << Pot::Installers::MacPort.new(self, port, &block)
168
+ end
169
+
170
+ def apt(*names, &block)
171
+ @installers << Pot::Installers::Apt.new(self, *names, &block)
172
+ end
173
+
174
+ def deb(*names, &block)
175
+ @installers << Pot::Installers::Deb.new(self, *names, &block)
176
+ end
177
+
178
+ def rpm(*names, &block)
179
+ @installers << Pot::Installers::Rpm.new(self, *names, &block)
180
+ end
181
+
182
+ def yum(*names, &block)
183
+ @installers << Pot::Installers::Yum.new(self, *names, &block)
184
+ end
185
+
186
+ def zypper(*names, &block)
187
+ @installers << Pot::Installers::Zypper.new(self, *names, &block)
188
+ end
189
+
190
+ def brew(*names, &block)
191
+ @installers << Pot::Installers::Brew.new(self, *names, &block)
192
+ end
193
+
194
+ def gem(name, options = {}, &block)
195
+ @recommends << :rubygems
196
+ @installers << Pot::Installers::Gem.new(self, name, options, &block)
197
+ end
198
+
199
+ def source(source, options = {}, &block)
200
+ @recommends << :build_essential # Ubuntu/Debian
201
+ @installers << Pot::Installers::Source.new(self, source, options, &block)
202
+ end
203
+
204
+ def binary(source, options = {}, &block)
205
+ @installers << Pot::Installers::Binary.new(self, source, options, &block)
206
+ end
207
+
208
+ def rake(name, options = {}, &block)
209
+ @installers << Pot::Installers::Rake.new(self, name, options, &block)
210
+ end
211
+
212
+ def thor(name, options = {}, &block)
213
+ @installers << Pot::Installers::Thor.new(self, name, options, &block)
214
+ end
215
+
216
+ def noop(&block)
217
+ @installers << Pot::Installers::Runner.new(self, "echo noop", &block)
218
+ end
219
+
220
+ def push_text(text, path, options = {}, &block)
221
+ @installers << Pot::Installers::PushText.new(self, text, path, options, &block)
222
+ end
223
+
224
+ def replace_text(regex, text, path, options={}, &block)
225
+ @installers << Pot::Installers::ReplaceText.new(self, regex, text, path, options, &block)
226
+ end
227
+
228
+ def transfer(source, destination, options = {}, &block)
229
+ @installers << Pot::Installers::Transfer.new(self, source, destination, options, &block)
230
+ end
231
+
232
+ def runner(cmd, options = {}, &block)
233
+ @installers << Pot::Installers::Runner.new(self, cmd, options, &block)
234
+ end
235
+
236
+ def verify(description = '', &block)
237
+ @verifications << Pot::Verify.new(self, description, &block)
238
+ end
239
+
240
+ def pacman(*names, &block)
241
+ @installers << Pot::Installers::Pacman.new(self, *names, &block)
242
+ end
243
+
244
+ def process(actor)
245
+ Pot.logger.info " * #{name}"
246
+ return if meta_package?
247
+
248
+ # Run a pre-test to see if the software is already installed. If so,
249
+ # we can skip it, unless we have the force option turned on!
250
+ unless @verifications.empty? || Pot.config.force
251
+ begin
252
+ process_verifications(actor, true)
253
+
254
+ Pot.logger.info "--> #{self.name} already installed"
255
+ return
256
+ rescue Pot::VerificationFailed => e
257
+ # Continue
258
+ end
259
+ end
260
+
261
+ @installers.each do |installer|
262
+ installer.process(actor)
263
+ end
264
+
265
+ process_verifications(actor)
266
+ end
267
+
268
+ def process_verifications(actor, pre = false)
269
+ return if @verifications.empty?
270
+
271
+ if pre
272
+ Pot.logger.info "--> Checking if #{self.name} is already installed"
273
+ else
274
+ Pot.logger.info "--> Verifying #{self.name} was properly installed"
275
+ end
276
+
277
+ @verifications.each do |v|
278
+ v.process(actor)
279
+ end
280
+ end
281
+
282
+ def requires(*packages)
283
+ @dependencies << packages
284
+ @dependencies.flatten!
285
+ end
286
+
287
+ def recommends(*packages)
288
+ @recommends << packages
289
+ @recommends.flatten!
290
+ end
291
+
292
+ def optional(*packages)
293
+ @optional << packages
294
+ @optional.flatten!
295
+ end
296
+
297
+ def tree(depth = 1, &block)
298
+ packages = []
299
+
300
+ @recommends.each do |dep|
301
+ package = REGISTER[dep]
302
+ next unless package # skip missing recommended packages as they're allowed to not exist
303
+ block.call(self, package, depth) if block
304
+ packages << package.tree(depth + 1, &block)
305
+ end
306
+
307
+ @dependencies.each do |dep|
308
+ package = REGISTER[dep]
309
+ package = select_package(dep, package) if package.is_a? Array
310
+
311
+ raise "Package definition not found for key: #{dep}" unless package
312
+ block.call(self, package, depth) if block
313
+ packages << package.tree(depth + 1, &block)
314
+ end
315
+
316
+ packages << self
317
+
318
+ @optional.each do |dep|
319
+ package = REGISTER[dep]
320
+ next unless package # skip missing optional packages as they're allow to not exist
321
+ block.call(self, package, depth) if block
322
+ packages << package.tree(depth + 1, &block)
323
+ end
324
+
325
+ packages
326
+ end
327
+
328
+ def to_s; @name; end
329
+
330
+ private
331
+
332
+ def select_package(name, packages)
333
+ if packages.size <= 1
334
+ package = packages.first
335
+ else
336
+ package = choose do |menu|
337
+ menu.prompt = "Multiple choices exist for virtual package #{name}"
338
+ menu.choices *packages.collect(&:to_s)
339
+ end
340
+ package = REGISTER[package]
341
+ end
342
+
343
+ cloud_info "Selecting #{package.to_s} for virtual package #{name}"
344
+
345
+ package
346
+ end
347
+
348
+ def meta_package?
349
+ @installers.empty?
350
+ end
351
+ end
352
+ end