sprinkle 0.3.1 → 0.3.2
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/.gitignore +1 -0
- data/README.markdown +6 -5
- data/Rakefile +4 -0
- data/VERSION +1 -1
- data/bin/sprinkle +5 -0
- data/lib/sprinkle.rb +7 -1
- data/lib/sprinkle/actors/ssh.rb +71 -17
- data/lib/sprinkle/actors/vlad.rb +11 -6
- data/lib/sprinkle/installers/binary.rb +1 -1
- data/lib/sprinkle/installers/deb.rb +4 -1
- data/lib/sprinkle/installers/freebsd_portinstall.rb +1 -1
- data/lib/sprinkle/installers/install_package.rb +79 -0
- data/lib/sprinkle/installers/push_text.rb +1 -1
- data/lib/sprinkle/installers/replace_text.rb +45 -0
- data/lib/sprinkle/installers/runner.rb +18 -0
- data/lib/sprinkle/installers/smart.rb +29 -0
- data/lib/sprinkle/installers/source.rb +10 -6
- data/lib/sprinkle/installers/transfer.rb +18 -4
- data/lib/sprinkle/installers/user.rb +15 -0
- data/lib/sprinkle/installers/zypper.rb +43 -0
- data/lib/sprinkle/package.rb +15 -0
- data/lib/sprinkle/verifiers/apt.rb +21 -0
- data/lib/sprinkle/verifiers/file.rb +9 -1
- data/lib/sprinkle/verifiers/package.rb +26 -0
- data/lib/sprinkle/verifiers/process.rb +2 -2
- data/lib/sprinkle/verifiers/ruby.rb +2 -2
- data/lib/sprinkle/verify.rb +2 -2
- data/spec/sprinkle/installers/push_text_spec.rb +4 -4
- data/spec/sprinkle/installers/replace_text_spec.rb +45 -0
- data/spec/sprinkle/installers/runner_spec.rb +31 -0
- data/spec/sprinkle/installers/source_spec.rb +8 -8
- data/spec/sprinkle/installers/zypper_spec.rb +49 -0
- data/spec/sprinkle/package_spec.rb +8 -0
- data/spec/sprinkle/verify_spec.rb +37 -38
- metadata +72 -21
data/.gitignore
CHANGED
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
|
+
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
|
-
|
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)
|
data/lib/sprinkle/actors/ssh.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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,
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
106
|
-
|
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
|
data/lib/sprinkle/actors/vlad.rb
CHANGED
@@ -13,7 +13,7 @@ module Sprinkle
|
|
13
13
|
# end
|
14
14
|
# end
|
15
15
|
#
|
16
|
-
# script is given a list of files which
|
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
|
-
|
40
|
-
@loaded_recipes <<
|
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
|
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 ::
|
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 ::
|
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
|
@@ -24,7 +24,10 @@ module Sprinkle
|
|
24
24
|
protected
|
25
25
|
|
26
26
|
def install_commands #:nodoc:
|
27
|
-
|
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
|
@@ -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
|