sprinkle 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ *.gem
1
2
  pkg
2
3
  .DS_Store
3
4
  .idea
data/README.markdown CHANGED
@@ -3,6 +3,7 @@
3
3
  * <http://redartisan.com/2008/5/27/sprinkle-intro>
4
4
  * <http://github.com/crafterm/sprinkle>
5
5
  * <http://github.com/benschwarz/passenger-stack>
6
+ * <http://github.com/trevorturk/sprinkle-packages>
6
7
  * <http://www.vimeo.com/2888665>
7
8
  * <http://redartisan.lighthouseapp.com/projects/25275-sprinkle/tickets>
8
9
 
@@ -23,7 +24,7 @@ An example package description follows:
23
24
  version '1.8.6'
24
25
  source "ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-#{version}-p111.tar.gz"
25
26
  requires :ruby_dependencies
26
-
27
+
27
28
  verify do
28
29
  has_file '/usr/bin/ruby'
29
30
  end
@@ -65,17 +66,17 @@ appserver, database and webserver can be virtual packages, where the user will b
65
66
  multiple choices for the virtual package exist.
66
67
 
67
68
  Sprinkle is architected to be extendable in many ways, one of those areas is in its deployment of commands to
68
- remote hosts. Currently Sprinkle supports the use of Capistrano, Vlad, or a direct net/ssh connection to
69
+ remote hosts. Currently Sprinkle supports the use of Capistrano, Vlad, or a direct net/ssh connection to
69
70
  issue commands on remote hosts via ssh, but could also be extended to use any other command transport mechanism
70
71
  desired. Sprinkle can also be configured to simply issue installation commands to provision the local system.
71
72
 
72
- An full example Sprinkle deployment script for deploying Rails (via gems), MySQL (via APT), Apache (via source)
73
+ An full example Sprinkle deployment script for deploying Rails (via gems), MySQL (via APT), Apache (via source)
73
74
  and Git (via source with dependencies from APT):
74
75
 
75
76
  # Sprinkle Rails deployment script
76
77
  #
77
78
  # This is an example Sprinkle script, configured to install Rails from gems, Apache, Ruby and Git from source,
78
- # and mysql and Git dependencies from apt on an Ubuntu system. Installation is configured to run via
79
+ # and mysql and Git dependencies from apt on an Ubuntu system. Installation is configured to run via
79
80
  # Capistrano (and an accompanying deploy.rb recipe script). Source based packages are downloaded and built into
80
81
  # /usr/local on the remote system.
81
82
  #
@@ -158,7 +159,7 @@ and Git (via source with dependencies from APT):
158
159
  version '1.0.5'
159
160
  requires :mongrel
160
161
  end
161
-
162
+
162
163
  package :git, :provides => :scm do
163
164
  description 'Git Distributed Version Control'
164
165
  version '1.5.6.3'
data/Rakefile CHANGED
@@ -23,6 +23,10 @@ rescue LoadError
23
23
  puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
24
24
  end
25
25
 
26
+ task "inst"=>[:clobber, :build] do
27
+ puts `gem install pkg/sprinkle-*.gem`
28
+ end
29
+
26
30
  require 'spec/rake/spectask'
27
31
  Spec::Rake::SpecTask.new(:spec) do |spec|
28
32
  spec.libs << 'lib' << 'spec'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.3.2
data/bin/sprinkle CHANGED
@@ -73,6 +73,10 @@ def powder_cloud(options)
73
73
  Sprinkle::OPTIONS[:cloud] = OPTIONS[:cloud] || false
74
74
  end
75
75
 
76
+ def verbosity(options)
77
+ Sprinkle::OPTIONS[:verbose] = OPTIONS[:verbose] || false
78
+ end
79
+
76
80
  def log_level(options)
77
81
  Object.logger.level = ActiveSupport::BufferedLogger::Severity::DEBUG if options[:verbose]
78
82
  end
@@ -86,5 +90,6 @@ force_mode(OPTIONS)
86
90
  operation_mode(OPTIONS)
87
91
  powder_cloud(OPTIONS)
88
92
  log_level(OPTIONS)
93
+ verbosity(OPTIONS)
89
94
 
90
95
  Sprinkle::Script.sprinkle File.read(powder), powder
data/lib/sprinkle.rb CHANGED
@@ -2,7 +2,13 @@ require 'rubygems'
2
2
  require 'active_support'
3
3
 
4
4
  # Use active supports auto load mechanism
5
- ActiveSupport::Dependencies.load_paths << File.dirname(__FILE__)
5
+ require 'active_support/version'
6
+ if ActiveSupport::VERSION::MAJOR > 2
7
+ require 'active_support/dependencies'
8
+ ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
9
+ else
10
+ ActiveSupport::Dependencies.load_paths << File.dirname(__FILE__)
11
+ end
6
12
 
7
13
  # Configure active support to log auto-loading of dependencies
8
14
  #ActiveSupport::Dependencies::RAILS_DEFAULT_LOGGER = Logger.new($stdout)
@@ -29,7 +29,9 @@ module Sprinkle
29
29
 
30
30
  def process(name, commands, roles, suppress_and_return_failures = false)
31
31
  return process_with_gateway(name, commands, roles) if gateway_defined?
32
- process_direct(name, commands, roles)
32
+ r = process_direct(name, commands, roles)
33
+ logger.debug green "process returning #{r}"
34
+ return r
33
35
  end
34
36
 
35
37
  def transfer(name, source, destination, roles, recursive = true, suppress_and_return_failures = false)
@@ -40,13 +42,17 @@ module Sprinkle
40
42
  protected
41
43
 
42
44
  def process_with_gateway(name, commands, roles)
45
+ res = []
43
46
  on_gateway do |gateway|
44
- Array(roles).each { |role| execute_on_role(commands, role, gateway) }
47
+ Array(roles).each { |role| res << execute_on_role(commands, role, gateway) }
45
48
  end
49
+ !(res.include? false)
46
50
  end
47
51
 
48
52
  def process_direct(name, commands, roles)
49
- Array(roles).each { |role| execute_on_role(commands, role) }
53
+ res = []
54
+ Array(roles).each { |role| res << execute_on_role(commands, role) }
55
+ !(res.include? false)
50
56
  end
51
57
 
52
58
  def transfer_with_gateway(name, source, destination, roles, recursive)
@@ -61,7 +67,9 @@ module Sprinkle
61
67
 
62
68
  def execute_on_role(commands, role, gateway = nil)
63
69
  hosts = @options[:roles][role]
64
- Array(hosts).each { |host| execute_on_host(commands, host, gateway) }
70
+ res = []
71
+ Array(hosts).each { |host| res << execute_on_host(commands, host, gateway) }
72
+ !(res.include? false)
65
73
  end
66
74
 
67
75
  def transfer_to_role(source, destination, role, gateway = nil)
@@ -70,23 +78,54 @@ module Sprinkle
70
78
  end
71
79
 
72
80
  def execute_on_host(commands, host, gateway = nil)
81
+ res = nil
82
+ logger.debug(blue "executing #{commands.inspect} on #{host}.")
73
83
  if gateway # SSH connection via gateway
74
84
  gateway.ssh(host, @options[:user]) do |ssh|
75
- execute_on_connection(commands, ssh)
85
+ res = execute_on_connection(commands, ssh)
86
+ ssh.loop
76
87
  end
77
88
  else # direct SSH connection
78
89
  Net::SSH.start(host, @options[:user], :password => @options[:password]) do |ssh|
79
- execute_on_connection(commands, ssh)
90
+ res = execute_on_connection(commands, ssh)
91
+ ssh.loop
80
92
  end
81
93
  end
94
+ res.detect{|x| x!=0}.nil?
82
95
  end
83
-
84
- def execute_on_connection(commands, connection)
85
- Array(commands).each do |command|
86
- connection.exec! command do |ch, stream, data|
87
- logger.send((stream == :stderr ? 'error' : 'debug'), data)
96
+
97
+ def execute_on_connection(commands, session)
98
+ res = []
99
+ Array(commands).each do |cmd|
100
+ session.open_channel do |channel|
101
+ channel.on_data do |ch, data|
102
+ logger.debug yellow("stdout said-->\n#{data}\n")
103
+ end
104
+ channel.on_extended_data do |ch, type, data|
105
+ next unless type == 1 # only handle stderr
106
+ logger.debug red("stderr said -->\n#{data}\n")
107
+ end
108
+
109
+ channel.on_request("exit-status") do |ch, data|
110
+ exit_code = data.read_long
111
+ if exit_code == 0
112
+ logger.debug(green 'success')
113
+ else
114
+ logger.debug(red('failed (%d).'%exit_code))
115
+ end
116
+ res << exit_code
117
+ end
118
+
119
+ channel.on_request("exit-signal") do |ch, data|
120
+ logger.debug red("#{cmd} was signaled!: #{data.read_long}")
121
+ end
122
+
123
+ channel.exec cmd do |ch, status|
124
+ logger.error("couldn't run remote command #{cmd}") unless status
125
+ end
88
126
  end
89
127
  end
128
+ res
90
129
  end
91
130
 
92
131
  def transfer_to_host(source, destination, host, recursive, gateway = nil)
@@ -100,18 +139,33 @@ module Sprinkle
100
139
  end
101
140
  end
102
141
  end
103
-
142
+
104
143
  def transfer_on_connection(source, destination, recursive, connection)
105
- scp = Net::SCP.new(connection)
106
- scp.upload! source, destination, :recursive => recursive
144
+ scp = Net::SCP.new(connection)
145
+ scp.upload! source, destination, :recursive => recursive
146
+ end
147
+
148
+ private
149
+ def color(code, s)
150
+ "\033[%sm%s\033[0m"%[code,s]
151
+ end
152
+ def red(s)
153
+ color(31, s)
154
+ end
155
+ def yellow(s)
156
+ color(33, s)
157
+ end
158
+ def green(s)
159
+ color(32, s)
160
+ end
161
+ def blue(s)
162
+ color(34, s)
107
163
  end
108
164
 
109
- private
110
-
111
165
  def gateway_defined?
112
166
  !! @options[:gateway]
113
167
  end
114
-
168
+
115
169
  def on_gateway(&block)
116
170
  gateway = Net::SSH::Gateway.new(@options[:gateway], @options[:user])
117
171
  block.call gateway
@@ -13,7 +13,7 @@ module Sprinkle
13
13
  # end
14
14
  # end
15
15
  #
16
- # script is given a list of files which capistrano will include and load.
16
+ # script is given a list of files which vlad will include and load.
17
17
  # These recipes are mainly to set variables such as :user, :password, and to
18
18
  # set the app domain which will be sprinkled.
19
19
  class Vlad
@@ -36,18 +36,23 @@ module Sprinkle
36
36
  # end
37
37
  def script(name)
38
38
  @loaded_recipes ||= []
39
- self.load name
40
- @loaded_recipes << script
39
+ require name
40
+ @loaded_recipes << name
41
41
  end
42
42
 
43
43
  def process(name, commands, roles, suppress_and_return_failures = false) #:nodoc:
44
- commands = commands.join ' && ' if commands.is_a? Array
44
+ commands = Array(commands)
45
+ if use_sudo
46
+ commands = commands.map{|x| "sudo #{x}"}
47
+ end
48
+ commands = commands.join(' && ')
49
+ puts "executing #{commands}"
45
50
  t = remote_task(task_sym(name), :roles => roles) { run commands }
46
51
 
47
52
  begin
48
53
  t.invoke
49
54
  return true
50
- rescue ::Vlad::CommandFailedError => e
55
+ rescue ::Rake::CommandFailedError => e
51
56
  return false if suppress_and_return_failures
52
57
 
53
58
  # Reraise error if we're not suppressing it
@@ -60,7 +65,7 @@ module Sprinkle
60
65
  begin
61
66
  rsync source, destination
62
67
  return true
63
- rescue ::Vlad::CommandFailedError => e
68
+ rescue ::Rake::CommandFailedError => e
64
69
  return false if suppress_and_return_failures
65
70
 
66
71
  # Reraise error if we're not suppressing it
@@ -36,7 +36,7 @@ module Sprinkle
36
36
  when /tar$/
37
37
  'tar xf'
38
38
  when /zip$/
39
- 'unzip'
39
+ 'unzip -o'
40
40
  else
41
41
  raise "Unknown binary archive format: #{archive_name}"
42
42
  end
@@ -24,7 +24,10 @@ module Sprinkle
24
24
  protected
25
25
 
26
26
  def install_commands #:nodoc:
27
- "wget -cq --directory-prefix=/tmp #{@packages.join(' ')}; dpkg -i #{@packages.collect{|p| "/tmp/#{package_name(p)}"}.join(" ")}"
27
+ [
28
+ "wget -cq --directory-prefix=/tmp #{@packages.join(' ')}",
29
+ "dpkg -i #{@packages.collect{|p| "/tmp/#{package_name(p)}"}.join(" ")}"
30
+ ]
28
31
  end
29
32
 
30
33
  private
@@ -33,4 +33,4 @@ module Sprinkle
33
33
 
34
34
  end
35
35
  end
36
- end
36
+ end
@@ -0,0 +1,79 @@
1
+ module Sprinkle
2
+ module Installers
3
+ class InstallPackage < Installer
4
+ cattr_accessor :installer
5
+ attr_accessor :packages #:nodoc:
6
+
7
+ def initialize(parent, packages, &block) #:nodoc:
8
+ super parent, &block
9
+ if packages.is_a?(Array) && packages.first.is_a?(Array)
10
+ packages = packages.first
11
+ else
12
+ packages = [packages] unless packages.is_a? Array
13
+ end
14
+
15
+ @packages = packages
16
+ end
17
+
18
+ protected
19
+
20
+ def install_commands #:nodoc:
21
+ case installer
22
+ when :smart
23
+ "smart install #{@packages.join(' ')} -y 2>&1 | tee -a /var/log/smart-sprinkle"
24
+ when :yum
25
+ "yum install #{@packages.join(' ')} -y 2>&1 | tee -a /var/log/yum-sprinkle"
26
+ else
27
+ raise "Unknown InstallPackage.installer"
28
+ end
29
+ end
30
+ end
31
+
32
+ class UninstallPackage < Installer
33
+ attr_accessor :packages #:nodoc:
34
+
35
+ def initialize(parent, packages, &block) #:nodoc:
36
+ super parent, &block
37
+ if packages.is_a?(Array) && packages.first.is_a?(Array)
38
+ packages = packages.first
39
+ else
40
+ packages = [packages] unless packages.is_a? Array
41
+ end
42
+
43
+ @packages = packages
44
+ end
45
+
46
+ protected
47
+
48
+ def install_commands #:nodoc:
49
+ case Sprinkle::Installers::InstallPackage.installer
50
+ when :smart
51
+ "smart remove #{@packages.join(' ')} -y 2>&1 | tee -a /var/log/smart-sprinkle"
52
+ when :yum
53
+ "yum erase #{@packages.join(' ')} -y 2>&1 | tee -a /var/log/yum-sprinkle"
54
+ else
55
+ raise "Unknown InstallPackage.installer"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ module Sprinkle
63
+ module Package
64
+ class Package
65
+ def install_package(*names, &block)
66
+ @installers << Sprinkle::Installers::InstallPackage.new(self, names, &block)
67
+ end
68
+
69
+ def uninstall_package(*names, &block)
70
+ @installers << Sprinkle::Installers::UninstallPackage.new(self, names, &block)
71
+ end
72
+
73
+
74
+ alias_method :install_packages, :install_package
75
+ alias_method :uninstall_packages, :uninstall_package
76
+ end
77
+ end
78
+ end
79
+
@@ -37,7 +37,7 @@ module Sprinkle
37
37
  protected
38
38
 
39
39
  def install_commands #:nodoc:
40
- "echo -e '#{@text.gsub("'", "'\\\\''").gsub("\n", '\n')}' |#{'sudo ' if option?(:sudo)}tee -a #{@path}"
40
+ "#{"#{'sudo ' if option?(:sudo)}grep \"^#{@text.gsub("'", "'\\\\''").gsub("\n", '\n')}$\" #{@path} ||" if option?(:idempotent) }/bin/echo -e '#{@text.gsub("'", "'\\\\''").gsub("\n", '\n')}' |#{'sudo ' if option?(:sudo)}tee -a #{@path}"
41
41
  end
42
42
 
43
43
  end
@@ -0,0 +1,45 @@
1
+ module Sprinkle
2
+ module Installers
3
+ # = Replace text installer
4
+ #
5
+ # This installer replaces a text with another one in a file.
6
+ #
7
+ # == Example Usage
8
+ #
9
+ # Change ssh port in /etc/ssh/sshd_config
10
+ #
11
+ # package :magic_beans do
12
+ # replace_text 'Port 22', 'Port 2500', '/etc/ssh/sshd_config'
13
+ # end
14
+ #
15
+ # If you user has access to 'sudo' and theres a file that requires
16
+ # priveledges, you can pass :sudo => true
17
+ #
18
+ # package :magic_beans do
19
+ # replace_text 'Port 22', 'Port 2500', '/etc/ssh/sshd_config', :sudo => true
20
+ # end
21
+ #
22
+ # A special verify step exists for this very installer
23
+ # its known as file_contains, it will test that a file indeed
24
+ # contains a substring that you send it.
25
+ #
26
+ class ReplaceText < Installer
27
+ attr_accessor :regex, :text, :path #:nodoc:
28
+
29
+ def initialize(parent, regex, text, path, options={}, &block) #:nodoc:
30
+ super parent, options, &block
31
+ @regex = regex
32
+ @text = text
33
+ @path = path
34
+ end
35
+
36
+ protected
37
+
38
+ def install_commands #:nodoc:
39
+ logger.info "--> Replace '#{@regex}' with '#{@text}' in file #{@path}"
40
+ "#{'sudo ' if option?(:sudo)}sed -i 's/#{@regex.gsub("'", "'\\\\''").gsub("/", "\\\\/").gsub("\n", '\n')}/#{@text.gsub("'", "'\\\\''").gsub("/", "\\\\/").gsub("\n", '\n')}/g' #{@path}"
41
+ end
42
+
43
+ end
44
+ end
45
+ end