├── spec
├── spec.opts
├── fixtures
│ └── my_file.txt
├── templates
│ ├── locals.erb
│ └── test.erb
├── sprinkle
│ ├── installers
│ │ ├── npm_spec.rb
│ │ ├── pear_spec.rb
│ │ ├── thor_spec.rb
│ │ ├── rake_spec.rb
│ │ ├── mac_port_spec.rb
│ │ ├── freebsd_portinstall_spec.rb
│ │ ├── bsd_port_spec.rb
│ │ ├── openbsd_pkg_spec.rb
│ │ ├── freebsd_pkg_spec.rb
│ │ ├── opensolaris_pkg_spec.rb
│ │ ├── user_spec.rb
│ │ ├── yum_spec.rb
│ │ ├── rpm_spec.rb
│ │ ├── zypper_spec.rb
│ │ ├── smart_spec.rb
│ │ ├── brew_spec.rb
│ │ ├── runner_spec.rb
│ │ ├── replace_text_spec.rb
│ │ ├── binary_spec.rb
│ │ ├── pecl_spec.rb
│ │ ├── apt_spec.rb
│ │ ├── gem_spec.rb
│ │ ├── push_text_spec.rb
│ │ └── file_spec.rb
│ ├── extensions
│ │ ├── array_spec.rb
│ │ ├── string_spec.rb
│ │ └── rendering_spec.rb
│ ├── actors
│ │ ├── ssh_spec.rb
│ │ └── local_spec.rb
│ ├── sprinkle_spec.rb
│ ├── package
│ │ └── package_repository_spec.rb
│ ├── script_spec.rb
│ ├── deployment_spec.rb
│ └── policy_spec.rb
└── spec_helper.rb
├── templates
└── test.erb
├── lib
├── sprinkle
│ ├── version.rb
│ ├── extensions
│ │ ├── array.rb
│ │ ├── symbol.rb
│ │ ├── blank_slate.rb
│ │ ├── string.rb
│ │ ├── sudo.rb
│ │ └── attributes.rb
│ ├── commands
│ │ ├── reconnect.rb
│ │ ├── command.rb
│ │ └── transfer.rb
│ ├── installers
│ │ ├── smart.rb
│ │ ├── pacman.rb
│ │ ├── freebsd_pkg.rb
│ │ ├── brew.rb
│ │ ├── deb.rb
│ │ ├── reconnect.rb
│ │ ├── npm.rb
│ │ ├── zypper.rb
│ │ ├── pear.rb
│ │ ├── bsd_port.rb
│ │ ├── thor.rb
│ │ ├── group.rb
│ │ ├── rpm.rb
│ │ ├── opensolaris_pkg.rb
│ │ ├── yum.rb
│ │ ├── rake.rb
│ │ ├── freebsd_portinstall.rb
│ │ ├── mac_port.rb
│ │ ├── package_installer.rb
│ │ ├── openbsd_pkg.rb
│ │ ├── user.rb
│ │ ├── replace_text.rb
│ │ ├── runner.rb
│ │ ├── apt.rb
│ │ ├── binary.rb
│ │ ├── push_text.rb
│ │ ├── gem.rb
│ │ ├── install_package.rb
│ │ ├── pecl.rb
│ │ └── file.rb
│ ├── verifiers
│ │ ├── package.rb
│ │ ├── test.rb
│ │ ├── process.rb
│ │ ├── ruby.rb
│ │ ├── permission.rb
│ │ ├── executable.rb
│ │ └── file.rb
│ ├── core.rb
│ ├── package
│ │ ├── chooser.rb
│ │ ├── package_repository.rb
│ │ └── rendering.rb
│ ├── actors
│ │ ├── ssh
│ │ │ └── connection_cache.rb
│ │ ├── dummy.rb
│ │ ├── actor.rb
│ │ ├── local.rb
│ │ ├── vlad.rb
│ │ └── capistrano.rb
│ ├── errors
│ │ ├── transfer_failure.rb
│ │ ├── remote_command_failure.rb
│ │ ├── pretty_failure.rb
│ │ └── template_error.rb
│ ├── script.rb
│ ├── utility
│ │ └── log_recorder.rb
│ ├── deployment.rb
│ ├── verify.rb
│ └── policy.rb
└── sprinkle.rb
├── examples
├── rails
│ ├── deploy.rb
│ ├── templates
│ │ └── mysql.cnf.erb
│ ├── packages
│ │ ├── essential.rb
│ │ ├── scm.rb
│ │ ├── search.rb
│ │ ├── database.rb
│ │ ├── rails.rb
│ │ └── server.rb
│ ├── README
│ └── rails.rb
├── packages
│ ├── scm
│ │ ├── subversion.rb
│ │ └── git.rb
│ ├── ruby
│ │ ├── rails.rb
│ │ ├── rubygems.rb
│ │ └── ruby.rb
│ ├── build_essential.rb
│ ├── databases
│ │ ├── sqlite3.rb
│ │ ├── mysql.rb
│ │ └── mysql_source.rb
│ ├── servers
│ │ └── apache.rb
│ └── phusion.rb
└── sprinkle
│ └── sprinkle.rb
├── .travis.yml
├── .gitignore
├── .tm_properties
├── Gemfile
├── script
├── console
├── destroy
└── generate
├── MIT-LICENSE
├── sprinkle.gemspec
├── CREDITS
├── Rakefile
├── CHANGELOG.md
├── Gemfile.lock
└── bin
└── sprinkle
/spec/spec.opts:
--------------------------------------------------------------------------------
1 | --colour
--------------------------------------------------------------------------------
/spec/fixtures/my_file.txt:
--------------------------------------------------------------------------------
1 | ...
2 |
--------------------------------------------------------------------------------
/templates/test.erb:
--------------------------------------------------------------------------------
1 | hello <%= @world %>
--------------------------------------------------------------------------------
/spec/templates/locals.erb:
--------------------------------------------------------------------------------
1 | hello <%= world %>
--------------------------------------------------------------------------------
/spec/templates/test.erb:
--------------------------------------------------------------------------------
1 | hello <%= @world %>
--------------------------------------------------------------------------------
/lib/sprinkle/version.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | Version = "0.7.7"
3 | end
--------------------------------------------------------------------------------
/examples/rails/deploy.rb:
--------------------------------------------------------------------------------
1 | set :user, 'root'
2 | role :app, 'yourhost.com', :primary => true
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 2.0.0
4 | - 1.9.3
5 | - 1.9.2
6 | - 1.8.7
7 | - ree
8 |
--------------------------------------------------------------------------------
/lib/sprinkle/extensions/array.rb:
--------------------------------------------------------------------------------
1 | class Array #:nodoc:
2 | def to_task_name
3 | collect(&:to_task_name).join('_')
4 | end
5 | end
--------------------------------------------------------------------------------
/lib/sprinkle/extensions/symbol.rb:
--------------------------------------------------------------------------------
1 | class Symbol #:nodoc:
2 |
3 | def to_task_name
4 | to_s.to_task_name
5 | end
6 |
7 | end
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | pkg
3 | .DS_Store
4 | .idea
5 | .*.swp
6 | coverage/*
7 | rdoc
8 | .ruby-version
9 |
10 | work
11 | config
12 | tmp
13 |
--------------------------------------------------------------------------------
/examples/packages/scm/subversion.rb:
--------------------------------------------------------------------------------
1 | package :subversion, :provides => :scm do
2 | description 'Subversion Version Control'
3 |
4 | apt 'subversion'
5 | end
6 |
--------------------------------------------------------------------------------
/examples/rails/templates/mysql.cnf.erb:
--------------------------------------------------------------------------------
1 | [mysqld]
2 | <%= "innodb_file_per_table" if opts[:innodb_file_per_table] %>
3 | innodb_buffer_pool_size = <%= opts[:innodb_buffer_pool_size] %>
--------------------------------------------------------------------------------
/examples/packages/ruby/rails.rb:
--------------------------------------------------------------------------------
1 | package :rails do
2 | description 'Ruby on Rails'
3 | version '3.2'
4 |
5 | gem 'rails'
6 |
7 | verify do
8 | has_executable 'rails'
9 | end
10 | end
--------------------------------------------------------------------------------
/.tm_properties:
--------------------------------------------------------------------------------
1 | projectDirectory = "$CWD"
2 |
3 | stockFiles = "{Gemfile.lock,test.rb}"
4 |
5 | excludeDirectories = "{rdoc,work}"
6 | # excludeInFolderSearch = "${excludeDirectories}"
7 | excludeFiles = "{*.gem,$stockFiles}"
--------------------------------------------------------------------------------
/lib/sprinkle/extensions/blank_slate.rb:
--------------------------------------------------------------------------------
1 | class BlankSlate #:nodoc:
2 | instance_methods.each do |m|
3 | undef_method(m) unless %w( __send__ __id__ send class inspect instance_eval instance_variables object_id ).include?(m.to_s)
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/sprinkle/extensions/string.rb:
--------------------------------------------------------------------------------
1 | class String #:nodoc:
2 |
3 | # REVISIT: what chars shall we allow in task names?
4 | def to_task_name
5 | s = downcase
6 | s.gsub!(/-/, '_') # all - to _ chars
7 | s
8 | end
9 |
10 | end
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # to make travis CI happy
2 | #source :rubygems
3 | source "https://rubygems.org"
4 |
5 | gemspec
6 |
7 | group :development do
8 | gem 'railties'
9 | gem 'rdoc', "3.10"
10 | gem 'sdoc'
11 | gem 'pry'
12 | gem 'pry_debug'
13 | gem 'pry-rescue'
14 | end
--------------------------------------------------------------------------------
/lib/sprinkle/commands/reconnect.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Commands
3 | class Reconnect < Command
4 |
5 | def initialize()
6 | end
7 |
8 | def inspect
9 | ":RECONNECT"
10 | end
11 |
12 | end
13 | end
14 | end
--------------------------------------------------------------------------------
/script/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # File: script/console
3 | irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4 |
5 | libs = " -r irb/completion"
6 | libs << " -r #{File.dirname(__FILE__) + '/../lib/sprinkle.rb'}"
7 | puts "Loading sprinkle gem"
8 | exec "#{irb} #{libs} --simple-prompt"
--------------------------------------------------------------------------------
/examples/rails/packages/essential.rb:
--------------------------------------------------------------------------------
1 | ## Special package, anything that defines a 'source' package means build-essential should be installed for Ubuntu
2 |
3 | package :build_essential do
4 | description 'Build tools'
5 |
6 | apt 'build-essential' do
7 | # Update the sources and upgrade the lists before we build essentials
8 | pre :install, 'apt-get update'
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/examples/rails/packages/scm.rb:
--------------------------------------------------------------------------------
1 | package :git_dependencies do
2 | description 'Git Build Dependencies'
3 |
4 | apt 'git', :dependencies_only => true
5 | end
6 |
7 | package :git, :provides => :scm do
8 | description 'Git Distributed Version Control'
9 | version '1.6.3.3'
10 | requires :git_dependencies
11 |
12 | source "http://kernel.org/pub/software/scm/git/git-#{version}.tar.gz"
13 | end
--------------------------------------------------------------------------------
/lib/sprinkle/extensions/sudo.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Sudo
3 |
4 | def sudo_cmd
5 | return "#{@delivery.try(:sudo_command) || "sudo"} " if sudo?
6 | end
7 |
8 | def sudo?
9 | sudo_stack.detect { |x| x==true or x==false }
10 | end
11 |
12 | def sudo_stack
13 | [ options[:sudo], package.sudo?, @delivery.try(:sudo?) ]
14 | end
15 |
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/examples/packages/scm/git.rb:
--------------------------------------------------------------------------------
1 | package :git, :provides => :scm do
2 | description 'Git Distributed Version Control'
3 | version '1.5.6.3'
4 | requires :git_dependencies
5 |
6 | source "http://kernel.org/pub/software/scm/git/git-#{version}.tar.gz"
7 | end
8 |
9 | package :git_dependencies do
10 | description 'Git Build Dependencies'
11 |
12 | apt 'git', :dependencies_only => true
13 | end
14 |
--------------------------------------------------------------------------------
/examples/rails/packages/search.rb:
--------------------------------------------------------------------------------
1 | package :sphinx, :provides => :search do
2 | description 'MySQL full text search engine'
3 | version '0.9.8.1'
4 | requires :mysql_dev
5 |
6 | source "http://www.sphinxsearch.com/downloads/sphinx-#{version}.tar.gz"
7 | end
8 |
9 | package :mysql_dev do
10 | description 'MySQL Database development package'
11 |
12 | apt %w( libmysqlclient15-dev )
13 | end
14 |
--------------------------------------------------------------------------------
/examples/packages/build_essential.rb:
--------------------------------------------------------------------------------
1 | ## Special package, anything that defines a 'source' package means build-essential should be installed for Ubuntu
2 |
3 | package :build_essential do
4 | description 'Build tools'
5 |
6 | apt 'build-essential' do
7 | # Update the sources and upgrade the lists before we build essentials
8 | pre :install, ['aptitude update', 'aptitude safe-upgrade', 'aptitude full-upgrade']
9 | end
10 | end
--------------------------------------------------------------------------------
/examples/rails/README:
--------------------------------------------------------------------------------
1 | = Example Rails Sprinkle Deployment Script
2 |
3 | The following example shows how you can provision Rails and associated packages onto a remote server (or set of servers).
4 |
5 | == Usage:
6 |
7 | $> sprinkle -s rails.rb
8 |
9 | or in test mode:
10 |
11 | $> sprinkle -t -s rails.rb
12 |
13 | == Information
14 |
15 | For more information, please see: http://github.com/crafterm/sprinkle/tree/master/README.markdown
--------------------------------------------------------------------------------
/examples/packages/databases/sqlite3.rb:
--------------------------------------------------------------------------------
1 | # Packages to install sqlite3 and the sqlite3 ruby driver.
2 | package :sqlite3, :provides => :database do
3 | description 'SQLite3 database'
4 |
5 | apt 'sqlite3'
6 | end
7 |
8 | package :sqlite3_ruby_driver do
9 | description 'Ruby SQLite3 library.'
10 | requires :rubygems
11 |
12 | apt 'libsqlite3-dev libsqlite3-ruby1.8'
13 | verify do
14 | ruby_can_load 'sqlite3'
15 | end
16 | end
--------------------------------------------------------------------------------
/examples/packages/databases/mysql.rb:
--------------------------------------------------------------------------------
1 | package :mysql, :provides => :database do
2 | description 'MySQL Database'
3 |
4 | apt %w( mysql-server mysql-client libmysqlclient15-dev )
5 | end
6 |
7 | package :mysql_ruby_driver do
8 | description 'Ruby MySQL database driver'
9 |
10 | gem 'mysql' # :build_flags => "—with-mysql-config=/usr/local/mysql/bin/mysql_config"
11 |
12 | verify do
13 | ruby_can_load 'mysql'
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/script/destroy:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3 |
4 | begin
5 | require 'rubigen'
6 | rescue LoadError
7 | require 'rubygems'
8 | require 'rubigen'
9 | end
10 | require 'rubigen/scripts/destroy'
11 |
12 | ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13 | RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14 | RubiGen::Scripts::Destroy.new.run(ARGV)
15 |
--------------------------------------------------------------------------------
/script/generate:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3 |
4 | begin
5 | require 'rubigen'
6 | rescue LoadError
7 | require 'rubygems'
8 | require 'rubigen'
9 | end
10 | require 'rubigen/scripts/generate'
11 |
12 | ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13 | RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14 | RubiGen::Scripts::Generate.new.run(ARGV)
15 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/smart.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | class Smart < PackageInstaller
4 |
5 | api do
6 | def smart(*names, &block)
7 | install Smart.new(self, *names, &block)
8 | end
9 | end
10 |
11 | protected
12 |
13 | def install_commands #:nodoc:
14 | "smart install #{@packages.join(' ')} -y 2>&1 | tee -a /var/log/smart-sprinkle"
15 | end
16 | end
17 | end
18 | end
--------------------------------------------------------------------------------
/lib/sprinkle/verifiers/package.rb:
--------------------------------------------------------------------------------
1 | # TODO: remove
2 | module Sprinkle
3 | module Verifiers
4 | module Package #:nodoc:
5 | Sprinkle::Verify.register(Sprinkle::Verifiers::Package)
6 |
7 | def has_package(*packages)
8 | puts "has_package and has_packages are depreciated"
9 | raise "please use has_yum and friends instead"
10 | end
11 |
12 | alias_method :has_packages, :has_package
13 | end
14 | end
15 | end
--------------------------------------------------------------------------------
/examples/packages/servers/apache.rb:
--------------------------------------------------------------------------------
1 | package :apache, :provides => :webserver do
2 | description 'Apache2 web server.'
3 |
4 | apt 'apache2 apache2.2-common apache2-mpm-prefork apache2-utils libexpat1 ssl-cert' do
5 | post :install, 'a2enmod rewrite'
6 | end
7 |
8 | verify do
9 | has_process 'apache2'
10 | end
11 | end
12 |
13 | package :apache2_prefork_dev do
14 | description 'A dependency required by some packages.'
15 |
16 | apt 'apache2-prefork-dev'
17 | end
--------------------------------------------------------------------------------
/lib/sprinkle/core.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | # stores the global list of policies as they are defined
3 | POLICIES = []
4 |
5 | module Core
6 | # Defines a single policy. Currently the only option, which is also
7 | # required, is :roles, which defines which servers a policy is
8 | # used on.
9 | def policy(name, options = {}, &block)
10 | p = Sprinkle::Policy.new(name, options, &block)
11 | POLICIES << p
12 | p
13 | end
14 |
15 | end
16 | end
--------------------------------------------------------------------------------
/lib/sprinkle/commands/command.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Commands
3 | class Command
4 |
5 | def initialize(str, opts = {})
6 | @sudo = opts[:sudo]
7 | @str = str
8 | # this is a dummy class for now, not intended to be used directly
9 | raise
10 | end
11 |
12 | def sudo?
13 | @sudo
14 | end
15 |
16 | def string
17 | # TODO: sudo
18 | @str
19 | end
20 |
21 | end
22 | end
23 | end
--------------------------------------------------------------------------------
/examples/rails/packages/database.rb:
--------------------------------------------------------------------------------
1 | package :mysql, :provides => :database do
2 | description 'MySQL Database'
3 |
4 | defaults :innodb_file_per_table => true,
5 | :innodb_buffer_pool_size => "512MB"
6 |
7 | file "/etc/my.cnf",
8 | :contents => render("mysql.cnf"),
9 | :sudo => true
10 |
11 | apt %w( mysql-server mysql-client )
12 | end
13 |
14 | package :ruby_mysql_driver do
15 | description 'Ruby MySQL database driver'
16 | requires :mysql
17 |
18 | gem 'mysql2'
19 | end
20 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/pacman.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The pacman installer installs Pacman packages
4 | class Pacman < PackageInstaller
5 |
6 | ##
7 | # installs the Pacman packages passed
8 | # :method: pacman
9 | # :call-seq: pacman(*packages)
10 | auto_api
11 |
12 | protected
13 |
14 | def install_commands #:nodoc:
15 | "pacman -Sy #{@packages.join(' ')} --no-confirm --needed"
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/examples/packages/ruby/rubygems.rb:
--------------------------------------------------------------------------------
1 | package :rubygems do
2 | description 'Ruby Gems Package Management System'
3 | version '1.8.23'
4 | requires :ruby
5 |
6 | source "http://rubyforge.org/frs/download.php/38646/rubygems-#{version}.tgz" do
7 | custom_install 'ruby setup.rb'
8 | post :install, 'ln -s /usr/bin/gem1.8 /usr/bin/gem'
9 | post :install, 'gem update'
10 | post :install, 'gem update --system'
11 | end
12 |
13 | verify 'binary' do
14 | has_file '/usr/bin/gem1.8'
15 | has_symlink '/usr/bin/gem', '/usr/bin/gem1.8'
16 | end
17 | end
--------------------------------------------------------------------------------
/lib/sprinkle/verifiers/test.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Verifiers
3 | # = Test Verifier
4 | #
5 | # Checks that a specific test runs successfully (using the unix test command)
6 | #
7 | # == Example Usage
8 | #
9 | # verify { test '-f /some_file' }
10 | #
11 | module Test
12 | Sprinkle::Verify.register(Sprinkle::Verifiers::Test)
13 |
14 | # Checks to make sure a test runs successfully on the remote server
15 | def test(args)
16 | @commands << "test #{args}"
17 | end
18 |
19 | end
20 | end
21 | end
--------------------------------------------------------------------------------
/spec/sprinkle/installers/npm_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::Npm do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'spec')
7 | @installer = Sprinkle::Installers::Npm.new(@package, 'spec')
8 | end
9 |
10 | describe 'during installation' do
11 | it 'should invoke the npm executer for all specified tasks' do
12 | @install_commands = @installer.send :install_commands
13 | @install_commands.should == "npm install --global spec"
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/pear_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::Pear do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'spec')
7 | @installer = Sprinkle::Installers::Pear.new(@package, 'spec')
8 | end
9 |
10 | describe 'during installation' do
11 | it 'should invoke the pear executer for all specified tasks' do
12 | @install_commands = @installer.send :install_commands
13 | @install_commands.should == "pear install --alldeps spec"
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/sprinkle/verifiers/process.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Verifiers
3 | # = Process Verifier
4 | #
5 | # Contains a verifier to check that a process is running.
6 | #
7 | # == Example Usage
8 | #
9 | # verify { has_process 'httpd' }
10 | #
11 | module Process
12 | Sprinkle::Verify.register(Sprinkle::Verifiers::Process)
13 |
14 | # Checks to make sure process is a process running
15 | # on the remote server.
16 | def has_process(process)
17 | @commands << "ps -C #{process}"
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/examples/packages/ruby/ruby.rb:
--------------------------------------------------------------------------------
1 | package :ruby do
2 | description 'Ruby Virtual Machine'
3 | version '1.8.6'
4 |
5 | apt %q(ruby1.8-dev ruby1.8 ri1.8 rdoc1.8 irb1.8 libreadline-ruby1.8 libruby1.8 libopenssl-ruby) do
6 | post :install, [%q(ln -s /usr/bin/ruby1.8 /usr/bin/ruby),
7 | %q(ln -s /usr/bin/ri1.8 /usr/bin/ri),
8 | %q(ln -s /usr/bin/rdoc1.8 /usr/bin/rdoc),
9 | %q(ln -s /usr/bin/irb1.8 /usr/bin/irb)]
10 | end
11 |
12 | verify 'binaries' do
13 | has_file '/usr/bin/ruby1.8'
14 | has_file '/usr/bin/ri1.8'
15 | has_file '/usr/bin/rdoc1.8'
16 | has_file '/usr/bin/irb1.8'
17 | end
18 | end
--------------------------------------------------------------------------------
/spec/sprinkle/extensions/array_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Array, 'task name conversions' do
4 |
5 | it 'should be able to deliver a task name' do
6 | ['build_essential'].to_task_name.should == 'build_essential'
7 | end
8 |
9 | it 'should join multiple elements together with a _ char' do
10 | ['gdb', 'gcc', 'g++'].to_task_name.should == 'gdb_gcc_g++'
11 | end
12 |
13 | it 'should use the task name of the underlying array element' do
14 | string = 'build-essential'
15 | string.should_receive(:to_task_name).and_return('build_essential')
16 | [string].to_task_name.should == 'build_essential'
17 | end
18 |
19 | end
20 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | $:.unshift(File.dirname(__FILE__) + '/../lib')
2 | require 'sprinkle'
3 |
4 | module Sprinkle
5 | module TestLogger
6 | def logger
7 | # ActiveSupport::BufferedLogger was deprecated and replaced by ActiveSupport::Logger in Rails 4.
8 | # Use ActiveSupport::Logger if available.
9 | active_support_logger = defined?(ActiveSupport::Logger) ? ActiveSupport::Logger : ActiveSupport::BufferedLogger
10 | @@__log_file__ ||= StringIO.new
11 | @@__log__ = active_support_logger.new @@__log_file__, active_support_logger::Severity::INFO
12 | end
13 | end
14 | end
15 |
16 | class Object
17 | include Sprinkle::TestLogger
18 | end
19 |
20 | ActiveSupport::Deprecation.silenced = true
21 |
--------------------------------------------------------------------------------
/lib/sprinkle/package/chooser.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle::Package
2 | class Chooser #:nodoc:
3 |
4 | def self.select_package(name, packages)
5 | if packages.size <= 1
6 | package = packages.first
7 | else
8 | package = choose do |menu|
9 | menu.prompt = "Multiple choices exist for virtual package #{name}"
10 | packages.each do |pkg|
11 | menu.choice(pkg.to_s) { pkg; }
12 | end
13 | end
14 | end
15 | cloud_info "Selecting #{package.to_s} for virtual package #{name}"
16 | package
17 | end
18 |
19 | def self.cloud_info(message)
20 | logger.info(message) if Sprinkle::OPTIONS[:cloud] or logger.debug?
21 | end
22 |
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/sprinkle/extensions/string_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe String, 'task name conversions' do
4 |
5 | it 'should be able to deliver a task name' do
6 | 'build_essential'.to_task_name.should == 'build_essential'
7 | end
8 |
9 | it 'should convert all - chars to _ in the task name' do
10 | 'build-essential'.to_task_name.should == 'build_essential'
11 | end
12 |
13 | it 'should convert multiple - chars to _ chars in the task name' do
14 | 'build--essential'.to_task_name.should == 'build__essential'
15 | end
16 |
17 | it 'should lowercase the task name' do
18 | 'BUILD-ESSENTIAL'.to_task_name.should == 'build_essential'
19 | end
20 |
21 | end
--------------------------------------------------------------------------------
/lib/sprinkle/commands/transfer.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Commands
3 | class Transfer < Command
4 |
5 | attr_reader :source, :destination, :opts
6 |
7 | def initialize(source, destination, opts={})
8 | @source = source
9 | @destination = destination
10 | @opts = opts
11 | end
12 |
13 | def recursive?
14 | !!@opts[:recursive]
15 | end
16 |
17 | def inspect
18 | ":TRANSFER, src: #{source}, dest: #{destination}, opts: #{@opts.inspect}"
19 | end
20 |
21 | def eql?(a,b)
22 | a.source == b.source &&
23 | a.destionation == b.destination &&
24 | a.opts == b.opts
25 | end
26 |
27 | end
28 | end
29 | end
--------------------------------------------------------------------------------
/lib/sprinkle/actors/ssh/connection_cache.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Actors
3 | class SSHConnectionCache #:nodoc:
4 | def initialize(options={})
5 | @cache = {}
6 | @gateway = Net::SSH::Gateway.new(options[:gateway], options[:user]) if options[:gateway]
7 | end
8 | def start(host, user, opts={})
9 | key="#{host}/#{user}#{opts.to_s}"
10 | if @gateway
11 | @cache[key] ||= @gateway.ssh(host, user, opts)
12 | else
13 | @cache[key] ||= Net::SSH.start(host, user, opts)
14 | end
15 | end
16 | def reconnect(host)
17 | @cache.delete_if do |k,v|
18 | (v.close; true) if k =~ /^#{host}\//
19 | end
20 | end
21 | def shutdown!
22 | @gateway.shutdown! if @gateway
23 | end
24 | end
25 | end
26 | end
--------------------------------------------------------------------------------
/lib/sprinkle/errors/transfer_failure.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Errors
3 |
4 | class TransferFailure < PrettyFailure #:nodoc:
5 |
6 | def self.no_permission(installer,e)
7 | tf=TransferFailure.new(installer, {}, e)
8 | tf.details[:error]=e.message
9 | tf
10 | end
11 |
12 | def print_summary
13 | summary
14 | # log "Command", @details[:command]
15 | log "ERROR", @details[:error]
16 | if details[:error] =~ /Permission denied/
17 | log "HINTS", "You may want to try passing the :sudo option to transfer."
18 | end
19 | end
20 |
21 | def summary
22 | boxed("Package '#{@installer.package.name}' could not transfer #{@installer.source}")
23 | end
24 |
25 | end
26 |
27 | end
28 | end
--------------------------------------------------------------------------------
/lib/sprinkle/script.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | # = Scripting
3 | #
4 | # Script gives you a way to programatically run a given
5 | # sprinkle script.
6 | class Script
7 | include Sprinkle::Deployment
8 |
9 | def initialize
10 | @deployment = nil
11 | end
12 |
13 | # Run a given sprinkle script. This method is blocking so
14 | # it will not return until the sprinkling is complete or fails.
15 | #--
16 | # FIXME: Improve documentation, possibly notify user how to tell
17 | # if a sprinkling failed.
18 | #++
19 | def self.sprinkle(script, filename = '__SCRIPT__')
20 | powder = new
21 | powder.instance_eval script, filename
22 | powder.sprinkle
23 | end
24 |
25 | def sprinkle #:nodoc:
26 | @deployment.process if @deployment
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/lib/sprinkle/utility/log_recorder.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Utility #:nodoc:
3 | class LogRecorder #:nodoc:
4 |
5 | attr_accessor :err, :out, :command, :code
6 |
7 | def initialize(cmd=nil)
8 | reset(cmd)
9 | end
10 |
11 | def log(stream, data)
12 | case stream
13 | when :err then @err << data
14 | when :out then @out << data
15 | end
16 | end
17 |
18 | # hash suitable to pass into a pretty failure details hash
19 | def hash
20 | {:error => err, :stdout => out, :command => command, :code => code}
21 | end
22 |
23 | def reset(cmd=nil)
24 | @command=cmd
25 | @code=nil
26 | @err=""
27 | @out=""
28 | end
29 |
30 | end
31 |
32 | end
33 | end
--------------------------------------------------------------------------------
/lib/sprinkle/errors/remote_command_failure.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Errors
3 |
4 | class RemoteCommandFailure < PrettyFailure #:nodoc:
5 |
6 | def print_summary
7 | summary
8 | log "Command", @details[:command]
9 | # capistrano returns this
10 | log "Hosts", @details[:hosts] if @details[:hosts]
11 | # ssh actor returns error and stdout outputs
12 | log "STDERR", @details[:error] unless @details[:error].blank?
13 | log "STDOUT", @details[:stdout] unless @details[:stdout].blank?
14 | log "Actor error message", @details[:message] if @details[:message]
15 | end
16 |
17 | def summary
18 | boxed("Package '#{@installer.package.name}' returned error code #{@details[:code]}.")
19 | end
20 |
21 | end
22 |
23 | end
24 | end
--------------------------------------------------------------------------------
/examples/rails/packages/rails.rb:
--------------------------------------------------------------------------------
1 | package :ruby_dependencies do
2 | description 'Ruby Virtual Machine Build Dependencies'
3 |
4 | apt %w( bison zlib1g-dev libssl-dev libreadline5-dev libncurses5-dev file )
5 | end
6 |
7 | package :ruby do
8 | description 'Ruby Virtual Machine'
9 | version '1.8.6'
10 | patchlevel = '369'
11 | requires :ruby_dependencies
12 |
13 | source "ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-#{version}-p#{patchlevel}.tar.gz" # implicit :style => :gnu
14 | end
15 |
16 | package :rubygems do
17 | description 'Ruby Gems Package Management System'
18 | version '1.8.23'
19 | requires :ruby
20 |
21 | source "http://rubyforge.org/frs/download.php/60718/rubygems-#{version}.tgz" do
22 | custom_install 'ruby setup.rb'
23 | end
24 | end
25 |
26 | package :rails do
27 | description 'Ruby on Rails'
28 | version '3.2'
29 |
30 | gem 'rails'
31 | end
32 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/freebsd_pkg.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The FreeBSDPkg installer installs FreeBSD packages.
4 | #
5 | # == Example Usage
6 | #
7 | # Installing the magic_beans package.
8 | #
9 | # package :magic_beans do
10 | # freebsd_pkg 'magic_beans'
11 | # end
12 | #
13 | # You may also specify multiple packages as an array:
14 | #
15 | # package :magic_beans do
16 | # freebsd_pkg %w(magic_beans magic_sauce)
17 | # end
18 | #
19 | class FreebsdPkg < PackageInstaller
20 |
21 | ##
22 | # installs the FreeBSD packages passed
23 | # :method: freebsd_pkg
24 | # :call-seq: freebsd_pkg(*packages)
25 | auto_api
26 |
27 | protected
28 |
29 | def install_commands #:nodoc:
30 | "pkg_add -r #{@packages.join(' ')}"
31 | end
32 |
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/lib/sprinkle/verifiers/ruby.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Verifiers
3 | # = Ruby Verifiers
4 | #
5 | # The verifiers in this module are ruby specific.
6 | module Ruby
7 | Sprinkle::Verify.register(Sprinkle::Verifiers::Ruby)
8 |
9 | # Checks if ruby can require the files given. rubygems
10 | # is always included first.
11 | def ruby_can_load(*files)
12 | # Always include rubygems first
13 | files = files.unshift('rubygems').collect { |x| "require '#{x}'" }
14 |
15 | @commands << "ruby -e \"#{files.join(';')}\""
16 | end
17 |
18 | # Checks if a gem exists by calling "gem list" and grepping against it.
19 | def has_gem(name, version = nil)
20 | version = version ? "--version '#{version}'" : ''
21 | @commands << "gem list '#{name}' --installed #{version} > /dev/null"
22 | end
23 | end
24 | end
25 | end
--------------------------------------------------------------------------------
/lib/sprinkle/errors/pretty_failure.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Errors #:nodoc:
3 |
4 | class PrettyFailure < StandardError #:nodoc:
5 |
6 | attr_accessor :details
7 |
8 | def initialize(installer, details={}, previous_error=nil)
9 | @installer = installer
10 | @details = details
11 | @previous_error = previous_error
12 | end
13 |
14 | def log(s, o)
15 | puts s
16 | puts "-" * (s.length+2)
17 | puts o
18 | puts
19 | end
20 |
21 | def boxed(s)
22 | puts red("-"*54)
23 | puts red("| #{s.center(50)} |")
24 | puts red("-"*54)
25 | puts
26 | end
27 |
28 | private
29 |
30 | def color(code, s)
31 | "\033[%sm%s\033[0m"%[code,s]
32 | end
33 |
34 | def red(s)
35 | color(31, s)
36 | end
37 |
38 | end
39 |
40 | end
41 | end
--------------------------------------------------------------------------------
/lib/sprinkle/installers/brew.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The Homebrew package installer uses the +brew+ command to install
4 | # packages on OSX.
5 | #
6 | # == Example Usage
7 | #
8 | # package :magic_beans do
9 | # description "Beans beans they're good for your heart..."
10 | # brew 'ntp'
11 | #
12 | # verify { has_brew 'ntp' }
13 | #
14 | # end
15 | #
16 | class Brew < PackageInstaller
17 |
18 | api do
19 | def brew(*names, &block)
20 | recommends :homebrew
21 | install_package(*names, &block)
22 | end
23 | end
24 |
25 | verify_api do
26 | def has_brew(package)
27 | @commands << "brew list | grep #{package}"
28 | end
29 | end
30 |
31 | protected
32 |
33 | def install_commands #:nodoc:
34 | "brew install #{@packages.join(' ')}"
35 | end
36 |
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/thor_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::Thor do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'spec')
7 | end
8 |
9 | def create_thor(names, options = {}, &block)
10 | Sprinkle::Installers::Thor.new(@package, names, options, &block)
11 | end
12 |
13 | describe 'during installation' do
14 |
15 | it 'should invoke the thor executer for all specified tasks' do
16 | @installer = create_thor 'spec'
17 | @install_commands = @installer.send :install_commands
18 | @install_commands.should =~ /thor spec/
19 | end
20 |
21 | it 'should invoke the thor executer for all specified tasks' do
22 | @installer = create_thor 'spec', :thorfile => '/some/Thorfile'
23 | @install_commands = @installer.send :install_commands
24 | @install_commands.should == "thor -f /some/Thorfile spec"
25 | end
26 |
27 | end
28 |
29 | end
--------------------------------------------------------------------------------
/lib/sprinkle/installers/deb.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The Deb installer installs deb packages sourced from a remote URL
4 | #
5 | # == Example Usage
6 | #
7 | # Installing the magic_beans deb.
8 | #
9 | # package :magic_beans do
10 | # deb 'http://debs.example.com/magic_beans.deb'
11 | # end
12 | #
13 | class Deb < PackageInstaller
14 |
15 | ##
16 | # install deb packages from an external URL
17 | # :call-seq:
18 | # deb(*package_urls)
19 | auto_api :deb
20 |
21 | protected
22 |
23 | def install_commands #:nodoc:
24 | [
25 | "wget -cq --directory-prefix=/tmp #{@packages.join(' ')}",
26 | "dpkg -i #{@packages.collect{|p| "/tmp/#{package_name(p)}"}.join(" ")}"
27 | ]
28 | end
29 |
30 | private
31 |
32 | def package_name(url)
33 | url.split('/').last
34 | end
35 |
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/rake_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::Rake do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'spec')
7 | end
8 |
9 | def create_rake(names, options = {}, &block)
10 | Sprinkle::Installers::Rake.new(@package, names, options, &block)
11 | end
12 |
13 | describe 'during installation' do
14 |
15 | it 'should invoke the rake executer for all specified tasks' do
16 | @installer = create_rake 'spec'
17 | @install_commands = @installer.send :install_commands
18 | @install_commands.should =~ /rake spec/
19 | end
20 |
21 | it 'should invoke the rake executer for all specified tasks' do
22 | @installer = create_rake 'spec', :rakefile => '/some/Rakefile'
23 | @install_commands = @installer.send :install_commands
24 | @install_commands.should == "rake -f /some/Rakefile spec"
25 | end
26 |
27 | end
28 |
29 | end
30 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/reconnect.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # Disconnects and reconnects the remote SSH session, you might want to do this
4 | # after pushing a file that would affect the local shell environment
5 | #
6 | # == Example Usage
7 | #
8 | # package :download_with_proxy do
9 | # push_text proxy_config, "/etc/environment", :sudo => true
10 | # reconnect
11 | # source "http://someurlthatneedstheproxy.com/installer.tar.gz"
12 | # end
13 | class Reconnect < Installer
14 |
15 | api do
16 | def reconnect(options={}, &block)
17 | install Sprinkle::Installers::Reconnect.new(self, options, &block)
18 | end
19 | end
20 |
21 | # :RECONNECT is a symbol that the actors understand to mean to drop
22 | # and reestablish any SSH conncetions they have open
23 | def install_commands #:nodoc:
24 | Commands::Reconnect.new()
25 | end
26 |
27 | end
28 | end
29 | end
--------------------------------------------------------------------------------
/lib/sprinkle/installers/npm.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # = Npm package Installed
4 | #
5 | # Installs an npm module
6 | #
7 | # == Example Usage
8 | #
9 | # package :magic_beans do
10 | # npm 'grunt'
11 | # end
12 | #
13 | # verify { has_npm 'grunt' }
14 | class Npm < Installer
15 |
16 | attr_accessor :package_name
17 |
18 | api do
19 | def npm(package, &block)
20 | install Npm.new(self, package, &block)
21 | end
22 | end
23 |
24 | verify_api do
25 | def has_npm(package)
26 | @commands << "npm --global list | grep \"#{package}@\""
27 | end
28 | end
29 |
30 | def initialize(parent, package_name, &block) #:nodoc:
31 | super parent, &block
32 | @package_name = package_name
33 | end
34 |
35 | protected
36 |
37 | def install_commands #:nodoc:
38 | "npm install --global #{@package_name}"
39 | end
40 |
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/spec/sprinkle/actors/ssh_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Sprinkle::Actors::SSH do
4 | describe 'process' do
5 | before do
6 | subject.stub(:gateway_defined?).and_return(false)
7 | end
8 |
9 | subject do
10 | Sprinkle::Actors::SSH.new do
11 | role :app, "booger.com"
12 | end
13 | end
14 |
15 | let(:commands) { %w[one two three] }
16 | let(:roles) { %w[app] }
17 |
18 | describe 'when use_sudo is true' do
19 | before do
20 | subject.use_sudo(true)
21 | end
22 |
23 | it 'prepends "sudo" to each command' do
24 | subject.send(:prepare_commands,commands).should == ['sudo one', 'sudo two', 'sudo three']
25 | end
26 | end
27 |
28 | describe 'when use_sudo is false' do
29 | before do
30 | subject.use_sudo(false)
31 | end
32 |
33 | it 'does not prepend "sudo" to each command' do
34 | subject.send(:prepare_commands,commands).should == commands
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/zypper.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # = Zypper Installer
4 | #
5 | # Zypper is a command-line interface to ZYpp system management library.
6 | # It mostly be used on Suse or OpenSuse.
7 | #
8 | # == Example Usage
9 | #
10 | # Installing the magic_beans package via Zypper. Its all the craze these days.
11 | #
12 | # package :magic_beans do
13 | # zypper 'magic_beans'
14 | # end
15 | #
16 | # You may also specify multiple packages as an argument list or array:
17 | #
18 | # package :magic_beans do
19 | # zypper "magic_beans", "magic_sauce"
20 | # end
21 | class Zypper < PackageInstaller
22 |
23 | ##
24 | # installs the ZYpp packages passed
25 | # :method: zypper
26 | # :call-seq: zypper(*packages)
27 | auto_api
28 |
29 | protected
30 |
31 | def install_commands #:nodoc:
32 | "zypper -n install -l -R #{@packages.join(' ')}"
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/examples/rails/packages/server.rb:
--------------------------------------------------------------------------------
1 | package :mongrel do
2 | description 'Mongrel Application Server'
3 | version '1.1.5'
4 |
5 | gem 'mongrel'
6 | end
7 |
8 | package :mongrel_cluster, :provides => :appserver do
9 | description 'Cluster Management for Mongrel'
10 | version '1.0.5'
11 | requires :mongrel
12 |
13 | gem 'mongrel_cluster'
14 | end
15 |
16 | package :apache, :provides => :webserver do
17 | description 'Apache 2 HTTP Server'
18 | version '2.2.11'
19 | requires :apache_dependencies
20 |
21 | source "http://www.apache.org/dist/httpd/httpd-#{version}.tar.bz2" do
22 | enable %w( mods-shared=all proxy proxy-balancer proxy-http rewrite cache headers ssl deflate so )
23 | prefix "/opt/local/apache2-#{version}"
24 | post :install, 'install -m 755 support/apachectl /etc/init.d/apache2', 'update-rc.d -f apache2 defaults'
25 | end
26 | end
27 |
28 | package :apache_dependencies do
29 | description 'Apache 2 HTTP Server Build Dependencies'
30 |
31 | apt %w( openssl libtool mawk zlib1g-dev libssl-dev )
32 | end
33 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/pear.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # = Pear package installed
4 | #
5 | # Installs the specified pear package
6 | #
7 | # == Example Usage
8 | #
9 | # package :php_stuff do
10 | # pear 'PHP_Compat'
11 | # verify { has_pear 'PHP_Compat' }
12 | # end
13 | class Pear < Installer
14 | attr_accessor :package_name
15 |
16 | api do
17 | def pear(package, &block)
18 | install Pear.new(self, package, &block)
19 | end
20 | end
21 |
22 | verify_api do
23 | def has_pear(package)
24 | @commands << "pear list | grep \"#{package}\" | grep \"stable\""
25 | end
26 | end
27 |
28 | def initialize(parent, package_name, &block) #:nodoc:
29 | super parent, &block
30 | @package_name = package_name
31 | end
32 |
33 | protected
34 | def install_commands #:nodoc:
35 | "pear install --alldeps #{@package_name}"
36 | end
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/bsd_port.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The BSD Port installer installs OpenBSD and FreeBSD ports.
4 | # Before usage, the ports sytem must be installed and
5 | # ready on the target operating system.
6 | #
7 | # == Example Usage
8 | #
9 | # Installing the magic_beans port.
10 | #
11 | # package :magic_beans do
12 | # bsd_port 'magic/magic_beans'
13 | # end
14 | #
15 | class BsdPort < Installer
16 | attr_accessor :port #:nodoc:
17 |
18 | api do
19 | def bsd_port(port, options ={}, &block)
20 | install BsdPort.new(self, port, options, &block)
21 | end
22 | end
23 |
24 | def initialize(parent, port, options={}, &block) #:nodoc:
25 | super parent, options, &block
26 | @port = port
27 | end
28 |
29 | protected
30 |
31 | def install_commands #:nodoc:
32 | "sh -c 'cd /usr/ports/#{@port} && make BATCH=yes install clean'"
33 | end
34 |
35 | end
36 | end
37 | end
--------------------------------------------------------------------------------
/lib/sprinkle/installers/thor.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # = Thor Installer
4 | #
5 | # This installer runs a thor task.
6 | #
7 | # == Example Usage
8 | #
9 | # The following example runs the command "thor spec" on
10 | # the remote server.
11 | #
12 | # package :spec do
13 | # thor 'spec'
14 | # end
15 | #
16 | # Specify a Thorfile with the :thorfile option.
17 | #
18 | # package :spec do
19 | # thor 'spec', :file => "/var/setup/Thorfile"
20 | # end
21 | class Thor < Sprinkle::Installers::Rake
22 |
23 | api do
24 | def thor(task, options = {}, &block)
25 | install Thor.new(self, task, options, &block)
26 | end
27 | end
28 |
29 | protected
30 |
31 | def executable #:nodoc:
32 | "thor"
33 | end
34 |
35 | def taskfile #:nodoc:
36 | file = @options[:thorfile] || @options[:file]
37 | file ? "-f #{file} " : ""
38 | end
39 |
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/spec/sprinkle/sprinkle_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle do
4 |
5 | it 'should automatically extend Object to support package, policy and deployment DSL keywords' do
6 | %w( package policy ).each do |keyword|
7 | Object.should respond_to(keyword.to_sym)
8 | end
9 | end
10 |
11 | it 'should default to production mode' do
12 | Sprinkle::OPTIONS[:testing].should be_false
13 | end
14 |
15 | it 'should automatically create a logger object on Kernel' do
16 | Object.should respond_to(:logger)
17 | logger.should_not be_nil
18 | # ActiveSupport::BufferedLogger was deprecated and replaced by ActiveSupport::Logger in Rails 4.
19 | # Use ActiveSupport::Logger if available
20 | active_support_logger = defined?(ActiveSupport::Logger) ? ActiveSupport::Logger : ActiveSupport::BufferedLogger
21 | logger.class.should == active_support_logger
22 | end
23 |
24 | it 'should create a logger of level INFO' do
25 | logger.level.should == Logger::Severity::INFO
26 | end
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/group.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The user installer helps add groups. You may pass flags as an option.
4 | #
5 | # == Example Usage
6 | #
7 | # package :users do
8 | # add_group 'webguys', :flags => "--shell /usr/bin/zsh"
9 | #
10 | # verify do
11 | # has_group 'webguys'
12 | # end
13 | # end
14 | class Group < Installer
15 | api do
16 | def add_group(group, options={}, &block)
17 | install Group.new(self, group, options, &block)
18 | end
19 | end
20 |
21 | verify_api do
22 | def has_group(group)
23 | @commands << "egrep -i \"^#{group}:\" /etc/group"
24 | end
25 | end
26 |
27 | def initialize(package, groupname, options, &block) #:nodoc:
28 | super package, options, &block
29 | @groupname = groupname
30 | end
31 |
32 | protected
33 | def install_commands #:nodoc:
34 | "addgroup #{@options[:flags]} #{@groupname}"
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/rpm.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # = RPM Package Installer
4 | #
5 | # The RPM package installer installs RPM packages.
6 | #
7 | # == Example Usage
8 | #
9 | # Installing the magic_beans RPM. Its all the craze these days.
10 | #
11 | # package :magic_beans do
12 | # rpm 'magic_beans'
13 | # verify { has_rpm 'magic_beans' }
14 | # end
15 | #
16 | # You may also specify multiple rpms as an array:
17 | #
18 | # package :magic_beans do
19 | # rpm %w(magic_beans magic_sauce)
20 | # end
21 | class Rpm < PackageInstaller
22 |
23 | ##
24 | # install RPM packages
25 | # :method: rpm
26 | # :call-seq:
27 | # rpm(*packages)
28 | auto_api
29 |
30 | verify_api do
31 | def has_rpm(package)
32 | @commands << "rpm -qa | grep #{package}"
33 | end
34 | end
35 |
36 | protected
37 |
38 | def install_commands #:nodoc:
39 | "rpm -Uvh #{@packages.join(' ')}"
40 | end
41 |
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2008-2013 Marcus Crafter
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/lib/sprinkle/installers/opensolaris_pkg.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The OpenSolaris package installer installs OpenSolaris packages.
4 | #
5 | # == Example Usage
6 | #
7 | # Installing the magic_beans package.
8 | #
9 | # package :magic_beans do
10 | # opensolaris_pkg 'magic_beans'
11 | # end
12 | #
13 | # You may also specify multiple packages as an array:
14 | #
15 | # package :magic_beans do
16 | # opensolaris_pkg 'magic_beans', 'magic_sauce'
17 | # end
18 | #
19 | # == Note
20 | # If you are using capistrano as the deployment method
21 | # you will need to add the following lines to your deploy.rb
22 | #
23 | # set :sudo, 'pfexec'
24 | # set :sudo_prompt, ''
25 | class OpensolarisPkg < PackageInstaller
26 |
27 | ##
28 | # installs the OpenSolaris packages passed
29 | # :method: opensolaris_pkg
30 | # :call-seq: opensolaris_pkg(*packages)
31 | auto_api
32 |
33 | protected
34 |
35 | def install_commands #:nodoc:
36 | "pkg install #{@packages.join(' ')}"
37 | end
38 |
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/sprinkle/verifiers/permission.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Verifiers
3 | # = Permission and ownership Verifier
4 | #
5 | # Contains a verifier to check the permissions and ownership of a file or directory.
6 | #
7 | # == Example Usage
8 | #
9 | # verify { has_permission '/etc/apache2/apache2.conf', 0644 }
10 | #
11 | # verify { belongs_to_user '/etc/apache2/apache2.conf', 'noop' }
12 | #
13 | # verify { belongs_to_user '/etc/apache2/apache2.conf', 1000 }
14 | #
15 | module Permission
16 | Sprinkle::Verify.register(Sprinkle::Verifiers::Permission)
17 |
18 | def has_permission(path, permission)
19 | @commands << "find #{path} -maxdepth 0 -perm #{permission} | egrep '.*'"
20 | end
21 |
22 | def belongs_to_user(path, user)
23 | arg = user.is_a?(Integer) ? "-uid" : "-user"
24 | @commands << "find #{path} -maxdepth 0 #{arg} #{user} | egrep '.*'"
25 | end
26 |
27 | def belongs_to_group(path, group)
28 | arg = group.is_a?(Integer) ? "-gid" : "-group"
29 | @commands << "find #{path} -maxdepth 0 #{arg} #{group} | egrep '.*'"
30 | end
31 |
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/yum.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The Yum package installer installs RPM packages.
4 | #
5 | # == Example Usage
6 | #
7 | # Installing the magic_beans RPM via Yum. Its all the craze these days.
8 | #
9 | # package :magic_beans do
10 | # yum 'magic_beans', 'magic_corn'
11 | # verify do
12 | # has_yum 'magic_beans'
13 | # has_yum 'magic_corn'
14 | # end
15 | # end
16 | #
17 | # To install a specific version just add that version after the name
18 | #
19 | # package :magic_beans do
20 | # yum "magic_beans-3.0"
21 | # end
22 | class Yum < PackageInstaller
23 |
24 | ##
25 | # installs the RPM packages passed
26 | # :method: yum
27 | # :call-seq: yum(*packages)
28 | auto_api
29 |
30 | verify_api do
31 | def has_yum(package)
32 | @commands << "yum list installed #{package} | grep ^#{package}"
33 | end
34 | end
35 |
36 | protected
37 |
38 | def install_commands #:nodoc:
39 | "yum install #{@packages.join(' ')} -y"
40 | end
41 |
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/examples/sprinkle/sprinkle.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sprinkle -c -s
2 |
3 | # Example of the simplest Sprinkle script to install a single gem on a remote host. This
4 | # particular script assumes that rubygems (and ruby, etc) are already installed on the remote
5 | # host. To see a larger example of installing an entire ruby, rubygems, gem stack from source,
6 | # please see the rails example.
7 |
8 | # Packages, only sprinkle is defined in this world
9 |
10 | package :sprinkle do
11 | description 'Sprinkle Provisioning Tool'
12 | gem 'crafterm-sprinkle' do
13 | source 'http://gems.github.com' # use alternate gem server
14 | #repository '/opt/local/gems' # specify an alternate local gem repository
15 | end
16 | end
17 |
18 |
19 | # Policies, sprinkle policy requires only the sprinkle gem
20 |
21 | policy :sprinkle, :roles => :app do
22 | requires :sprinkle
23 | end
24 |
25 |
26 | # Deployment settings
27 |
28 | deployment do
29 |
30 | # use vlad for deployment
31 | delivery :vlad do
32 | role :app, 'yourhost.com'
33 | end
34 |
35 | end
36 |
37 | # End of script, given the above information, Spinkle will apply the defined policy on all roles using the
38 | # deployment settings specified.
39 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/mac_port_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::MacPort do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'package')
7 | end
8 |
9 | def create_port(ports, &block)
10 | Sprinkle::Installers::MacPort.new(@package, ports, &block)
11 | end
12 |
13 | describe 'when created' do
14 |
15 | it 'should accept a single package to install' do
16 | @installer = create_port 'ruby'
17 | @installer.port.should == 'ruby'
18 | end
19 |
20 | end
21 |
22 | describe 'during installation' do
23 |
24 | before do
25 | @installer = create_port 'ruby' do
26 | pre :install, 'op1'
27 | post :install, 'op2'
28 | end
29 | @install_commands = @installer.send :install_commands
30 | end
31 |
32 | it 'should invoke the port installer for all specified packages' do
33 | @install_commands.should =~ /port install ruby/
34 | end
35 |
36 | it 'should automatically insert pre/post commands for the specified package' do
37 | @installer.send(:install_sequence).should == [ 'op1', 'port install ruby', 'op2' ]
38 | end
39 |
40 | end
41 |
42 | end
43 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/rake.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # This installer runs a rake task.
4 | #
5 | # == Example Usage
6 | #
7 | # The following example runs the command "rake spec" on
8 | # the remote server. Specify an optional Rakefile with
9 | # the :rakefile option.
10 | #
11 | # package :spec do
12 | # rake 'spec', :file => "/var/setup/Rakefile"
13 | # end
14 | class Rake < Installer
15 |
16 | api do
17 | def rake(task, options = {}, &block)
18 | install Rake.new(self, task, options, &block)
19 | end
20 | end
21 |
22 | def initialize(parent, commands, options = {}, &block) #:nodoc:
23 | super parent, options, &block
24 | @commands = commands
25 | end
26 |
27 | protected
28 |
29 | def install_commands #:nodoc:
30 | "#{executable} #{taskfile}#{@commands}"
31 | end
32 |
33 | def executable #:nodoc:
34 | "rake"
35 | end
36 |
37 | def taskfile #:nodoc:
38 | file = @options[:rakefile] || @options[:file]
39 | file ? "-f #{file} " : ""
40 | end
41 |
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/examples/packages/databases/mysql_source.rb:
--------------------------------------------------------------------------------
1 | # Install the latest MySQL database from source
2 | package :mysql do
3 | requires :mysql_dependencies, :mysql_user_group, :mysql_user, :mysql_core
4 | end
5 |
6 | package :mysql_dependencies do
7 | description 'MySQL dependencies'
8 | apt 'cmake'
9 | end
10 |
11 | package :mysql_user_group do
12 | description 'MySQL user group'
13 | group 'mysql'
14 | verify do
15 | has_group 'mysql'
16 | end
17 | end
18 |
19 | package :mysql_user do
20 | description 'MySQL user'
21 | requires :mysql_user_group
22 | runner 'useradd -r -g mysql mysql'
23 | verify do
24 | has_user 'mysql'
25 | end
26 | end
27 |
28 | package :mysql_core do
29 | description 'MySQL database'
30 | version '5.5.25a'
31 | requires :mysql_dependencies, :mysql_user_group, :mysql_user
32 | source "http://dev.mysql.com/get/Downloads/MySQL-5.5/mysql-#{version}.tar.gz/from/http://cdn.mysql.com/" do
33 | custom_archive "mysql-#{version}.tar.gz"
34 | configure_command 'cmake .'
35 | post :install, '/usr/local/mysql/scripts/mysql_install_db --user=mysql --basedir=/usr/local/mysql'
36 | post :install, 'chown -R mysql:mysql /usr/local/mysql/data'
37 | end
38 | verify do
39 | has_executable '/usr/local/mysql/bin/mysql'
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/freebsd_portinstall.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # = FreeBSD Portinstall Installer
4 | #
5 | # The Portinstall installer installs FreeBSD ports.
6 | # It uses the ports-mgmt/portupgrade port to install.
7 | # Before usage, the ports system must be installed and
8 | # read on the target operating system.
9 | # It is recommended to use `portsnap fetch extract` to
10 | # install the ports system.
11 | #
12 | # == Example Usage
13 | #
14 | # Installing the magic_beans port.
15 | #
16 | # package :magic_beans do
17 | # freebsd_portinstall 'magic/magic_beans'
18 | # end
19 | #
20 | class FreebsdPortinstall < Installer
21 | attr_accessor :port #:nodoc:
22 |
23 | api do
24 | def freebsd_portinstall(port, options={}, &block)
25 | install FreebsdPortinstall.new(self, port, options, &block)
26 | end
27 | end
28 |
29 | def initialize(parent, port, options={}, &block) #:nodoc:
30 | super parent, options, &block
31 | @port = port
32 | end
33 |
34 | protected
35 |
36 | def install_commands #:nodoc:
37 | "portinstall --batch #{@port}"
38 | end
39 |
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/mac_port.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The MacPort installer installs macports ports.
4 | #
5 | # == Example Usage
6 | #
7 | # Installing the magic_beans port.
8 | #
9 | # package :magic_beans do
10 | # mac_port 'magic/magic_beans'
11 | # end
12 | #
13 | # == Notes
14 | #
15 | # Before MacPorts packages can be installed, the PATH
16 | # environment variable probably has to be changed so
17 | # Sprinkle can find the /opt/local/bin/port executable
18 | #
19 | # You must set PATH in ~/.ssh/environment on the remote
20 | # system and enable 'PermitUserEnvironment yes' in /etc/sshd_config
21 | #
22 | class MacPort < Installer
23 |
24 | api do
25 | def mac_port(port, options={}, &block)
26 | install MacPort.new(self, port, options, &block)
27 | end
28 | end
29 |
30 | attr_accessor :port #:nodoc:
31 |
32 | def initialize(parent, port, options = {}, &block) #:nodoc:
33 | super parent, options, &block
34 | @port = port
35 | end
36 |
37 | protected
38 |
39 | def install_commands #:nodoc:
40 | "port install #{@port}"
41 | end
42 |
43 | end
44 | end
45 | end
--------------------------------------------------------------------------------
/spec/sprinkle/installers/freebsd_portinstall_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::FreebsdPortinstall do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'package')
7 | end
8 |
9 | def create_port(ports, &block)
10 | Sprinkle::Installers::FreebsdPortinstall.new(@package, ports, &block)
11 | end
12 |
13 | describe 'when created' do
14 |
15 | it 'should accept a single package to install' do
16 | @installer = create_port 'lang/ruby'
17 | @installer.port.should == 'lang/ruby'
18 | end
19 |
20 | end
21 |
22 | describe 'during installation' do
23 |
24 | before do
25 | @installer = create_port 'lang/ruby' do
26 | pre :install, 'op1'
27 | post :install, 'op2'
28 | end
29 | @install_commands = @installer.send :install_commands
30 | end
31 |
32 | it 'should invoke the port installer for all specified packages' do
33 | @install_commands.should =~ /portinstall --batch lang\/ruby/
34 | end
35 |
36 | it 'should automatically insert pre/post commands for the specified package' do
37 | @installer.send(:install_sequence).should == [ 'op1', 'portinstall --batch lang/ruby', 'op2' ]
38 | end
39 |
40 | end
41 |
42 | end
43 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/package_installer.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # This is a abstract class installer that most all the package installers
4 | # inherit from (deb, *BSD pkg, rpm, etc)
5 | class PackageInstaller < Installer
6 |
7 | # holds the list of packages to be installed
8 | attr_accessor :packages
9 |
10 | def initialize(parent, *packages, &block) #:nodoc:
11 | options = packages.extract_options!
12 | super parent, options, &block
13 | @packages = [*packages].flatten
14 | end
15 |
16 | # automatically sets up the api for package installation based on the class name
17 | #
18 | # Apt becomes the method `apt`, etc
19 | def self.auto_api(*args)
20 | method_name = args.first || self.to_s.underscore.split("/").last
21 | class_name = self.to_s
22 | api do
23 | method="def #{method_name}(*names, &block)
24 | install #{class_name}.new(self, *names, &block)
25 | end"
26 | eval(method)
27 | end
28 | end
29 |
30 | # called by subclasses of PackageInstaller
31 | def install_package(*names, &block) #:nodoc:
32 | install Sprinkle::Installers::class.new(self, *names, &block)
33 | end
34 |
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/openbsd_pkg.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The OpenBSD package installer installs OpenBSD packages.
4 | #
5 | # == Example Usage
6 | #
7 | # Installing the magic_beans package.
8 | #
9 | # package :magic_beans do
10 | # openbsd_pkg 'magic_beans'
11 | # end
12 | #
13 | # You may also specify multiple packages as an array:
14 | #
15 | # package :magic_beans do
16 | # openbsd_pkg %w(magic_beans magic_sauce)
17 | # end
18 | #
19 | # == Notes
20 | # Before OpenBSD packages can be installed, the PKG_PATH
21 | # environment variable must be set.
22 | #
23 | # You must set PKG_PATH in ~/.ssh/environment on the remote
24 | # system and enable 'PermitUserEnvironment yes' in /etc/ssh/sshd_config
25 | #
26 | # For help on PKG_PATH see section 15.2.2 of the OpenBSD FAQ
27 | # (http://www.openbsd.org/faq/faq15.html)
28 | class OpenbsdPkg < PackageInstaller
29 |
30 | ##
31 | # installs the OpenBSD packages passed
32 | # :method: openbsd_pkg
33 | # :call-seq: openbsd_pkg(*packages)
34 | auto_api
35 |
36 | protected
37 |
38 | def install_commands #:nodoc:
39 | "pkg_add #{@packages.join(' ')}"
40 | end
41 |
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/bsd_port_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::BsdPort do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'package')
7 | end
8 |
9 | def create_port(ports, &block)
10 | Sprinkle::Installers::BsdPort.new(@package, ports, &block)
11 | end
12 |
13 | describe 'when created' do
14 |
15 | it 'should accept a single package to install' do
16 | @installer = create_port 'lang/ruby'
17 | @installer.port.should == 'lang/ruby'
18 | end
19 |
20 | end
21 |
22 | describe 'during installation' do
23 |
24 | before do
25 | @installer = create_port 'lang/ruby' do
26 | pre :install, 'op1'
27 | post :install, 'op2'
28 | end
29 | @install_commands = @installer.send :install_commands
30 | end
31 |
32 | it 'should invoke the port installer for all specified packages' do
33 | @install_commands.should =~ /cd \/usr\/ports\/lang\/ruby && make BATCH=yes install clean/
34 | end
35 |
36 | it 'should automatically insert pre/post commands for the specified package' do
37 | @installer.send(:install_sequence).should == [ 'op1', 'sh -c \'cd /usr/ports/lang/ruby && make BATCH=yes install clean\'', 'op2' ]
38 | end
39 |
40 | end
41 |
42 | end
43 |
--------------------------------------------------------------------------------
/lib/sprinkle/package/package_repository.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle::Package
2 | class PackageRepository #:nodoc:
3 |
4 | # sets up an empty repository
5 | def initialize
6 | clear
7 | end
8 |
9 | def clear
10 | @packages = []
11 | end
12 |
13 | # adds a single package to the repository
14 | def add(package)
15 | @packages << package
16 | end
17 | def <<(package); add(package); end
18 |
19 | # returns the first package matching the name and options given
20 | def first(name, opts={})
21 | find_all(name, opts).try(:first)
22 | end
23 |
24 | # returns all packages matching the name and options given (including via provides)
25 | def find_all(name, opts={})
26 | # opts ||= {}
27 | all = [@packages.select {|x| x.name.to_s == name.to_s },
28 | find_all_by_provides(name, opts)].flatten.compact
29 | filter(all, opts)
30 | end
31 |
32 | def count
33 | @packages.size
34 | end
35 |
36 | private
37 |
38 | def find_all_by_provides(name, opts={})
39 | @packages.select {|x| x.provides and x.provides.to_s == name.to_s }
40 | end
41 |
42 | def filter(all, opts)
43 | all = all.select {|x| "#{x.version}" == opts[:version].to_s} if opts[:version]
44 | all
45 | end
46 |
47 | end
48 | end
--------------------------------------------------------------------------------
/lib/sprinkle/installers/user.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The user installer add users. You may pass :flags as an option.
4 | #
5 | # == Example Usage
6 | #
7 | # package :users do
8 | # add_user 'admin', :flags => "--disabled-password"
9 | #
10 | # verify do
11 | # has_user 'admin', :in_group => "root"
12 | # end
13 | # end
14 | class User < Installer
15 |
16 | api do
17 | def add_user(username, options={}, &block)
18 | install User.new(self, username, options, &block)
19 | end
20 | end
21 |
22 | verify_api do
23 | def has_user(user, opts = {})
24 | if opts[:in_group]
25 | @commands << "id -nG #{user} | xargs -n1 echo | grep #{opts[:in_group]}"
26 | else
27 | @commands << "id #{user}"
28 | end
29 | end
30 | end
31 |
32 | def initialize(package, username, options = {}, &block) #:nodoc:
33 | super package, options, &block
34 | @username = username
35 | end
36 |
37 | protected
38 |
39 | def install_commands #:nodoc:
40 | noninteractive = " --gecos ,,,"
41 | flags = @options[:flags] || ""
42 | flags << noninteractive unless flags =~ /--gecos/
43 | "#{sudo_cmd}adduser #{flags.strip} #{@username}"
44 | end
45 |
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/spec/sprinkle/package/package_repository_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Package::PackageRepository do
4 |
5 | before do
6 | @repository = PackageRepository.new {}
7 | @test_package = Package.new(:test) {}
8 | @mysql_package = Package.new(:mysql, :provides => :db) {}
9 | @test_v2_package = Package.new(:test) do
10 | version "2"
11 | end
12 | @another_package = Package.new(:another) {}
13 | end
14 |
15 | it 'should allow adding a package' do
16 | @repository.add @test_package
17 | @repository.count.should eq 1
18 | end
19 |
20 | it 'should allow clearing' do
21 | @repository.add @test_package
22 | @repository.clear
23 | @repository.count.should eq 0
24 | end
25 |
26 | it "should find by provides" do
27 | @repository.add @mysql_package
28 | @repository.find_all("db").should eq [ @mysql_package ]
29 | end
30 |
31 | it "should find by name" do
32 | @repository.add @test_package
33 | @repository.find_all("test").should eq [ @test_package ]
34 | end
35 |
36 | it "should filter by version" do
37 | @repository.add @test_package
38 | @repository.add @test_v2_package
39 | @repository.find_all("test").size.should eq 2
40 | @repository.first("test", :version => "2").should eq @test_v2_package
41 | end
42 |
43 | after do
44 | end
45 |
46 | end
47 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/openbsd_pkg_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::OpenbsdPkg do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'package')
7 | end
8 |
9 | def create_pkg(pkgs, &block)
10 | Sprinkle::Installers::OpenbsdPkg.new(@package, pkgs, &block)
11 | end
12 |
13 | describe 'when created' do
14 |
15 | it 'should accept a single package to install' do
16 | @installer = create_pkg 'ruby'
17 | @installer.packages.should == [ 'ruby' ]
18 | end
19 |
20 | it 'should accept an array of packages to install' do
21 | @installer = create_pkg %w( gcc gdb g++ )
22 | @installer.packages.should == ['gcc', 'gdb', 'g++']
23 | end
24 |
25 | end
26 |
27 | describe 'during installation' do
28 |
29 | before do
30 | @installer = create_pkg 'ruby' do
31 | pre :install, 'op1'
32 | post :install, 'op2'
33 | end
34 | @install_commands = @installer.send :install_commands
35 | end
36 |
37 | it 'should invoke the freebsd_pkg installer for all specified packages' do
38 | @install_commands.should =~ /pkg_add ruby/
39 | end
40 |
41 | it 'should automatically insert pre/post commands for the specified package' do
42 | @installer.send(:install_sequence).should == [ 'op1', 'pkg_add ruby', 'op2' ]
43 | end
44 |
45 | end
46 |
47 | end
48 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/freebsd_pkg_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::FreebsdPkg do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'package')
7 | end
8 |
9 | def create_pkg(pkgs, &block)
10 | Sprinkle::Installers::FreebsdPkg.new(@package, pkgs, &block)
11 | end
12 |
13 | describe 'when created' do
14 |
15 | it 'should accept a single package to install' do
16 | @installer = create_pkg 'ruby'
17 | @installer.packages.should == [ 'ruby' ]
18 | end
19 |
20 | it 'should accept an array of packages to install' do
21 | @installer = create_pkg %w( gcc gdb g++ )
22 | @installer.packages.should == ['gcc', 'gdb', 'g++']
23 | end
24 |
25 | end
26 |
27 | describe 'during installation' do
28 |
29 | before do
30 | @installer = create_pkg 'ruby' do
31 | pre :install, 'op1'
32 | post :install, 'op2'
33 | end
34 | @install_commands = @installer.send :install_commands
35 | end
36 |
37 | it 'should invoke the freebsd_pkg installer for all specified packages' do
38 | @install_commands.should =~ /pkg_add -r ruby/
39 | end
40 |
41 | it 'should automatically insert pre/post commands for the specified package' do
42 | @installer.send(:install_sequence).should == [ 'op1', 'pkg_add -r ruby', 'op2' ]
43 | end
44 |
45 | end
46 |
47 | end
48 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/opensolaris_pkg_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::OpensolarisPkg do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'package')
7 | end
8 |
9 | def create_pkg(pkgs, &block)
10 | Sprinkle::Installers::OpensolarisPkg.new(@package, pkgs, &block)
11 | end
12 |
13 | describe 'when created' do
14 |
15 | it 'should accept a single package to install' do
16 | @installer = create_pkg 'ruby'
17 | @installer.packages.should == [ 'ruby' ]
18 | end
19 |
20 | it 'should accept an array of packages to install' do
21 | @installer = create_pkg %w( gcc gdb g++ )
22 | @installer.packages.should == ['gcc', 'gdb', 'g++']
23 | end
24 |
25 | end
26 |
27 | describe 'during installation' do
28 |
29 | before do
30 | @installer = create_pkg 'ruby' do
31 | pre :install, 'op1'
32 | post :install, 'op2'
33 | end
34 | @install_commands = @installer.send :install_commands
35 | end
36 |
37 | it 'should invoke the freebsd_pkg installer for all specified packages' do
38 | @install_commands.should =~ /pkg install ruby/
39 | end
40 |
41 | it 'should automatically insert pre/post commands for the specified package' do
42 | @installer.send(:install_sequence).should == [ 'op1', 'pkg install ruby', 'op2' ]
43 | end
44 |
45 | end
46 |
47 | end
48 |
--------------------------------------------------------------------------------
/spec/sprinkle/actors/local_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Actors::Local do
4 |
5 | before do
6 | @local = Sprinkle::Actors::Local.new
7 |
8 | @package = Package.new("super") {}
9 | end
10 |
11 | describe 'when installing' do
12 |
13 | before do
14 | @installer = Sprinkle::Installers::Runner.new(@package, "echo hi")
15 | @commands = %w( op1 op2 )
16 | @roles = %w( app )
17 | @name = 'name'
18 |
19 | @local.stub(:run_command).and_return(0)
20 | end
21 |
22 | it 'should run the commands on the local system' do
23 | @local.should_receive(:run_command).once.and_return(0)
24 | @local.install @installer, @roles
25 | end
26 |
27 | end
28 |
29 | describe 'when verifying' do
30 |
31 | before do
32 | @verifier = Sprinkle::Verify::new(@package) {}
33 | @verifier.commands.concat ["test","test"]
34 | @roles = %w( app )
35 | @name = 'name'
36 | end
37 |
38 | it 'should return false when verification fails' do
39 | @local.stub(:run_command).and_return(1)
40 | res = @local.verify @verifier, @roles
41 | res.should == false
42 | end
43 |
44 | it 'should run the commands on the local system' do
45 | @local.stub(:run_command).and_return(0)
46 | res = @local.verify @verifier, @roles
47 | res.should == true
48 | end
49 |
50 | end
51 |
52 | end
53 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/user_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::User do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'spec', :sudo? => false)
7 | @user = "bob"
8 | end
9 |
10 | def create_user(name, options = {}, &block)
11 | Sprinkle::Installers::User.new @package, name, options, &block
12 | end
13 |
14 | describe 'during installation' do
15 |
16 | it "should invoke add user" do
17 | @installer = create_user 'bob'
18 | @install_commands = @installer.send :install_commands
19 | @install_commands.should == "adduser --gecos ,,, bob"
20 | end
21 |
22 | it "should merge flags" do
23 | @installer = create_user 'bob', :flags => "-x"
24 | @install_commands = @installer.send :install_commands
25 | @install_commands.should == "adduser -x --gecos ,,, bob"
26 | end
27 |
28 | it "should use actual gecos options if passed" do
29 | @installer = create_user 'bob', :flags => "--gecos bob,,,"
30 | @install_commands = @installer.send :install_commands
31 | @install_commands.should == "adduser --gecos bob,,, bob"
32 | end
33 |
34 | it "should use sudo if sudo specified" do
35 | @installer = create_user 'bob', :sudo => true
36 | @install_commands = @installer.send :install_commands
37 | @install_commands.should == "sudo adduser --gecos ,,, bob"
38 | end
39 |
40 | end
41 |
42 | end
43 |
--------------------------------------------------------------------------------
/sprinkle.gemspec:
--------------------------------------------------------------------------------
1 | require "./lib/sprinkle/version"
2 | require 'date'
3 |
4 | Gem::Specification.new do |s|
5 | s.name = "sprinkle"
6 | s.version = Sprinkle::Version
7 |
8 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9 | s.authors = ["Marcus Crafter", "Josh Goebel"]
10 | s.date = Date.today
11 | s.description = "Ruby DSL based software provisioning tool"
12 | s.email = "crafterm@redartisan.com"
13 | s.executables = ["sprinkle"]
14 | s.extra_rdoc_files = [
15 | "README.md"
16 | ]
17 |
18 | s.license = "MIT"
19 |
20 | s.files = `git ls-files`.split("\n")
21 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23 |
24 | s.homepage = "https://github.com/sprinkle-tool/sprinkle"
25 | s.require_paths = ["lib"]
26 | s.rubyforge_project = "sprinkle"
27 | s.rubygems_version = "1.8.15"
28 | s.summary = "Ruby DSL based software provisioning tool"
29 |
30 | s.add_development_dependency(%q, [">= 2.5"])
31 | s.add_development_dependency(%q, [">= 0.8.7"])
32 | s.add_development_dependency(%q, [">= 3.12"])
33 | s.add_runtime_dependency(%q, [">= 2.7.0"])
34 | s.add_runtime_dependency(%q, [">= 1.1.0"])
35 | s.add_runtime_dependency(%q, [">= 2.0.2"])
36 | s.add_runtime_dependency(%q, [">= 1.4.0"])
37 | s.add_runtime_dependency(%q, [">= 2.5.5", '< 3'])
38 |
39 | end
40 |
41 |
--------------------------------------------------------------------------------
/lib/sprinkle/extensions/attributes.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Attributes
3 | extend ActiveSupport::Concern
4 |
5 | included do
6 | attr_accessor :delivery
7 | end
8 |
9 | def defaults(deployment)
10 | defaults = deployment.defaults[self.class.name.split(/::/).last.downcase.to_sym]
11 | self.set_defaults(&defaults) if defaults
12 | @delivery = deployment.style
13 | end
14 |
15 | def set_defaults(&block)
16 | before = @options
17 | @options = {}
18 | self.instance_eval(&block) if block
19 | @options = before.reverse_merge(@options)
20 | end
21 |
22 | private
23 |
24 | def read_from_package(m)
25 | @package.send(m) if @package.respond_to?(m) and @package.method(m).arity.abs < 2
26 | end
27 |
28 | def option?(sym)
29 | !!@options[sym]
30 | end
31 |
32 | module ClassMethods
33 |
34 | def attributes(*list)
35 | list.each do |a|
36 | define_method a do |*val|
37 | val=nil if val.empty?
38 | val ? @options[a] = val.first : @options[a] || read_from_package(a)
39 | end
40 | end
41 | end
42 |
43 | def multi_attributes(*list)
44 | list.each do |a|
45 | define_method a do |*val|
46 | val = val.try(:first)
47 | return @options[a] unless val
48 | @options[a]||=[]
49 | val.is_a?(Array) ? @options[a] += val : @options[a] << val
50 | end
51 | end
52 | end
53 | end
54 |
55 | end
56 | end
--------------------------------------------------------------------------------
/spec/sprinkle/installers/yum_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::Yum do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'package')
7 | end
8 |
9 | def create_rpm(rpms, &block)
10 | Sprinkle::Installers::Yum.new(@package, rpms, &block)
11 | end
12 |
13 | describe 'when created' do
14 |
15 | it 'should accept a single package to install' do
16 | @installer = create_rpm 'ruby'
17 | @installer.packages.should == [ 'ruby' ]
18 | end
19 |
20 | it 'should accept an array of packages to install' do
21 | @installer = create_rpm %w( gcc gdb g++ )
22 | @installer.packages.should == ['gcc', 'gdb', 'g++']
23 | end
24 |
25 | end
26 |
27 | describe 'during installation' do
28 |
29 | before do
30 | @installer = create_rpm 'ruby' do
31 | pre :install, 'op1'
32 | post :install, 'op2'
33 | end
34 | @install_commands = @installer.send :install_commands
35 | end
36 |
37 | it 'should invoke the rpm installer for all specified packages' do
38 | @install_commands.should =~ /yum install ruby -y/
39 | end
40 |
41 | it 'should automatically insert pre/post commands for the specified package' do
42 | @installer.send(:install_sequence).should == [ 'op1', 'yum install ruby -y', 'op2' ]
43 | end
44 |
45 | it 'should install a specific version if defined' do
46 | @installer = create_rpm 'ruby-2.0'
47 | @installer.send(:install_sequence).should == [ 'yum install ruby-2.0 -y' ]
48 | end
49 |
50 | end
51 |
52 | end
53 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/rpm_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::Rpm do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'package')
7 | end
8 |
9 | def create_rpm(debs, &block)
10 | Sprinkle::Installers::Rpm.new(@package, debs, &block)
11 | end
12 |
13 | describe 'when created' do
14 |
15 | it 'should accept a single package to install' do
16 | @installer = create_rpm 'ruby'
17 | @installer.packages.should == [ 'ruby' ]
18 | end
19 |
20 | it 'should accept an array of packages to install' do
21 | @installer = create_rpm %w( gcc gdb g++ )
22 | @installer.packages.should == ['gcc', 'gdb', 'g++']
23 | end
24 |
25 | it 'should accept a list of packages to install' do
26 | @installer = Sprinkle::Installers::Rpm.new(@package, "gcc", "gdb", "g++")
27 | @installer.packages.should == ['gcc', 'gdb', 'g++']
28 | end
29 |
30 | end
31 |
32 | describe 'during installation' do
33 |
34 | before do
35 | @installer = create_rpm 'ruby' do
36 | pre :install, 'op1'
37 | post :install, 'op2'
38 | end
39 | @install_commands = @installer.send :install_commands
40 | end
41 |
42 | it 'should invoke the rpm installer for all specified packages' do
43 | @install_commands.should =~ /rpm -Uvh ruby/
44 | end
45 |
46 | it 'should automatically insert pre/post commands for the specified package' do
47 | @installer.send(:install_sequence).should == [ 'op1', 'rpm -Uvh ruby', 'op2' ]
48 | end
49 |
50 | end
51 |
52 | end
53 |
--------------------------------------------------------------------------------
/lib/sprinkle/errors/template_error.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle::Errors
2 | # Blatantly stole this from Chef
3 | class TemplateError < RuntimeError #:nodoc:
4 | attr_reader :original_exception, :context
5 | SOURCE_CONTEXT_WINDOW = 2 unless defined? SOURCE_CONTEXT_WINDOW
6 |
7 | def initialize(original_exception, template, context)
8 | @original_exception, @template, @context = original_exception, template, context
9 | end
10 |
11 | def message
12 | @original_exception.message
13 | end
14 |
15 | def line_number
16 | @line_number ||= $1.to_i if original_exception.backtrace.find {|line| line =~ /\(erubis\):(\d+)/ }
17 | end
18 |
19 | def source_location
20 | "on line ##{line_number}"
21 | end
22 |
23 | def source_listing
24 | return nil if line_number.nil?
25 |
26 | @source_listing ||= begin
27 | line_index = line_number - 1
28 | beginning_line = line_index <= SOURCE_CONTEXT_WINDOW ? 0 : line_index - SOURCE_CONTEXT_WINDOW
29 | source_size = SOURCE_CONTEXT_WINDOW * 2 + 1
30 | lines = @template.split(/\n/)
31 | contextual_lines = lines[beginning_line, source_size]
32 | output = []
33 | contextual_lines.each_with_index do |line, index|
34 | line_number = (index+beginning_line+1).to_s.rjust(3)
35 | output << "#{line_number}: #{line}"
36 | end
37 | output.join("\n")
38 | end
39 | end
40 |
41 | def to_s
42 | "\n\n#{self.class} (#{message}) #{source_location}:\n\n" +
43 | "#{source_listing}\n\n #{original_exception.backtrace.join("\n ")}\n\n"
44 | end
45 | end
46 | end
--------------------------------------------------------------------------------
/spec/sprinkle/installers/zypper_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::Zypper do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'package')
7 | end
8 |
9 | def create_zypper(*packages, &block)
10 | Sprinkle::Installers::Zypper.new(@package, *packages, &block)
11 | end
12 |
13 | describe 'when created' do
14 |
15 | it 'should accept a single package to install' do
16 | @installer = create_zypper 'ruby'
17 | @installer.packages.should == [ 'ruby' ]
18 | end
19 |
20 | it 'should accept an array of packages to install' do
21 | @installer = create_zypper %w( gcc gdb g++ )
22 | @installer.packages.should == ['gcc', 'gdb', 'g++']
23 | end
24 |
25 | it 'should accept an argument list of packages to install' do
26 | @installer = create_zypper "gcc", "gdb", "g++"
27 | @installer.packages.should == ['gcc', 'gdb', 'g++']
28 | end
29 | end
30 |
31 | describe 'during installation' do
32 |
33 | before do
34 | @installer = create_zypper 'ruby', 'rubygems' do
35 | pre :install, 'op1'
36 | post :install, 'op2'
37 | end
38 | @install_commands = @installer.send :install_commands
39 | end
40 |
41 | it 'should invoke the zypper installer for all specified packages' do
42 | @install_commands.should =~ /zypper -n install -l -R ruby rubygems/
43 | end
44 |
45 | it 'should automatically insert pre/post commands for the specified package' do
46 | @installer.send(:install_sequence).should == [ 'op1', 'zypper -n install -l -R ruby rubygems', 'op2' ]
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/smart_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::Smart do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'package')
7 | end
8 |
9 | def create_smart(pkgs, &block)
10 | Sprinkle::Installers::Smart.new(@package, pkgs, &block)
11 | end
12 |
13 | describe 'when created' do
14 |
15 | it 'should accept a single package to install' do
16 | @installer = create_smart 'ruby'
17 | @installer.packages.should == [ 'ruby' ]
18 | end
19 |
20 | it 'should accept an array of packages to install' do
21 | @installer = create_smart %w( gcc gdb g++ )
22 | @installer.packages.should == ['gcc', 'gdb', 'g++']
23 | end
24 |
25 | it 'should accept a list of packages to install' do
26 | @installer = Sprinkle::Installers::Smart.new(@package, "gcc", "gdb", "g++")
27 | @installer.packages.should == ['gcc', 'gdb', 'g++']
28 | end
29 |
30 | end
31 |
32 | describe 'during installation' do
33 |
34 | before do
35 | @installer = create_smart 'ruby' do
36 | pre :install, 'op1'
37 | post :install, 'op2'
38 | end
39 | @install_commands = @installer.send :install_commands
40 | end
41 |
42 | it 'should invoke the rpm installer for all specified packages' do
43 | @install_commands.should == "smart install ruby -y 2>&1 | tee -a /var/log/smart-sprinkle"
44 | end
45 |
46 | it 'should automatically insert pre/post commands for the specified package' do
47 | @installer.send(:install_sequence).should == [ 'op1',
48 | 'smart install ruby -y 2>&1 | tee -a /var/log/smart-sprinkle', 'op2' ]
49 | end
50 |
51 | end
52 |
53 | end
54 |
--------------------------------------------------------------------------------
/lib/sprinkle/verifiers/executable.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Verifiers
3 | # = Executable Verifier
4 | #
5 | # Contains a verifier to check the existance of an executable
6 | # script on your server.
7 | #
8 | # == Example Usage
9 | #
10 | # First, absolute path to an executable:
11 | #
12 | # verify { has_executable '/usr/special/secret/bin/scipt' }
13 | #
14 | # Second, a global executable which would be available anywhere on the
15 | # command line:
16 | #
17 | # verify { has_executable 'grep' }
18 | module Executable
19 | Sprinkle::Verify.register(Sprinkle::Verifiers::Executable)
20 |
21 | # Checks if path is an executable script using which
22 | # - accepts both absolute paths and binary names with no path
23 | def has_executable(path)
24 | @commands << "which #{path}"
25 | end
26 |
27 | # Same as has_executable but with checking for e certain version number.
28 | # Last option is the parameter to append for getting the version (which
29 | # defaults to "-v").
30 | def has_executable_with_version(path, version, get_version = '-v')
31 | if path.include?('/')
32 | @commands << "[ -x #{path} -a -n \"`#{path} #{get_version} 2>&1 | egrep -e \\\"#{version}\\\"`\" ]"
33 | else
34 | @commands << "[ -n \"`echo \\`which #{path}\\``\" -a -n \"`\\`which #{path}\\` #{get_version} 2>&1 | egrep -e \\\"#{version}\\\"`\" ]"
35 | end
36 | end
37 |
38 | # Same as has_executable but checking output of a certain command
39 | # with grep.
40 | def has_version_in_grep(cmd, version)
41 | @commands << "[ -n \"`#{cmd} 2> /dev/null | egrep -e \\\"#{version}\\\"`\" ]"
42 | end
43 | end
44 | end
45 | end
--------------------------------------------------------------------------------
/lib/sprinkle.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'active_support/all'
3 |
4 | if ActiveSupport::VERSION::MAJOR > 2
5 | require 'active_support/dependencies'
6 | ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
7 | else
8 | ActiveSupport::Dependencies.load_paths << File.dirname(__FILE__)
9 | end
10 |
11 | # Configure active support to log auto-loading of dependencies
12 | #ActiveSupport::Dependencies::RAILS_DEFAULT_LOGGER = Logger.new($stdout)
13 | #ActiveSupport::Dependencies.log_activity = true
14 |
15 | def require_all(*args) # :nodoc:
16 | args.each { |f|
17 | Dir[File.dirname(__FILE__) + "/sprinkle/#{f}"].each { |e| require e } }
18 | end
19 |
20 | $LOAD_PATH << File.dirname(__FILE__)
21 |
22 | require 'sprinkle/version'
23 |
24 | require_all "extensions/*.rb"
25 |
26 | require 'sprinkle/package'
27 | require 'sprinkle/verify'
28 | require 'sprinkle/installers/installer'
29 | require 'sprinkle/installers/package_installer'
30 | require_all "verifiers/*.rb", "installers/*.rb"
31 |
32 | module Sprinkle
33 | # Configuration options
34 | OPTIONS = { :testing => false, :verbose => false, :force => false }
35 |
36 | module Logger
37 | def logger # :nodoc:
38 | # ActiveSupport::BufferedLogger was deprecated and replaced by ActiveSupport::Logger in Rails 4.
39 | # Use ActiveSupport::Logger if available.
40 | active_support_logger = defined?(ActiveSupport::Logger) ? ActiveSupport::Logger : ActiveSupport::BufferedLogger
41 | @@__log__ ||= active_support_logger.new($stdout, active_support_logger::Severity::INFO)
42 | end
43 | end
44 | end
45 |
46 | # Object is extended with a few helper methods. Please see Sprinkle::Core.
47 | #--
48 | # Define a logging target and understand packages, policies and deployment DSL
49 | #++
50 | class Object
51 | include Sprinkle::Package, Sprinkle::Core, Sprinkle::Logger
52 | end
53 |
--------------------------------------------------------------------------------
/spec/sprinkle/script_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Script, 'class' do
4 |
5 | it 'should define a entry point into the system' do
6 | Sprinkle::Script.should respond_to(:sprinkle)
7 | end
8 |
9 | it 'should respond to deployment' do
10 | Sprinkle::Script.new.should respond_to(:deployment)
11 | end
12 |
13 | end
14 |
15 | describe Sprinkle::Script, 'when given a script' do
16 |
17 | before do
18 | @script = 'script'
19 | @filename = 'filename'
20 |
21 | @sprinkle = Sprinkle::Script.new
22 | Sprinkle::Script.stub(:new).and_return(@sprinkle)
23 | end
24 |
25 | it 'should create a new sprinkle instance' do
26 | Sprinkle::Script.should_receive(:new).and_return(@sprinkle)
27 | Sprinkle::Script.sprinkle @script
28 | end
29 |
30 | it 'should evaulate the sprinkle script against the instance' do
31 | @sprinkle.should_receive(:instance_eval).and_return
32 | Sprinkle::Script.sprinkle @script
33 | end
34 |
35 | it 'should specify the filename if given for line number errors' do
36 | @sprinkle.should_receive(:instance_eval).with(@script, @filename).and_return
37 | Sprinkle::Script.sprinkle @script, @filename
38 | end
39 |
40 | it 'should specify a filename of __SCRIPT__ by default if none is provided' do
41 | @sprinkle.should_receive(:instance_eval).with(@script, '__SCRIPT__').and_return
42 | Sprinkle::Script.sprinkle @script
43 | end
44 |
45 | it 'should automatically run in production mode by default' do
46 | @sprinkle.should_receive(:instance_eval).with(@script, '__SCRIPT__').and_return
47 | Sprinkle::Script.sprinkle @script
48 | end
49 |
50 | it 'should ask the Sprinkle instance to process the data from the script' do
51 | @sprinkle.should_receive(:sprinkle)
52 | Sprinkle::Script.sprinkle @script
53 | end
54 |
55 | end
56 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/replace_text.rb:
--------------------------------------------------------------------------------
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 | # privileges, 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 | api do
30 | def replace_text(regex, text, path, options={}, &block)
31 | install ReplaceText.new(self, regex, text, path, options, &block)
32 | end
33 | end
34 |
35 | def initialize(parent, regex, text, path, options={}, &block) #:nodoc:
36 | super parent, options, &block
37 | @regex = regex
38 | @text = text
39 | @path = path
40 | end
41 |
42 | def announce
43 | log "--> Replace '#{@regex}' with '#{@text}' in file #{@path}"
44 | end
45 |
46 | protected
47 |
48 | def escape_sed_arg(s)
49 | escape_shell_arg(s).gsub("/", "\\\\/").gsub('&', '\\\&')
50 | end
51 |
52 | def install_commands #:nodoc:
53 | "#{sudo_cmd}sed -i 's/#{escape_sed_arg(@regex)}/#{escape_sed_arg(@text)}/g' #{@path}"
54 | end
55 |
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/CREDITS:
--------------------------------------------------------------------------------
1 | = CREDITS
2 |
3 | Many thanks to the following people who have submitted ideas, patches, helped with
4 | testing and/or generally provided support to Sprinkle, I really appreciate your help:
5 |
6 | Koen Punt (http://github.com/koenpunt)
7 | Manuel Meurer (http://github.com/manuelmeurer)
8 | Kristin Baumann (http://crafterm.net/kristin/blog/)
9 | Ben Schwarz (http://germanforblack.com/)
10 | Jim Freeze (http://www.artima.com/rubycs/articles/ruby_as_dslP.html)
11 | Matthew Tanase (http://www.slicehost.com)
12 | Jared Kuolt (http://www.slicehost.com)
13 | Jamis Buck (http://www.capify.org)
14 | Matt Allen (http://blog.allen.com.au)
15 | Eric Hodel (http://blog.segment7.net)
16 | Pete Yandell (http://notahat.com)
17 | Adam Meehan (http://duckpunching.com)
18 | Mitchell Hashimoto (http://mitchellhashimoto.com)
19 | Ari Lerner (http://www.citrusbyte.com)
20 | Jorgen Orehøj Erichsen (http://blog.erichsen.net)
21 | Joshua Sierles (http://diluvia.net)
22 | Julian Russell (http://github.com/plusplus)
23 | Dave (Gassto) (http://github.com/gassto)
24 | Bodaniel Jeanes (http://bjeanes.github.com)
25 | Jacob Harris (http://open.nytimes.com)
26 | Justin Pease (http://jit.nuance9.com)
27 | Tobias Lütke (http://blog.leetsoft.com)
28 | Josh Reynolds (http://github.com/jreynolds)
29 | Jan Ulbrich (http://www.ulbrich.net)
30 | Chris Gaffney (http://github.com/gaffneyc)
31 | Maxmpz (http://github.com/maxmpz)
32 | Geoff Garside (http://geoffgarside.co.uk/)
33 | Oliver Kiessler (http://inceedo.com)
34 | Nick Plante (http://blog.zerosum.org)
35 |
36 | The transfer installer contains a piece of exception reporting code copied from
37 | the Chef library (http://wiki.opscode.com/display/chef/Home)
38 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/runner.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The runner installer is great for running a simple command.
4 | #
5 | # == Example Usage
6 | #
7 | # package :magic_beans do
8 | # runner "make world"
9 | # end
10 | #
11 | # You can also pass multiple commands as arguments or an array.
12 | #
13 | # package :magic_beans do
14 | # runner "make world", "destroy world"
15 | # runner [ "make world", "destroy world" ]
16 | # end
17 | #
18 | # Environment variables can be supplied throught the :env option.
19 | #
20 | # package :magic_beans do
21 | # runner "make world", :env => {
22 | # :PATH => '/this/is/my/path:$PATH'
23 | # }
24 | # end
25 | #
26 | class Runner < Installer
27 |
28 | api do
29 | def runner(*cmds, &block)
30 | options = cmds.extract_options!
31 | install Runner.new(self, cmds, options, &block)
32 | end
33 |
34 | # runs 'echo noop' on the remote host
35 | def noop
36 | install Runner.new(self, "echo noop")
37 | end
38 | end
39 |
40 | attr_accessor :cmds #:nodoc:
41 | def initialize(parent, cmds, options = {}, &block) #:nodoc:
42 | super parent, options, &block
43 | @env = options.delete(:env)
44 | @cmds = [*cmds].flatten
45 | raise "you need to specify a command" if cmds.nil?
46 | end
47 |
48 | protected
49 |
50 | def env_str #:nodoc:
51 | @env_str ||= @env.inject("env ") do |s, (k,v)|
52 | s << "#{k.to_s.upcase}=#{v} "
53 | end
54 | end
55 |
56 | def install_commands #:nodoc:
57 | cmds = @env ? @cmds.map { |cmd| "#{env_str}#{cmd}"} : @cmds
58 |
59 | sudo? ?
60 | cmds.map { |cmd| "#{sudo_cmd}#{cmd}"} :
61 | cmds
62 | end
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/lib/sprinkle/actors/dummy.rb:
--------------------------------------------------------------------------------
1 | require 'capistrano/cli'
2 | require 'pp'
3 |
4 | module Sprinkle
5 | module Actors
6 | class Dummy < Actor #:nodoc:
7 |
8 | attr_accessor :per_host
9 |
10 | def initialize(&block) #:nodoc:
11 | # @config.set(:_sprinkle_actor, self)
12 | @roles={}
13 | self.instance_eval(&block)
14 | end
15 |
16 | def role(role, server, opts={})
17 | @roles[role]||=[]
18 | @roles[role] << [ server, opts ]
19 | end
20 |
21 | # Determines if there are any servers for the given roles
22 | def servers_for_role?(roles)
23 | roles=Array(roles)
24 | roles.any? { |r| @roles.keys.include?(r) }
25 | end
26 |
27 | def install(installer, roles, opts={})
28 | @installer = installer
29 | if self.per_host=opts.delete(:per_host)
30 | servers_per_role(roles).each do |server|
31 | installer.reconfigure_for(server)
32 | installer.announce
33 | process(installer.package.name, installer.install_sequence, server, opts)
34 | end
35 | else
36 | process(installer.package, installer.install_sequence, roles, opts)
37 | end
38 | end
39 |
40 | def sudo?
41 | false
42 | end
43 |
44 | def verify(verifier, roles, opts = {})
45 | process(verifier.package.name, verifier.commands, roles, opts = {})
46 | end
47 |
48 | def servers_per_role(role)
49 | @roles[role]
50 | end
51 |
52 | def print_command(cmd)
53 | puts cmd.inspect
54 | end
55 |
56 | def process(name, commands, roles, opts = {}) #:nodoc:
57 | # puts "PROCESS: #{name} on #{roles}"
58 | commands.each do |cmd|
59 | print_command(cmd)
60 | end
61 | # return false if suppress_and_return_failures
62 | true
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/apt.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The Apt package installer uses the +apt-get+ command to install
4 | # packages. The apt installer has only one option which can be
5 | # modified which is the +dependencies_only+ option. When this is
6 | # set to true, the installer uses +build-dep+ instead of +install+
7 | # to only build the dependencies.
8 | #
9 | # == Example Usage
10 | #
11 | # First, a simple installation of the magic_beans package:
12 | #
13 | # package :magic_beans do
14 | # apt 'magic_beans_package'
15 | # verify { has_apt 'magic_beans_package' }
16 | # end
17 | #
18 | # Second, only build the magic_beans dependencies:
19 | #
20 | # package :magic_beans_depends do
21 | # apt 'magic_beans_package' do
22 | # dependencies_only true
23 | # end
24 | # end
25 | #
26 | # As you can see, setting options is as simple as creating a
27 | # block and calling the option as a method with the value as
28 | # its parameter.
29 | class Apt < PackageInstaller
30 | def initialize(parent, *packages, &block) #:nodoc:
31 | super parent, *packages, &block
32 | @options.reverse_merge!(:dependencies_only => false)
33 | end
34 |
35 | attributes :dependencies_only
36 |
37 | auto_api
38 |
39 | verify_api do
40 | def has_apt(package)
41 | @commands << "dpkg --status #{package} | grep \"ok installed\""
42 | end
43 | end
44 |
45 | protected
46 |
47 | def install_commands #:nodoc:
48 | command = @options[:dependencies_only] ? 'build-dep' : 'install'
49 | noninteractive = "#{sudo_cmd}env DEBCONF_TERSE='yes' DEBIAN_PRIORITY='critical' DEBIAN_FRONTEND=noninteractive"
50 | "#{noninteractive} apt-get --force-yes -qyu #{command} #{@packages.join(' ')}"
51 | end
52 |
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/brew_spec.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/../../spec_helper'
2 |
3 | describe Sprinkle::Installers::Brew do
4 |
5 | before do
6 | @formula = double(Sprinkle::Package, :name => 'formula', :sudo? => false)
7 | end
8 |
9 | def create_brew(*formulas, &block)
10 | Sprinkle::Installers::Brew.new(@formula, *formulas, &block)
11 | end
12 |
13 | describe 'when created' do
14 |
15 | it 'should accept a single package to install' do
16 | @installer = create_brew 'ruby'
17 | @installer.packages.should == [ 'ruby' ]
18 | end
19 |
20 | it 'should accept an array of packages to install' do
21 | @installer = create_brew %w( gcc gdb g++ )
22 | @installer.packages.should == ['gcc', 'gdb', 'g++']
23 | end
24 |
25 | it 'should remove options from packages list' do
26 | @installer = create_brew 'ruby'
27 | @installer.packages.should == [ 'ruby' ]
28 | end
29 |
30 | end
31 |
32 | describe 'during installation' do
33 |
34 | before do
35 | @installer = create_brew 'ruby' do
36 | pre :install, 'op1'
37 | post :install, 'op2'
38 | end
39 | @install_commands = @installer.send :install_commands
40 | end
41 |
42 | it 'should invoke the apt installer for all specified packages' do
43 | @install_commands.should =~ /brew install ruby/
44 | end
45 |
46 | it 'should automatically insert pre/post commands for the specified package' do
47 | @installer.send(:install_sequence).should == [ 'op1', %(brew install ruby), 'op2' ]
48 | end
49 |
50 | end
51 |
52 | # describe 'during dependencies only installation' do
53 | #
54 | # before do
55 | # @installer = create_apt('ruby') { dependencies_only true }
56 | # @install_commands = @installer.send :install_commands
57 | # end
58 | #
59 | # it 'should invoke the apt installer with build-dep command for all specified packages' do
60 | # @install_commands.should =~ /apt-get --force-yes -qyu build-dep ruby/
61 | # end
62 | #
63 | # end
64 | end
--------------------------------------------------------------------------------
/lib/sprinkle/verifiers/file.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Verifiers
3 | # = File Verifier
4 | #
5 | # Contains a verifier to check the existance of a file.
6 | #
7 | # == Example Usage
8 | #
9 | # verify { has_file '/etc/apache2/apache2.conf' }
10 | #
11 | # verify { file_contains '/etc/apache2/apache2.conf', 'mod_gzip'}
12 | #
13 | module File
14 | Sprinkle::Verify.register(Sprinkle::Verifiers::File)
15 |
16 | # tests that the file path exists
17 | def has_file(path)
18 | test "-f #{path}"
19 | end
20 |
21 | # Tests that the directory dir exists.
22 | def has_directory(dir)
23 | test "-d #{dir}"
24 | end
25 |
26 | # Checks that symlink is a symbolic link. If file is
27 | # given, it checks that symlink points to file
28 | def has_symlink(symlink, file = nil)
29 | if file.nil?
30 | test "-L #{symlink}"
31 | else
32 | test "'#{file}' = `readlink #{symlink}`"
33 | end
34 | end
35 |
36 | def no_file(path)
37 | test "! -f #{path}"
38 | end
39 |
40 | def md5_of_file(path, md5)
41 | test "\"`#{sudo_cmd}md5sum #{path} | cut -f1 -d' '`\" = \"#{md5}\""
42 | end
43 |
44 | def sha1_of_file(path, sha1)
45 | test "\"`#{sudo_cmd}sha1sum #{path} | cut -f1 -d' '`\" = \"#{sha1}\""
46 | end
47 |
48 | def file_contains(path, text)
49 | @commands << "grep '#{text}' #{path}"
50 | end
51 |
52 | # TODO: remove 0.9
53 | def user_present(username)
54 | ActiveSupport::Deprecation.warn("user_present is depreciated. Use has_user instead.")
55 | has_user username
56 | end
57 |
58 | def matches_local(localfile, remotefile, mode=nil)
59 | raise "Couldn't find local file #{localfile}" unless ::File.exists?(localfile)
60 | require 'digest/md5'
61 | local = Digest::MD5.hexdigest(::File.read(localfile))
62 | md5_of_file remotefile, local
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/binary.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # The Binary installer will download a binary archive and then extract
4 | # it in the directory specified by the prefix option.
5 | #
6 | # == Example Usage
7 | #
8 | # binary "http://some.url.com/archive.tar.gz" do
9 | # prefix "/home/user/local"
10 | # archives "/home/user/sources"
11 | # end
12 | #
13 | # This example will download archive.tar.gz to /home/user/sources and then
14 | # extract it into /home/user/local.
15 | class Binary < Installer
16 |
17 | api do
18 | def binary(source, options = {}, &block)
19 | install Binary.new(self, source, options, &block)
20 | end
21 | end
22 |
23 | def initialize(parent, binary_archive, options = {}, &block) #:nodoc:
24 | @binary_archive = binary_archive
25 | super parent, options, &block
26 | end
27 |
28 | def prepare_commands #:nodoc:
29 | raise 'No installation area defined' unless @options[:prefix]
30 | raise 'No archive download area defined' unless @options[:archives]
31 |
32 | [ "mkdir -p #{@options[:prefix]}",
33 | "mkdir -p #{@options[:archives]}" ]
34 | end
35 |
36 | def install_commands #:nodoc:
37 | commands = [ "bash -c 'wget -cq --directory-prefix=#{@options[:archives]} #{@binary_archive}'" ]
38 | commands << "bash -c \"cd #{@options[:prefix]} && #{sudo_cmd} #{extract_command} '#{@options[:archives]}/#{archive_name}'\""
39 | end
40 |
41 | def archive_name #:nodoc:
42 | @archive_name ||= @binary_archive.split("/").last.gsub('%20', ' ')
43 | end
44 |
45 | def extract_command #:nodoc:
46 | case archive_name
47 | when /(tar.gz)|(tgz)$/
48 | 'tar xzf'
49 | when /(tar.bz2)|(tb2)$/
50 | 'tar xjf'
51 | when /tar$/
52 | 'tar xf'
53 | when /zip$/
54 | 'unzip -o'
55 | else
56 | raise "Unknown binary archive format: #{archive_name}"
57 | end
58 | end
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/lib/sprinkle/actors/actor.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | # = Actors
3 | #
4 | # An actor is a method of command delivery to a remote machine. Actors are the
5 | # layer setting between Sprinkle and the systems you and wanting to apply
6 | # policies to.
7 | #
8 | # Sprinkle ships with actors for Capistrano, Vlad, localhost and pure SSH.
9 | module Actors
10 | # == Writing an actor
11 | #
12 | # All actors should inherit from Sprinkle::Actors::Actor. Actors must provide the
13 | # following methods:
14 | #
15 | # * #install (installer, roles, options)
16 | # * #verify (verifier, roles, options)
17 | # * #servers_for_role? (roles)
18 | # * #sudo?
19 | # * #sudo_command
20 | #
21 | # Hopefully these methods are kind of fairly obvious. They should return true
22 | # to indicate success and false to indicate failure.
23 | # The actual commands you need to execute can be retrived from
24 | # +installer.install_sequence+ and +verifier.commands+.
25 | class Actor
26 |
27 | # an actor must define this method so that each policy can ask the actor
28 | # if there are any servers with that policy's roles so the policy knows
29 | # whether it should execute or not
30 | #
31 | # input: a single role or array of roles
32 | def servers_for_role?(role)
33 | raise "please define servers_for_role?"
34 | end
35 |
36 | def install(installer, roles, options)
37 | raise "you must define install"
38 | end
39 |
40 | # an actor must define this and let the installers know if it plans
41 | # to try and add sudo to all of their commands or not since some
42 | # installers might need to handle sudo their own special way
43 | def sudo?
44 | raise "you must define sudo?"
45 | end
46 |
47 | # if an installer needs to call sudo this is the command the actor
48 | # would prefer the installers to use
49 | def sudo_command
50 | raise "you must define sudo_command"
51 | end
52 |
53 | def verify(verifier, roles, options)
54 | raise "you must define verify"
55 | end
56 |
57 | end
58 | end
59 | end
--------------------------------------------------------------------------------
/examples/packages/phusion.rb:
--------------------------------------------------------------------------------
1 | # Contains software created by Phusion.nl which is Ruby Enterprise Edition
2 | # and mod_rails
3 |
4 | package :ruby_enterprise do
5 | description 'Ruby Enterprise Edition'
6 | version '1.8.6-20080810'
7 | requires :apache
8 | requires :passenger
9 |
10 | source 'http://rubyforge.org/frs/download.php/41040/ruby-enterprise-1.8.6-20080810.tar.gz' do
11 | custom_install 'echo -en "\n\n\n\n" | ./installer'
12 |
13 | # Modify the passenger conf file to point to REE
14 | post :install, 'sed -i "s|^PassengerRuby [/a-zA-Z0-9.]*$|PassengerRuby /opt/ruby-enterprise-1.8.6-20080810/bin/ruby|" /etc/apache2/extras/passenger.conf'
15 |
16 | # Restart apache
17 | post :install, '/etc/init.d/apache2 restart'
18 | end
19 |
20 | verify do
21 | has_directory '/opt/ruby-enterprise-1.8.6-20080810'
22 | has_executable '/opt/ruby-enterprise-1.8.6-20080810/bin/ruby'
23 | end
24 | end
25 |
26 | package :passenger, :provides => :appserver do
27 | description 'Phusion Passenger (mod_rails)'
28 | requires :apache
29 | requires :apache2_prefork_dev
30 |
31 | gem 'passenger' do
32 | post :install, 'echo -en "\n\n\n\n" | passenger-install-apache2-module'
33 |
34 | # Create the passenger conf file
35 | post :install, 'mkdir /etc/apache2/extras'
36 | post :install, 'touch /etc/apache2/extras/passenger.conf'
37 | post :install, "echo 'Include /etc/apache2/extras/passenger.conf' >> /etc/apache2/apache2.conf"
38 |
39 | [%q(LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-2.0.3/ext/apache2/mod_passenger.so),
40 | %q(PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-2.0.3),
41 | %q(PassengerRuby /usr/bin/ruby1.8),
42 | %q(RailsEnv development)].each do |line|
43 | post :install, "echo '#{line}' >> /etc/apache2/extras/passenger.conf"
44 | end
45 |
46 | # Restart apache to note changes
47 | post :install, '/etc/init.d/apache2 restart'
48 | end
49 |
50 | verify do
51 | has_file '/etc/apache2/extras/passenger.conf'
52 | has_file '/usr/lib/ruby/gems/1.8/gems/passenger-2.0.3/ext/apache2/mod_passenger.so'
53 | has_directory '/usr/lib/ruby/gems/1.8/gems/passenger-2.0.3'
54 | end
55 | end
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler/setup'
3 |
4 | require 'rake'
5 | require 'rspec/core/rake_task'
6 | require 'sdoc'
7 | require 'rdoc/task'
8 | require './lib/sprinkle/version'
9 |
10 | task "inst" => [:clobber, :build] do
11 | puts `gem install pkg/sprinkle-*.gem`
12 | end
13 |
14 | desc 'Default: run specs.'
15 | task :default => :spec
16 |
17 | desc "Run specs"
18 | RSpec::Core::RakeTask.new do |t|
19 | t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
20 | # Put spec opts in a file named .rspec in root
21 | end
22 |
23 | desc "Generate code coverage"
24 | RSpec::Core::RakeTask.new(:coverage) do |t|
25 | t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
26 | t.rcov = true
27 | t.rcov_opts = ['--exclude', 'spec']
28 | end
29 |
30 | class RDoc::Comment
31 | def gsub(*args)
32 | @text.gsub(*args)
33 | end
34 | end
35 |
36 | RDoc::Task.new do |rdoc|
37 | version = Sprinkle::Version
38 |
39 | rdoc.options << '-e' << 'UTF-8'
40 | rdoc.options << '-f' << 'sdoc'
41 | # rdoc.options << "-T" << "rails"
42 |
43 | rdoc.rdoc_dir = 'rdoc'
44 | rdoc.title = "sprinkle #{version}"
45 | rdoc.rdoc_files.include('README*')
46 | rdoc.rdoc_files.include('lib/**/*.rb')
47 | end
48 |
49 | STATS_DIRECTORIES = [
50 | %w(Library lib/sprinkle/),
51 | %w(Specs spec),
52 | ].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
53 | BROKEN = [
54 | %w(Actors lib/sprinkle/actors),
55 | # %w(Errors lib/sprinkle/errors),
56 | # %w(Extensions lib/sprinkle/extensions),
57 | %w(Installers lib/sprinkle/installers),
58 | # %w(Utility lib/sprinkle/utility),
59 | %w(Package lib/sprinkle/package),
60 | %w(Verifiers lib/sprinkle/verifiers),
61 | ].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
62 |
63 | desc "Report code statistics (KLOCs, etc) from the application"
64 | task :stats do
65 | require 'rails/code_statistics'
66 | CodeStatistics::TEST_TYPES << "Specs"
67 | cs=CodeStatistics.new(*BROKEN)
68 | cs.instance_variable_set("@total",nil)
69 | cs.to_s
70 | CodeStatistics.new(*STATS_DIRECTORIES).to_s
71 | end
72 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/runner_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::Runner do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'package', :sudo? => false)
7 | end
8 |
9 | def create_runner(*cmds)
10 | options=cmds.extract_options!
11 | Sprinkle::Installers::Runner.new(@package, cmds, options)
12 | end
13 |
14 | describe 'when created' do
15 | it 'should accept a single cmd to run' do
16 | @installer = create_runner 'teste'
17 | @installer.cmds.should eq ['teste']
18 | end
19 |
20 | it 'should accept an array of commands to run' do
21 | @installer = create_runner ['teste', 'world']
22 | @installer.cmds.should eq ['teste', 'world']
23 | @installer.install_sequence.should eq ['teste', 'world']
24 | end
25 | end
26 |
27 | describe 'during installation' do
28 |
29 | it 'should use sudo if specified locally' do
30 | @installer = create_runner 'teste', :sudo => true
31 | @install_commands = @installer.send :install_commands
32 | @install_commands.should eq ['sudo teste']
33 | end
34 |
35 | it "should accept env options and convert to uppercase" do
36 | @installer = create_runner 'command1', :env => {
37 | :z => 'foo',
38 | :PATH => '/some/path',
39 | :user => 'deploy',
40 | :a => 'bar'
41 | }
42 | @install_commands = @installer.send :install_commands
43 | command_parts = @install_commands.first.split(/ /)
44 |
45 | command_parts.shift.should eq 'env'
46 | command_parts.pop.should eq 'command1'
47 |
48 | command_parts.should =~ ['PATH=/some/path', 'USER=deploy', 'Z=foo', 'A=bar']
49 |
50 | end
51 |
52 | it "should accept multiple commands" do
53 | @installer = create_runner 'teste', 'test2'
54 | @install_commands = @installer.send :install_commands
55 | @install_commands.should eq ['teste','test2']
56 | end
57 |
58 | it 'should run the given command for all specified packages' do
59 | @installer = create_runner 'teste'
60 | @install_commands = @installer.send :install_commands
61 | @install_commands.should eq ['teste']
62 | end
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/push_text.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # Beware, strange "installer" coming your way.
4 | #
5 | # This push text installer pushes simple configuration into a file.
6 | #
7 | # == Example Usage
8 | #
9 | # Installing magic_beans into apache2.conf
10 | #
11 | # package :magic_beans do
12 | # push_text 'magic_beans', '/etc/apache2/apache2.conf'
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 | # push_text 'magic_beans', '/etc/apache2/apache2.conf', :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 | # package :magic_beans do
27 | # push_text 'magic_beans', '/etc/apache2/apache2.conf'
28 | # verify do
29 | # file_contains '/etc/apache2/apache2.conf', 'magic_beans'
30 | # end
31 | # end
32 | #
33 | class PushText < Installer
34 | attr_accessor :text, :path #:nodoc:
35 |
36 | api do
37 | def push_text(text, path, options = {}, &block)
38 | install PushText.new(self, text, path, options, &block)
39 | end
40 | end
41 |
42 | def initialize(parent, text, path, options={}, &block) #:nodoc:
43 | super parent, options, &block
44 | # by default we would not want to push the same thing over and over
45 | options.reverse_merge!(:idempotent => true)
46 | @text = text
47 | @path = path
48 | end
49 |
50 | def announce #:nodoc:
51 | log "--> Append '#{@text}' to file #{@path}"
52 | end
53 |
54 | protected
55 |
56 | def install_commands #:nodoc:
57 | escaped_text = escape_shell_arg(@text)
58 | escaped_regex = Regexp.escape(@text)
59 | command = ""
60 | command << "#{sudo_cmd}grep -qPzo '^#{escaped_regex}$' #{@path} || " if option?(:idempotent)
61 | command << "/bin/echo -e '#{escaped_text}' |#{sudo_cmd}tee -a #{@path}"
62 | command
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/sprinkle/package/rendering.rb:
--------------------------------------------------------------------------------
1 | require 'pp'
2 | require 'erubis'
3 | require 'digest/md5'
4 |
5 | module Sprinkle::Package
6 | # For help on rendering, see the Sprinkle::Installers::FileInstaller.
7 | module Rendering
8 | extend ActiveSupport::Concern
9 |
10 | included do
11 | self.send :include, Helpers
12 | end
13 |
14 | # render src as ERB
15 | def template(src, context=binding)
16 | eruby = Erubis::Eruby.new(src)
17 | eruby.result(context)
18 | rescue Object => e
19 | raise Sprinkle::Errors::TemplateError.new(e, src, context)
20 | end
21 |
22 | # read in filename and render it as ERB
23 | def render(filename, context=binding)
24 | contents=File.read(expand_filename(filename))
25 | template(contents, context)
26 | end
27 |
28 | # Helper methods can be called from inside your package and
29 | # verification code
30 | module Helpers
31 | # return the md5 of a string (as a hex string)
32 | def md5(s)
33 | Digest::MD5.hexdigest(s)
34 | end
35 | end
36 |
37 | # sets the path a package should use to search for templates
38 | def template_search_path(path)
39 | @template_search_path = path
40 | end
41 |
42 | private
43 |
44 | def search_paths(n) #:nodoc:
45 | # if we are given an absolute path, return just that path
46 | return [File.dirname(n)] if n.starts_with? "/"
47 |
48 | pwd = Dir.pwd
49 | package_dir = @template_search_path
50 |
51 | p = []
52 | # if ./ is used assume the path is relative to the package
53 | if package_dir
54 | p << File.expand_path(File.join(package_dir,"templates"))
55 | p << File.expand_path(package_dir)
56 | else
57 | # otherwise search template folders relate to cwd
58 | p << File.expand_path(File.join(pwd,"templates"))
59 | p << File.expand_path(pwd)
60 | end
61 |
62 | p.uniq
63 | end
64 |
65 | def expand_filename(n) #:nodoc:
66 | name = File.basename(n)
67 | paths = search_paths(n).map do |p|
68 | [File.join(p,name), File.join(p,"#{name}.erb")]
69 | end.flatten
70 |
71 | paths.each do |f|
72 | return f if File.exist?(f)
73 | end
74 |
75 | puts "RESOLVED SEARCH PATHS"
76 | pp paths
77 |
78 | raise "template not found: #{n}"
79 |
80 | end
81 |
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/spec/sprinkle/deployment_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Deployment do
4 | include Sprinkle::Deployment
5 |
6 | def create_deployment(&block)
7 | deployment do
8 | delivery :capistrano, &block
9 |
10 | source do
11 | prefix '/usr/local'
12 | end
13 | end
14 | end
15 |
16 | describe 'when created' do
17 |
18 | it 'should be invalid without a block descriptor' do
19 | lambda { deployment }.should raise_error
20 | end
21 |
22 | it 'should be invalid without a delivery method' do
23 | lambda { @deployment = deployment do; end }.should raise_error
24 | end
25 |
26 | it 'should optionally accept installer defaults' do
27 | @deployment = create_deployment
28 | @deployment.source do; end
29 | @deployment.defaults.keys.should == [:source]
30 | end
31 |
32 | it 'should provide installer defaults as a proc when requested' do
33 | @deployment = create_deployment
34 | @deployment.defaults[:source].class.should == Proc
35 | end
36 |
37 | end
38 |
39 | describe 'delivery specification' do
40 |
41 | before do
42 | @actor = double(Sprinkle::Actors::Capistrano)
43 | Sprinkle::Actors::Capistrano.stub(:new).and_return(@actor)
44 | end
45 |
46 | it 'should automatically instantiate the delivery type' do
47 | @deployment = create_deployment
48 | @deployment.style.should == @actor
49 | end
50 |
51 | it 'should optionally accept a block to pass to the actor' do
52 | lambda { @deployment = create_deployment }.should_not raise_error
53 | end
54 |
55 | describe 'with a block' do
56 |
57 | it 'should pass the block to the actor for configuration' do
58 | @deployment = create_deployment do; recipes 'deploy'; end
59 | end
60 |
61 | end
62 | end
63 |
64 | describe 'when processing policies' do
65 |
66 | before do
67 | @policy = double(Sprinkle::Policy, :process => true)
68 | Sprinkle::POLICIES.clear
69 | Sprinkle::POLICIES << @policy
70 | @deployment = create_deployment
71 | end
72 |
73 | it 'should apply all policies, passing itself as the deployment context' do
74 | @policy.should_receive(:process).with(@deployment).and_return
75 | end
76 |
77 | after do
78 | @deployment.process
79 | end
80 | end
81 |
82 | end
83 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/gem.rb:
--------------------------------------------------------------------------------
1 |
2 | module Sprinkle
3 | module Installers
4 | # The gem package installer installs Ruby gems.
5 | #
6 | # The installer has a optional configuration: source.
7 | # By changing source you can specify a given ruby gems
8 | # repository from which to install.
9 | #
10 | # Besides `source`, this installer also supports these options:
11 | #
12 | # - `repository`: install directory
13 | # - `http_proxy`
14 | # - `build_docs`: by default, no `rdoc` and `ri`
15 | # - `build_flags`: additional build flags
16 | #
17 | # == Example Usage
18 | #
19 | # First, a simple installation of the magic_beans gem:
20 | #
21 | # package :magic_beans do
22 | # description "Beans beans they're good for your heart..."
23 | # gem 'magic_beans'
24 | # end
25 | #
26 | # Second, install magic_beans gem from github:
27 | #
28 | # package :magic_beans do
29 | # gem 'magic_beans_package' do
30 | # source 'http://gems.github.com'
31 | # end
32 | # end
33 | #
34 | # As you can see, setting options is as simple as creating a
35 | # block and calling the option as a method with the value as
36 | # its parameter.
37 | class Gem < Installer
38 |
39 | api do
40 | def gem(name, options = {}, &block)
41 | recommends :rubygems
42 | install Gem.new(self, name, options, &block)
43 | end
44 | end
45 |
46 | attr_accessor :gem #:nodoc:
47 |
48 | def initialize(parent, gem, options = {}, &block) #:nodoc:
49 | super parent, options, &block
50 | @gem = gem
51 | end
52 |
53 | attributes :source, :repository, :http_proxy, :build_docs, :build_flags, :version
54 |
55 | protected
56 |
57 | # rubygems 0.9.5+ installs dependencies by default, and does platform selection
58 |
59 | def install_commands #:nodoc:
60 | cmd = "#{sudo_cmd}gem install #{gem}"
61 | cmd << " --version '#{version}'" if version
62 | cmd << " --source #{source}" if source
63 | cmd << " --install-dir #{repository}" if option?(:repository)
64 | cmd << " --no-rdoc --no-ri" unless option?(:build_docs)
65 | cmd << " --http-proxy #{http_proxy}" if option?(:http_proxy)
66 | cmd << " -- #{build_flags}" if option?(:build_flags)
67 | cmd
68 | end
69 |
70 | end
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/replace_text_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::ReplaceText do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'package', :sudo? => false)
7 | end
8 |
9 | def create_replacement_text(regex, text, path, options={}, &block)
10 | Sprinkle::Installers::ReplaceText.new(@package, regex, text, path, options, &block)
11 | end
12 |
13 | describe 'when created' do
14 |
15 | it 'should accept text to replace, replacement, and path' do
16 | @installer = create_replacement_text 'text_to_replace', 'new_text', '/etc/example/foo.conf'
17 | @installer.regex.should eq 'text_to_replace'
18 | @installer.text.should eq 'new_text'
19 | @installer.path.should eq '/etc/example/foo.conf'
20 | end
21 |
22 | it 'should not escape original search string and replacement' do
23 | @installer = create_replacement_text "some option with 'quotes' & ampersand", "other 'quotes' & ampersand", '/etc/example/foo.conf'
24 | @installer.regex.should eq %q[some option with 'quotes' & ampersand]
25 | @installer.text.should eq %q[other 'quotes' & ampersand]
26 | @installer.path.should eq '/etc/example/foo.conf'
27 | end
28 |
29 | end
30 |
31 | describe 'during installation' do
32 |
33 | before do
34 | @installer = create_replacement_text 'bad option', 'super option', '/etc/brand/new.conf' do
35 | pre :install, 'op1'
36 | post :install, 'op2'
37 | end
38 | @install_commands = @installer.send :install_commands
39 | end
40 |
41 | it 'should invoke the replace text installer for all specified packages' do
42 | @install_commands.should eq %q[sed -i 's/bad option/super option/g' /etc/brand/new.conf]
43 | end
44 |
45 | it 'should automatically insert pre/post commands for the specified package' do
46 | @installer.send(:install_sequence).should eq [ 'op1', "sed -i 's/bad option/super option/g' /etc/brand/new.conf", 'op2' ]
47 | end
48 |
49 | it 'should correctly escape search string and replacement' do
50 | installer = create_replacement_text "some option with 'quotes' & ampersand", "other 'quotes' & ampersand", '/etc/example/foo.conf'
51 | installer.send(:install_commands).should eq "sed -i 's/some option with '\\''quotes'\\'' \\& ampersand/other '\\''quotes'\\'' \\& ampersand/g' /etc/example/foo.conf"
52 | end
53 | end
54 |
55 | end
56 |
--------------------------------------------------------------------------------
/spec/sprinkle/extensions/rendering_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Package::Rendering, 'rendering' do
4 |
5 | before do
6 | @root = File.expand_path(File.join(File.dirname(__FILE__), "../.."))
7 | @package = package :something do
8 | end
9 | end
10 |
11 | describe "path expansion" do
12 |
13 | it "should know / is root" do
14 | dirs = @package.send :search_paths, "/test/file"
15 | dirs.should eq ["/test"]
16 | end
17 |
18 | it "./ is local to where we tell it to be" do
19 | Dir.stub(:pwd).and_return("/path/is/")
20 | @package.template_search_path "/my/super/package/"
21 | dirs = @package.send :search_paths, "./test/file"
22 | dirs.should include("/my/super/package")
23 | dirs.should include("/my/super/package/templates")
24 | dirs.should_not include("/path/is")
25 | dirs.should_not include("/path/is/templates")
26 | end
27 |
28 | it "should search pwd when amgiguous" do
29 | Dir.stub(:pwd).and_return("/path/is/")
30 | dirs = @package.send :search_paths, "test/file"
31 | dirs.should include("/path/is")
32 | dirs.should include("/path/is/templates")
33 | dirs.size.should eq 2
34 | end
35 |
36 | end
37 |
38 | it "should be able to calculate md5s" do
39 | @package.md5("test").should == "098f6bcd4621d373cade4e832627b4f6"
40 | end
41 |
42 | it "should allow passing locals to template" do
43 | t = @package.template("hello <%= world %>", :world => "world")
44 | t.should == "hello world"
45 | end
46 |
47 | it "should allow access to the package context by default" do
48 | @package = package :new do
49 | @wowser = "wowser"
50 | end.instance
51 | @package.opts[:world]="world"
52 | t=@package.template("hello <%= opts[:world] %> <%= @wowser %>")
53 | t.should == "hello world wowser"
54 | end
55 |
56 | it "should be able to render a file from templates" do
57 | Dir.chdir(@root) do
58 | t = @package.render("test")
59 | t.should == "hello "
60 | end
61 | end
62 |
63 | it "should be able to render a file from absolute path" do
64 | path = File.join(@root, "templates/test.erb")
65 | t = @package.render(path)
66 | t.should == "hello "
67 | end
68 |
69 | it "should accept binding as second argument" do
70 | path = File.join(@root, "templates/locals.erb")
71 | t = @package.render(path, :world => "world")
72 | t.should == "hello world"
73 | end
74 |
75 | end
76 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | * Lots of sudo fixes
2 |
3 | *Various people*
4 |
5 | * Default package options (see Package docs)
6 |
7 | *Koen Punt*
8 |
9 | * Add the file installer so we can stop doing templates with `transfer`
10 |
11 | *Josh Goebel*
12 |
13 | * Officially depreciate transfer :render and the ability to render just by passing
14 | a multi-line string as the transfer source. If you want to render templates see the
15 | new `render()` and `template()` (rendering.rb) helpers and the `file` installer.
16 |
17 | *Josh Goebel*
18 |
19 | * A users own post :install hooks should happen after a file has completely been moved
20 | (when using sudo this was not the case)
21 |
22 | *Koen Punt*
23 |
24 | * Remove the Deployment module from Object.
25 |
26 | If anyone is relying on the behavior of placing their deployment block in a required
27 | file then they will first need to manually add the module back to the Object class
28 | themselves. Polluting Object is generally bad.
29 |
30 | *Josh Goebel*
31 |
32 | * Add support for specifying the Net::SSH keys property
33 |
34 | *Chris Kimpton*
35 |
36 | * push_text was escaping & and / when it should not be
37 |
38 | *Stefano Diem Benatti*
39 |
40 | * Sprinkle `sudo_cmd` and Capistrino should work together instead of getting in each others way
41 |
42 | When using the capistrano actor `sudo_cmd` will now use the capistrano
43 | generated sudo command and therefore automatically deal with password
44 | prompts, etc. This should fix hangs when installers try to call `sudo` on
45 | the other side of a pipe operation and capistrano can not recognize the
46 | password prompt.
47 |
48 | * Sprinkle executable should return an error code if there was a failure
49 |
50 | *Michael Nigh*
51 |
52 | * verify of local actor was never returning false so installers would never be executed
53 |
54 | *Edgars Beigarts*
55 |
56 | * Capistrano actor now defaults to loading "Capfile" not "deploy" when no block is given.
57 | If for some reason you just have a 'deploy' file in your root folder you
58 | should `capify .` your setup and move your deploy.rb file into the config
59 | folder. Or you can provide a block with `recipe 'deploy'` to force the
60 | old behavior.
61 |
62 | *Josh Goebel*
63 |
64 | * Capistrano actor now uses the configured setting of `run_method`, instead of always sudo.
65 | The default Capistrano setup prefers sudo, so nothing should change for
66 | most users. If you want to NOT use sudo to run commands you can set
67 | `use_sudo` or `run_method` accordingly in your capistrano recipes:
68 | `set :use_sudo, false` or `set :run_method, :run`
69 |
70 | *Michael Nigh*
71 |
--------------------------------------------------------------------------------
/examples/rails/rails.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sprinkle -s
2 |
3 | # Annotated Example Sprinkle Rails deployment script
4 | #
5 | # This is an example Sprinkle script configured to install Rails from gems, Apache, Ruby,
6 | # Sphinx and Git from source, and mysql and Git dependencies from apt on an Ubuntu system.
7 | #
8 | # Installation is configured to run via capistrano (and an accompanying deploy.rb recipe script).
9 | # Source based packages are downloaded and built into /usr/local on the remote system.
10 | #
11 | # A sprinkle script is separated into 3 different sections. Packages, policies and deployment:
12 |
13 |
14 | # Packages (separate files for brevity)
15 | #
16 | # Defines the world of packages as we know it. Each package has a name and
17 | # set of metadata including its installer type (eg. apt, source, gem, etc). Packages can have
18 | # relationships to each other via dependencies.
19 |
20 | require './packages/essential'
21 | require './packages/rails'
22 | require './packages/database'
23 | require './packages/server'
24 | require './packages/search'
25 | require './packages/scm'
26 |
27 |
28 | # Policies
29 | #
30 | # Names a group of packages (optionally with versions) that apply to a particular set of roles:
31 | #
32 | # Associates the rails policy to the application servers. Contains rails, and surrounding
33 | # packages. Note, appserver, database, webserver and search are all virtual packages defined above.
34 | # If there's only one implementation of a virtual package, it's selected automatically, otherwise
35 | # the user is requested to select which one to use.
36 |
37 | policy :rails, :roles => :app do
38 | requires :rails
39 | requires :appserver
40 | requires :database
41 | requires :webserver
42 | requires :search
43 | requires :scm
44 | end
45 |
46 |
47 | # Deployment
48 | #
49 | # Defines script wide settings such as a delivery mechanism for executing commands on the target
50 | # system (eg. capistrano), and installer defaults (eg. build locations, etc):
51 | #
52 | # Configures spinkle to use capistrano for delivery of commands to the remote machines (via
53 | # the named 'deploy' recipe). Also configures 'source' installer defaults to put package gear
54 | # in /usr/local
55 |
56 | deployment do
57 |
58 | # mechanism for deployment
59 | # delivery :capistrano do
60 | # recipes 'deploy'
61 | # end
62 | delivery :dummy do
63 | role :app, "test"
64 | end
65 |
66 | # source based package installer defaults
67 | source do
68 | prefix '/usr/local'
69 | archives '/usr/local/sources'
70 | builds '/usr/local/build'
71 | end
72 |
73 | end
74 |
75 | # End of script, given the above information, Spinkle will apply the defined policy on all roles using the
76 | # deployment settings specified.
77 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/install_package.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | class InstallPackage < Installer #:nodoc:
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) #:nodoc:
66 | ActiveSupport::Deprecation.warn("install_package will be removed from sprinkle 0.8, please use yum or smart installers instead.")
67 | @installers << Sprinkle::Installers::InstallPackage.new(self, names, &block)
68 | end
69 |
70 | def uninstall_package(*names, &block) #:nodoc:
71 | ActiveSupport::Deprecation.warn("uninstall_package will be removed from sprinkle 0.8, please use yum or smart installers instead.")
72 | @installers << Sprinkle::Installers::UninstallPackage.new(self, names, &block)
73 | end
74 |
75 | alias_method :install_packages, :install_package #:nodoc:
76 | alias_method :uninstall_packages, :uninstall_package #:nodoc:
77 | end
78 | end
79 | end
80 |
81 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/pecl.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | module Installers
3 | # = Pecl extension installed
4 | #
5 | # Installs the specified pecl extension
6 | #
7 | # == Example Usage
8 | #
9 | # package :php_stuff do
10 | # pecl 'mongo'
11 | # verify { has_pecl 'mongo' }
12 | # end
13 | #
14 | # You can optionally pass a version number to both `pecl` and `has_pecl`:
15 | #
16 | # package :php_stuff do
17 | # pecl 'mongo', :version => "1.4.3"
18 | # verify { has_pecl 'mongo', :version => "1.4.3" }
19 | # end
20 | #
21 | # Some extensions need an ini file. You can have that generated, by passing the `:ini_file` option:
22 | #
23 | # package :php_stuff do
24 | # pecl 'mongo', :ini_file => true
25 | # end
26 | #
27 | # If you need more fine grained control of the location or contents of the ini file, use:
28 | #
29 | # package :php_stuff do
30 | # pecl 'mongo', :ini_file => { :path => "/etc/php5/apache2/php.ini",
31 | # :content => "extension=mongo.so",
32 | # :sudo => true }
33 | # end
34 | #
35 | class Pecl < Installer
36 | attr_accessor :package_name, :package_version
37 |
38 | api do
39 | def pecl(package_name, options = {}, &block)
40 | install Pecl.new(self, package_name, options, &block)
41 | end
42 | end
43 |
44 | verify_api do
45 | def has_pecl(package_name, options = {})
46 | @commands = "TERM= pecl list | grep '^#{package_name}\\\\s*" + (options[:version] ? options[:version].to_s : "") + "'"
47 | end
48 | end
49 |
50 | def initialize(parent, package_name, options = {}, &block) #:nodoc:
51 | super parent, &block
52 | @package_name = package_name
53 | @package_version = options[:version]
54 | @ini_file = options[:ini_file]
55 | setup_ini if @ini_file
56 | end
57 |
58 | def setup_ini
59 | @ini_file = to_ini_file_hash(@ini_file)
60 | text = @ini_file[:content] || "extension=#{@package_name}.so"
61 | path = @ini_file[:path] || "/etc/php5/conf.d/#{@package_name}.ini"
62 | use_sudo = @ini_file[:sudo]===false ? false : true
63 | post(:install) do
64 | file(path, :content => text, :sudo => use_sudo)
65 | end
66 | end
67 |
68 | def to_ini_file_hash(s)
69 | return {:content => s} if s.is_a? String
70 | return {} if s===true
71 | s
72 | end
73 |
74 | protected
75 | def install_commands #:nodoc:
76 | cmd = "TERM= pecl install --alldeps #{@package_name}"
77 | cmd << "-#{@package_version}" if @package_version
78 | cmd
79 | end
80 | end
81 | end
82 | end
83 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | sprinkle (0.7.6.2)
5 | activesupport (>= 2.0.2)
6 | capistrano (>= 2.5.5, < 3)
7 | erubis (>= 2.7.0)
8 | highline (>= 1.4.0)
9 | open4 (>= 1.1.0)
10 |
11 | GEM
12 | remote: https://rubygems.org/
13 | specs:
14 | actionpack (3.2.13)
15 | activemodel (= 3.2.13)
16 | activesupport (= 3.2.13)
17 | builder (~> 3.0.0)
18 | erubis (~> 2.7.0)
19 | journey (~> 1.0.4)
20 | rack (~> 1.4.5)
21 | rack-cache (~> 1.2)
22 | rack-test (~> 0.6.1)
23 | sprockets (~> 2.2.1)
24 | activemodel (3.2.13)
25 | activesupport (= 3.2.13)
26 | builder (~> 3.0.0)
27 | activesupport (3.2.13)
28 | i18n (= 0.6.1)
29 | multi_json (~> 1.0)
30 | builder (3.0.4)
31 | capistrano (2.15.5)
32 | highline
33 | net-scp (>= 1.0.0)
34 | net-sftp (>= 2.0.0)
35 | net-ssh (>= 2.0.14)
36 | net-ssh-gateway (>= 1.1.0)
37 | coderay (1.0.9)
38 | diff-lcs (1.2.4)
39 | erubis (2.7.0)
40 | highline (1.6.19)
41 | hike (1.2.3)
42 | i18n (0.6.1)
43 | interception (0.3)
44 | journey (1.0.4)
45 | json (1.8.0)
46 | method_source (0.8.1)
47 | multi_json (1.8.2)
48 | net-scp (1.1.2)
49 | net-ssh (>= 2.6.5)
50 | net-sftp (2.1.2)
51 | net-ssh (>= 2.6.5)
52 | net-ssh (2.7.0)
53 | net-ssh-gateway (1.2.0)
54 | net-ssh (>= 2.6.5)
55 | open4 (1.3.0)
56 | pry (0.9.12.2)
57 | coderay (~> 1.0.5)
58 | method_source (~> 0.8)
59 | slop (~> 3.4)
60 | pry-rescue (1.1.1)
61 | interception (>= 0.3)
62 | pry
63 | pry_debug (0.1.0)
64 | pry (~> 0.9.0)
65 | rack (1.4.5)
66 | rack-cache (1.2)
67 | rack (>= 0.4)
68 | rack-ssl (1.3.3)
69 | rack
70 | rack-test (0.6.2)
71 | rack (>= 1.0)
72 | railties (3.2.13)
73 | actionpack (= 3.2.13)
74 | activesupport (= 3.2.13)
75 | rack-ssl (~> 1.3.2)
76 | rake (>= 0.8.7)
77 | rdoc (~> 3.4)
78 | thor (>= 0.14.6, < 2.0)
79 | rake (10.1.0)
80 | rdoc (3.10)
81 | json (~> 1.4)
82 | rspec (2.14.1)
83 | rspec-core (~> 2.14.0)
84 | rspec-expectations (~> 2.14.0)
85 | rspec-mocks (~> 2.14.0)
86 | rspec-core (2.14.5)
87 | rspec-expectations (2.14.1)
88 | diff-lcs (>= 1.1.3, < 2.0)
89 | rspec-mocks (2.14.3)
90 | sdoc (0.3.20)
91 | json (>= 1.1.3)
92 | rdoc (~> 3.10)
93 | slop (3.4.5)
94 | sprockets (2.2.2)
95 | hike (~> 1.2)
96 | multi_json (~> 1.0)
97 | rack (~> 1.0)
98 | tilt (~> 1.1, != 1.3.0)
99 | thor (0.18.1)
100 | tilt (1.4.1)
101 |
102 | PLATFORMS
103 | ruby
104 |
105 | DEPENDENCIES
106 | pry
107 | pry-rescue
108 | pry_debug
109 | railties
110 | rake (>= 0.8.7)
111 | rdoc (= 3.10)
112 | rspec (>= 2.5)
113 | sdoc
114 | sprinkle!
115 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/binary_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::Binary do
4 | include Sprinkle::Deployment
5 |
6 | def create_context(source = 'http://www.example.com/archive.tar.gz', sudo = false)
7 | deployment = deployment do
8 | delivery :capistrano
9 | binary source do
10 | prefix '/prefix/directory'
11 | archives '/archives/directory'
12 | end
13 | end
14 |
15 | installer = create_binary source, sudo do
16 | prefix '/prefix/directory'
17 | archives '/archives/directory'
18 | end
19 |
20 | installer.defaults(@deployment)
21 |
22 | [source, deployment, installer]
23 | end
24 |
25 | def create_binary(binary, sudo, version = nil, &block)
26 | @package ||= double(Sprinkle::Package, :name => 'package', :version => version, :sudo? => sudo)
27 | Sprinkle::Installers::Binary.new(@package, binary, &block)
28 | end
29 |
30 | describe "binary#prepare_commands" do
31 | before do
32 | @binary, @deployment, @installer = create_context
33 | end
34 |
35 | it "should return mkdir command to create the prefix directory" do
36 | @installer.send(:prepare_commands)[0].should == 'mkdir -p /prefix/directory'
37 | end
38 | it "should return mkdir command to create the archives directory" do
39 | @installer.send(:prepare_commands)[1].should == 'mkdir -p /archives/directory'
40 | end
41 | end
42 |
43 |
44 | describe "binary#install_commands" do
45 | before do
46 | @binary, @deployment, @installer = create_context
47 | end
48 |
49 | it "should return a commands to place the binary in the correct archive directory" do
50 | @installer.send(:install_commands)[0].should =~ /--directory-prefix=\/archives\/directory/
51 | end
52 |
53 | it "should return a command to extract to the correct prefix folder" do
54 | @installer.send(:install_commands)[1].should =~ /cd \/prefix\/directory/
55 | end
56 |
57 | it "should return a command to extract the right file in the right directory" do
58 | @installer.send(:install_commands)[1].should =~ / '\/archives\/directory\/archive.tar.gz'/
59 | end
60 | end
61 |
62 | describe "binary#install_commands", "with sudo support" do
63 | before do
64 | @binary, @deployment, @installer = create_context 'http://www.example.com/archive.tar.gz', true
65 | end
66 |
67 | it "should sudo the command when required" do
68 | @installer.send(:install_commands)[1].should =~ /sudo/
69 | end
70 | end
71 |
72 | describe "when source contains spaces (%20's) in path" do
73 | before do
74 | _, _, @installer = create_context('http://c758482.r82.cf2.rackcdn.com/Sublime%20Text%202.0.1%20x64.tar.bz2')
75 | end
76 |
77 | it "should correctly interpret the archive filename as it gets extracted downloaded to file system" do
78 | @installer.send(:install_commands)[1].should =~ / '\/archives\/directory\/Sublime Text 2.0.1 x64.tar.bz2'/
79 | end
80 | end
81 |
82 | end
83 |
--------------------------------------------------------------------------------
/lib/sprinkle/actors/local.rb:
--------------------------------------------------------------------------------
1 | require 'open4'
2 |
3 | module Sprinkle
4 | module Actors
5 | # The local actor executes all commands on your local system, as opposed to other
6 | # implementations that generally run commands on a remote system over the
7 | # network.
8 | #
9 | # This could be useful if you'd like to use Sprinkle to provision your
10 | # local machine. To enable this actor, in your Sprinkle script specify
11 | # the :local delivery mechanism.
12 | #
13 | # deployment do
14 | # delivery :local
15 | # end
16 | #
17 | # Note: The local actor completely ignores roles and behaves as if your
18 | # local system was a member of all roles defined.
19 | class Local < Actor
20 |
21 |
22 | class LocalCommandError < StandardError #:nodoc:
23 | end
24 |
25 | def servers_for_role?(role) #:nodoc:
26 | true
27 | end
28 |
29 | def sudo? #:nodoc:;
30 | false; end
31 | def sudo_command #:nodoc:;
32 | nil; end
33 |
34 | def install(installer, roles, opts = {}) #:nodoc:
35 | # all local installer cares about is the commands
36 | @installer = installer
37 | process(installer.package.name, installer.install_sequence, roles)
38 | rescue LocalCommandError => e
39 | raise_error(e)
40 | ensure
41 | @installer = nil
42 | end
43 |
44 | def verify(verifier, roles, opts = {}) #:nodoc:
45 | process(verifier.package.name, verifier.commands, roles)
46 | true
47 | rescue LocalCommandError
48 | false
49 | end
50 |
51 | protected
52 |
53 | def process(name, commands, roles, opts = {}) #:nodoc:
54 | @log_recorder = Sprinkle::Utility::LogRecorder.new
55 | commands.each do |command|
56 | if command.is_a?(Commands::Reconnect)
57 | res = 0
58 | elsif command.is_a?(Commands::Transfer)
59 | res = transfer(command.source, command.destination, roles,
60 | :recursive => command.recursive?)
61 | else
62 | res = run_command command
63 | end
64 | raise LocalCommandError if res != 0
65 | end
66 | return true
67 | end
68 |
69 | def run_command(cmd) #:nodoc:
70 | @log_recorder.reset cmd
71 | pid, _, out, err = Open4.popen4(cmd)
72 | _, status = Process::waitpid2 pid
73 | @log_recorder.log :err, err.read
74 | @log_recorder.log :out, out.read
75 | @log_recorder.code = status.to_i
76 | end
77 |
78 | def raise_error(e) #:nodoc:
79 | raise Sprinkle::Errors::RemoteCommandFailure.new(@installer, @log_recorder.hash, e)
80 | end
81 |
82 | def transfer(source, destination, roles, opts ={}) #:nodoc:
83 | opts.reverse_merge!(:recursive => true)
84 | flags = "-R " if opts[:recursive]
85 |
86 | run_command "cp #{flags}#{source} #{destination}"
87 | end
88 | end
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/pecl_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::Pecl do
4 | before do
5 | # @package = double(Sprinkle::Package, :name => 'spec', :class => double(Sprinkle::Package, :installer_methods => []))
6 | @package = Package.new("test") {}
7 | end
8 |
9 | describe 'providing just package name' do
10 | before do
11 | @installer = Sprinkle::Installers::Pecl.new(@package, 'spec')
12 | end
13 |
14 | describe 'during installation' do
15 | it 'should invoke the pecl installer' do
16 | @install_commands = @installer.send :install_commands
17 | @install_commands.should == "TERM= pecl install --alldeps spec"
18 | end
19 | end
20 | end
21 |
22 | describe 'providing explicit version' do
23 | before do
24 | @installer = Sprinkle::Installers::Pecl.new(@package, 'spec', :version => '1.1.1')
25 | end
26 |
27 | describe 'during installation' do
28 | it 'should invoke the pecl installer' do
29 | @install_commands = @installer.send :install_commands
30 | @install_commands.should == "TERM= pecl install --alldeps spec-1.1.1"
31 | end
32 | end
33 | end
34 |
35 | describe 'providing ini_file option' do
36 | describe 'during installation' do
37 | it 'should transfer file with default arguments in post install' do
38 | @installer = Sprinkle::Installers::Pecl.new(@package, 'spec', :ini_file => true) do
39 | self.stub(:file) do |path,options|
40 | path.should == "/etc/php5/conf.d/spec.ini"
41 | options[:content].should == "extension=spec.so"
42 | options[:sudo].should == true
43 | "post install file transfer"
44 | end
45 | end
46 | @install_sequence = @installer.install_sequence
47 | @install_sequence.should include("TERM= pecl install --alldeps spec")
48 | @install_sequence.should include("post install file transfer")
49 | end
50 |
51 | it 'should use custom path and content if provided' do
52 | @installer = Sprinkle::Installers::Pecl.new(@package, 'spec', :ini_file => {:path => "/custom/path", :content => "hello"}) do
53 | self.stub(:file) do |path,options|
54 | path.should == "/custom/path"
55 | options[:content].should == "hello"
56 | end
57 | end
58 | end
59 |
60 | it 'should use custom content if string passed' do
61 | @installer = Sprinkle::Installers::Pecl.new(@package, 'spec', :ini_file => "hello") do
62 | self.stub(:file) do |path,options|
63 | options[:content].should == "hello"
64 | end
65 | end
66 | end
67 |
68 | it 'should NOT use sudo if explicitly denied' do
69 | @installer = Sprinkle::Installers::Pecl.new(@package, 'spec', :ini_file => {:sudo => false}) do
70 | self.stub(:file) do |path,options|
71 | options[:sudo].should == false
72 | end
73 | end
74 | end
75 | end
76 | end
77 |
78 | end
79 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/apt_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::Apt do
4 |
5 | before do
6 | @package = create_pkg "name", :use_sudo => false
7 | end
8 |
9 | def create_pkg(name="name", opts={})
10 | @package = Sprinkle::Package::Package.new(name) {}
11 | @package.use_sudo opts[:use_sudo]
12 | @package
13 | end
14 |
15 | def create_apt(*debs, &block)
16 | @package.apt(*debs, &block)
17 | end
18 |
19 | describe 'when created' do
20 |
21 | it 'should accept a single package to install' do
22 | @installer = create_apt 'ruby'
23 | @installer.packages.should == [ 'ruby' ]
24 | end
25 |
26 | it 'should accept an array of packages to install' do
27 | @installer = create_apt %w( gcc gdb g++ )
28 | @installer.packages.should == ['gcc', 'gdb', 'g++']
29 | end
30 |
31 | it 'should remove options from packages list' do
32 | @installer = create_apt 'ruby', :dependencies_only => true
33 | @installer.packages.should == [ 'ruby' ]
34 | end
35 |
36 | end
37 |
38 |
39 | describe 'during installation' do
40 |
41 | before do
42 | @installer = create_apt 'ruby' do
43 | pre :install, 'op1'
44 | post :install, 'op2'
45 | end
46 | @install_commands = @installer.send :install_commands
47 | end
48 |
49 | it 'should use sudo if package specifies' do
50 | @package = create_pkg "name", :use_sudo => true
51 | @installer = create_apt 'ruby'
52 | @install_commands = @installer.send :install_commands
53 | @install_commands.should =~ /sudo env/
54 | end
55 |
56 | it 'should use sudo if installer specifies' do
57 | @package = create_pkg "name", :use_sudo => false
58 | @installer = create_apt 'ruby', :sudo => true
59 | @install_commands = @installer.send :install_commands
60 | @install_commands.should =~ /sudo env/
61 | end
62 |
63 | it 'should invoke the apt installer for all specified packages' do
64 | @install_commands.should =~ /apt-get --force-yes -qyu install ruby/
65 | end
66 |
67 | it 'should specify a non interactive mode to the apt installer' do
68 | @install_commands.should =~ /env DEBCONF_TERSE='yes' DEBIAN_PRIORITY='critical' DEBIAN_FRONTEND=noninteractive/
69 | end
70 |
71 | it 'should automatically insert pre/post commands for the specified package' do
72 | @installer.send(:install_sequence).should == [ 'op1', %(env DEBCONF_TERSE='yes' DEBIAN_PRIORITY='critical' DEBIAN_FRONTEND=noninteractive apt-get --force-yes -qyu install ruby), 'op2' ]
73 | end
74 |
75 | it 'should install a specific version if defined' do
76 | @installer = create_apt 'ruby=2'
77 | @installer.send(:install_sequence).should == [ %(env DEBCONF_TERSE='yes' DEBIAN_PRIORITY='critical' DEBIAN_FRONTEND=noninteractive apt-get --force-yes -qyu install ruby=2)]
78 | end
79 |
80 | end
81 |
82 | describe 'during dependencies only installation' do
83 |
84 | before do
85 | @installer = create_apt('ruby') { dependencies_only true }
86 | @install_commands = @installer.send :install_commands
87 | end
88 |
89 | it 'should invoke the apt installer with build-dep command for all specified packages' do
90 | @install_commands.should =~ /apt-get --force-yes -qyu build-dep ruby/
91 | end
92 |
93 | end
94 |
95 | end
96 |
--------------------------------------------------------------------------------
/bin/sprinkle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # Created on 2008-3-11.
4 | # Copyright (c) 2008. All rights reserved.
5 |
6 | begin
7 | require 'rubygems'
8 | rescue LoadError
9 | # no rubygems to load, so we fail silently
10 | end
11 |
12 | require 'optparse'
13 |
14 | # NOTE: the option -p/--path= is given as an example, and should probably be replaced in your application.
15 |
16 | OPTIONS = {}
17 | MANDATORY_OPTIONS = %w( path )
18 |
19 | ARGV.each do |arg|
20 | ENV[$1] = $2 if arg =~ /^(\w+)=(.*)$/
21 | end
22 |
23 | require File.dirname(__FILE__) + '/../lib/sprinkle/version'
24 |
25 | parser = OptionParser.new do |opts|
26 | opts.version = Sprinkle::Version
27 | opts.banner = < #{File.basename($0)} [options]
44 |
45 | Options are:
46 | BANNER
47 | opts.separator ""
48 | opts.on("-s", "--script=PATH", String,
49 | "path to a sprinkle script to run") { |v| OPTIONS[:path] = v }
50 | opts.on("--only [ROLE]", String,
51 | "only run sprinkle policies for given role") { |v| OPTIONS[:only_role] = v }
52 | opts.on("-t", "--test",
53 | "process but don't perform any actions","(this does not connect to any servers)") { |v| OPTIONS[:testing] = v }
54 | opts.on("-c", "--cloud",
55 | "show powder cloud, package hierarchy","and installation order") { |v| OPTIONS[:cloud] = v }
56 | opts.on("-f", "--force",
57 | "force installation of all packages","by skipping pre-verify checks.") { |v| OPTIONS[:force] = v }
58 | opts.on("-v", "--verbose",
59 | "verbose output") { |v| OPTIONS[:verbose] = v }
60 | opts.on("-h", "--help",
61 | "show this help message") { puts opts; exit }
62 | opts.parse!(ARGV)
63 |
64 | if MANDATORY_OPTIONS && MANDATORY_OPTIONS.find { |option| OPTIONS[option.to_sym].nil? }
65 | puts opts; exit
66 | end
67 | end
68 |
69 | def only_role(options)
70 | role=OPTIONS[:only_role]
71 | return if role.blank?
72 | Sprinkle::OPTIONS[:only_role] = role
73 | puts "Only running policies for :#{role}"
74 | end
75 |
76 | def force_mode(options)
77 | Sprinkle::OPTIONS[:force] = OPTIONS[:force] || false
78 | end
79 |
80 | def operation_mode(options)
81 | Sprinkle::OPTIONS[:testing] = OPTIONS[:testing] || false
82 | end
83 |
84 | def powder_cloud(options)
85 | Sprinkle::OPTIONS[:cloud] = OPTIONS[:cloud] || false
86 | end
87 |
88 | def verbosity(options)
89 | Sprinkle::OPTIONS[:verbose] = OPTIONS[:verbose] || false
90 | end
91 |
92 | def log_level(options)
93 | Object.logger.level = OPTIONS[:verbose] ? Logger::Severity::DEBUG : Logger::Severity::INFO
94 | end
95 |
96 | require File.dirname(__FILE__) + '/../lib/sprinkle'
97 |
98 | powder = OPTIONS[:path]
99 | raise "Sprinkle script is not readable: #{powder}" unless File.readable?(powder)
100 |
101 | force_mode(OPTIONS)
102 | operation_mode(OPTIONS)
103 | powder_cloud(OPTIONS)
104 | log_level(OPTIONS)
105 | verbosity(OPTIONS)
106 | only_role(OPTIONS)
107 |
108 | Sprinkle::Script.sprinkle File.read(powder), powder
109 |
--------------------------------------------------------------------------------
/lib/sprinkle/deployment.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | # = Deployments
3 | #
4 | # Deployment blocks specify deployment specific information about a
5 | # sprinkle script. An example:
6 | #
7 | # deployment do
8 | # # mechanism for deployment
9 | # delivery :capistrano do
10 | # recipes 'deploy'
11 | # end
12 | #
13 | # # source based package installer defaults
14 | # source do
15 | # prefix '/usr/local'
16 | # archives '/usr/local/sources'
17 | # builds '/usr/local/build'
18 | # end
19 | # end
20 | #
21 | # What the above example does is tell sprinkle that we will be using
22 | # *capistrano* (Sprinkle::Actors::Capistrano) for deployment and
23 | # everything within the block is capistrano specific configuration.
24 | # For more information on what options are available, check the corresponding
25 | # Sprinkle::Actors doc page.
26 | #
27 | # In addition to what delivery mechanism we're using, we specify some
28 | # configuration options for the "source" command. The only things
29 | # configurable, at this time, in the deployment block other than
30 | # the delivery method are installers. If installers are configurable,
31 | # they will say so on their corresponding documentation page. See
32 | # Sprinkle::Installers
33 | #
34 | # The deployment block must be included in the script file passed to the
35 | # sprinkle executable. It may not be loaded from a required file unless you
36 | # first manually include the Sprinkle::Deployment module in the Object class.
37 | #
38 | # Only one deployment block is on any given sprinkle script
39 | module Deployment
40 | # The method outlined above which specifies deployment specific information
41 | # for a sprinkle script. For more information, read the header of this module.
42 | def deployment(&block)
43 | @deployment = Deployment.new(&block)
44 | end
45 |
46 | class Deployment
47 | attr_accessor :style, :defaults #:nodoc:
48 |
49 | def initialize(&block) #:nodoc:
50 | @defaults = {}
51 | @style = nil
52 | self.instance_eval(&block)
53 | raise 'No delivery mechanism defined' unless @style
54 | end
55 |
56 | # Specifies which Sprinkle::Actors to use for delivery. Although all
57 | # actors jobs are the same: to run remote commands on a server, you
58 | # may have a personal preference. The block you pass is used to configure
59 | # the actor. For more information on what configuration options are
60 | # available, view the corresponding Sprinkle::Actors page.
61 | def delivery(type, &block) #:doc:
62 | type=type.to_s.titleize
63 | type="SSH" if type=="Ssh"
64 | @style = ("Sprinkle::Actors::" + type).constantize.new(&block)
65 | end
66 |
67 | def method_missing(sym, *args, &block) #:nodoc:
68 | if Sprinkle::Package::Package.installer_methods.include?(sym)
69 | @defaults[sym] = block
70 | else
71 | super sym, *args, &block
72 | end
73 | end
74 |
75 | def active_policies #:nodoc:
76 | if role=Sprinkle::OPTIONS[:only_role]
77 | role=role.to_sym
78 | POLICIES.select {|x| [x.roles].flatten.include?(role) }
79 | else
80 | POLICIES
81 | end
82 | end
83 |
84 | def process #:nodoc:
85 | active_policies.each do |policy|
86 | policy.process(self)
87 | end
88 | rescue Sprinkle::Errors::RemoteCommandFailure => e
89 | e.print_summary
90 | exit 1
91 | rescue Sprinkle::Errors::TransferFailure => e
92 | e.print_summary
93 | exit 2
94 | ensure
95 | # do any cleanup our actor may need to close network sockets, etc
96 | @style.teardown if @style.respond_to?(:teardown)
97 | end
98 | end
99 | end
100 | end
101 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/gem_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::Gem do
4 |
5 | before do
6 | @gem = 'rails'
7 | @version = '2.0.2'
8 | @options = { :source => 'http://gems.github.com/', :repository => '/tmp/gems', :build_flags => '--build_flag=foo', :http_proxy => 'http://proxy:8080' }
9 | end
10 |
11 | def create_gem(gem, version = nil, options = {}, &block)
12 | # @package = double(Sprinkle::Package, :name => gem, :version => version)
13 | @package = Package.new "test" do; end
14 | @package.version version
15 | Sprinkle::Installers::Gem.new(@package, gem, options, &block)
16 | end
17 |
18 | describe 'when created' do
19 |
20 | before do
21 | @installer = create_gem @gem, @version, @options
22 | end
23 |
24 | it "should return nil if no source is not configured" do
25 | @options.delete(:source)
26 | @installer = create_gem @gem, @version, @options
27 | @installer.source.should == nil
28 | end
29 |
30 | it 'should accept a single package to install' do
31 | @installer.gem.should == @gem
32 | end
33 |
34 | it 'should optionally store a version of the gem to install' do
35 | @installer.version.should == '2.0.2'
36 | end
37 |
38 | it 'should optionally store a source location of the gem to install' do
39 | @installer.source.should == 'http://gems.github.com/'
40 | end
41 |
42 | it 'should optionally store the repository location where gems are to be installed' do
43 | @installer.repository.should == @options[:repository]
44 | end
45 |
46 | it 'should optionally store the build flags' do
47 | @installer.build_flags.should == @options[:build_flags]
48 | end
49 |
50 | it 'should optionally store the http proxy' do
51 | @installer.http_proxy.should == @options[:http_proxy]
52 | end
53 |
54 | end
55 |
56 | describe 'during installation' do
57 |
58 | describe 'without a version' do
59 |
60 | before do
61 | @installer = create_gem @gem do
62 | pre :install, 'op1'
63 | post :install, 'op2'
64 | end
65 | end
66 |
67 | it 'should invoke the gem installer for the specified package' do
68 | @installer.send(:install_commands).should == "gem install #{@gem} --no-rdoc --no-ri"
69 | end
70 |
71 | it 'should automatically insert pre/post commands for the specified package' do
72 | @installer.send(:install_sequence).should == [ 'op1', "gem install #{@gem} --no-rdoc --no-ri", 'op2']
73 | end
74 |
75 | end
76 |
77 | describe 'with a specific version' do
78 |
79 | before do
80 | @installer = create_gem @gem, @version, :build_docs => true
81 | end
82 |
83 | it 'should install a specific version if defined, and with docs' do
84 | @installer.send(:install_commands).should == "gem install #{@gem} --version '#{@version}'"
85 | end
86 |
87 | end
88 |
89 | describe "with sudo" do
90 | before do
91 | @installer = create_gem @gem, nil, :sudo => true
92 | end
93 |
94 | it 'should call sudo' do
95 | @installer.send(:install_commands).should == "sudo gem install #{@gem} --no-rdoc --no-ri"
96 | end
97 |
98 | end
99 |
100 | describe 'with build flags' do
101 |
102 | before do
103 | @installer = create_gem @gem, nil, :build_flags => '--option=foo'
104 | end
105 |
106 | it 'should install with defined build flags' do
107 | @installer.send(:install_commands).should == "gem install #{@gem} --no-rdoc --no-ri -- --option=foo"
108 | end
109 |
110 | end
111 |
112 | describe 'with http proxy' do
113 |
114 | before do
115 | @installer = create_gem @gem, nil, :http_proxy => 'http://proxy:8080'
116 | end
117 |
118 | it 'should install with defined build flags' do
119 | @installer.send(:install_commands).should == "gem install #{@gem} --no-rdoc --no-ri --http-proxy http://proxy:8080"
120 | end
121 |
122 | end
123 |
124 | end
125 |
126 | end
127 |
--------------------------------------------------------------------------------
/lib/sprinkle/actors/vlad.rb:
--------------------------------------------------------------------------------
1 | require 'vlad'
2 |
3 | module Sprinkle
4 | module Actors
5 | # The Vlad actor is one of the delivery method options available out of the
6 | # box with Sprinkle. If you have the vlad the deployer gem installed, you
7 | # may use this delivery. The only configuration option available, and
8 | # which is mandatory to include is +script+. An example:
9 | #
10 | # deployment do
11 | # delivery :vlad do
12 | # script 'deploy'
13 | # end
14 | # end
15 | #
16 | # script is given a list of files which vlad will include and load.
17 | # These recipes are mainly to set variables such as :user, :password, and to
18 | # set the app domain which will be sprinkled.
19 | class Vlad < Actor
20 | attr_accessor :loaded_recipes #:nodoc:
21 |
22 | def initialize(&block) #:nodoc:
23 | self.instance_eval &block if block
24 | end
25 |
26 | def servers_for_role? #:nodoc:
27 | raise "The vlad actor needs a maintainer. "+
28 | "Please file an issue on github.com/sprinkle-tool/sprinkle if you can help."
29 | end
30 |
31 | def sudo? #:nodoc:
32 | # TODO
33 | raise
34 | end
35 |
36 | def sudo_command #:nodoc:
37 | "sudo"
38 | end
39 |
40 | # Defines a script file which will be included by vlad. Use these
41 | # script files to set vlad specific configurations. Multiple scripts
42 | # may be specified through multiple script calls, an example:
43 | #
44 | # deployment do
45 | # delivery :vlad do
46 | # script 'deploy'
47 | # script 'magic_beans'
48 | # end
49 | # end
50 | def script(name)
51 | @loaded_recipes ||= []
52 | require name
53 | @loaded_recipes << name
54 | end
55 |
56 | def install(installer, roles, opts={}) #:nodoc:
57 | @installer=installer
58 | if installer.install_sequence.include?(:TRANSFER)
59 | process_with_transfer(installer.package.name, installer.install_sequence, roles, opts)
60 | else
61 | process(installer.package.name, installer.install_sequence, roles, opts)
62 | end
63 | # recast our rake error to the common sprinkle error type
64 | rescue ::Rake::CommandFailedError => e
65 | raise Sprinkle::Errors::RemoteCommandFailure.new(installer, {}, e)
66 | ensure
67 | @installer = nil
68 | end
69 |
70 | def verify(verifier, roles, opts={}) #:nodoc:
71 | process(verifier.package.name, commands, roles,
72 | :suppress_and_return_failures => true)
73 | end
74 |
75 | protected
76 |
77 | def process(name, commands, roles, opts ={}) #:nodoc:
78 | commands = commands.map{|x| "#{sudo_command} #{x}"} if sudo?
79 | commands = commands.join(' && ')
80 | puts "executing #{commands}"
81 | task = remote_task(task_sym(name), :roles => roles) { run commands }
82 | invoke(task)
83 | end
84 |
85 | def process_with_transfer(name, commands, roles, opts ={}) #:nodoc:
86 | raise "cant do non recursive file transfers, sorry" if opts[:recursive] == false
87 | commands = commands.map{|x| x == :TRANSFER ? x : "sudo #{x}" } if sudo?
88 | i = commands.index(:TRANSFER)
89 | before = commands.first(i).join(" && ")
90 | after = commands.last(commands.size-i+1).join(" && ")
91 | inst = @installer
92 | task = remote_task(task_sym(name), :roles => roles) do
93 | run before unless before.empty?
94 | rsync inst.sourcepath, inst.destination
95 | run after unless after.empty?
96 | end
97 | invoke(task)
98 | end
99 |
100 | def invoke(t) #:nodoc:
101 | t.invoke
102 | return true
103 | rescue ::Rake::CommandFailedError => e
104 | return false if opts[:suppress_and_return_failures]
105 | # Reraise error if we're not suppressing it
106 | raise e
107 | end
108 |
109 | private
110 |
111 | def task_sym(name) #:nodoc:
112 | "install_#{name.to_task_name}".to_sym
113 | end
114 | end
115 | end
116 | end
117 |
--------------------------------------------------------------------------------
/lib/sprinkle/installers/file.rb:
--------------------------------------------------------------------------------
1 | require 'tempfile'
2 |
3 | module Sprinkle
4 | module Installers
5 | # = File installer
6 | #
7 | # This installer creates a file on the remote server.
8 | #
9 | # == Example Usage
10 | #
11 | # Installing a nginx.conf onto remote servers
12 | #
13 | # package :nginx_conf do
14 | # file '/etc/nginx.conf', :content => File.read('files/nginx.conf'),
15 | # :sudo => true
16 | # end
17 | #
18 | # Sudo is only necessary when the user your sprinkle is running as does
19 | # not have necessarily permissions to create the file on its own.
20 | # Such as when the file is in /etc.
21 | #
22 | # Should you need to run commands before or after the file transfer (making
23 | # directories or changing permissions), you can use the pre/post :install directives.
24 | #
25 | # == Rendering templates
26 | #
27 | # Use the template render helper to render an ERB template to a remote file (you
28 | # can use variables in your templates by setting them as instance variables inside
29 | # your package. Templates have access to package methods such as opts, args, etc.
30 | #
31 | # package :nginx_conf do
32 | # @nginx_port = 8080
33 | # file '/etc/nginx.conf',
34 | # :contents => render("nginx.conf")
35 | # # where [cwd] is the current working dir you're running sprinkle from
36 | # # [cwd]/templates/nginx.conf.erb or
37 | # # [cwd]/templates/nginx.conf should contain the erb template
38 | # end
39 | #
40 | # You can also tell the package where to look for templates, so that if you have
41 | # a complex package hierarchy such as:
42 | #
43 | # .../packages/p/postfix.rb
44 | # .../packages/p/postfix/templates/main.cf.erb
45 | #
46 | # package :postfix do
47 | # template_search_path File.dirname(__FILE__)
48 | # file '/etc/postfix/main.cf', :contents => render("main.cf")
49 | # # searches for:
50 | # # ../packages/p/main.cf[.erb]
51 | # # ../packages/p/templates/main.cf[.erb]
52 | # end
53 | class FileInstaller < Installer
54 | attr_reader :sourcepath, :destination, :contents #:nodoc:
55 |
56 | api do
57 | def file(destination, options = {}, &block) #:nodoc:
58 | # options.merge!(:binding => binding())
59 | install FileInstaller.new(self, destination, options, &block)
60 | end
61 | end
62 |
63 | def initialize(parent, destination, options={}, &block) #:nodoc:
64 | @destination = destination
65 | @contents = options[:content] || options[:contents]
66 | raise "need :contents key for file" unless @contents
67 | super parent, options, &block
68 |
69 | # setup file attributes
70 | owner options[:owner] if options[:owner]
71 | mode options[:mode] if options[:mode]
72 |
73 | post_move_if_sudo
74 | setup_source
75 | end
76 |
77 | def install_commands #:nodoc:
78 | Commands::Transfer.new(sourcepath, destination)
79 | end
80 |
81 | # calls chown own to set the file ownership
82 | def owner(owner)
83 | @owner = owner
84 | post :install, "#{sudo_cmd}chown #{owner} #{@destination}"
85 | end
86 |
87 | # calls chmod to set the files permissions
88 | def mode(mode)
89 | @mode = mode
90 | post :install, "#{sudo_cmd}chmod #{mode} #{@destination}"
91 | end
92 |
93 | private
94 |
95 | def post_move_if_sudo
96 | return unless sudo? # perform the file copy in two steps if we're using sudo
97 | final = @destination
98 | @destination = "/tmp/sprinkle_#{File.basename(@destination)}"
99 | # make sure we push the move ahead of any other post install tasks
100 | # a user may have requested
101 | post(:install).unshift ["#{sudo_cmd}mv #{@destination} #{final}"]
102 | end
103 |
104 | def setup_source
105 | @file = Tempfile.new(@package.name.to_s)
106 | @file.print @contents
107 | @file.close
108 | @sourcepath = @file.path
109 | end
110 |
111 | def post_process
112 | @file.unlink
113 | end
114 |
115 | end
116 | end
117 | end
118 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/push_text_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::PushText do
4 |
5 | before do
6 | @package = double(Sprinkle::Package, :name => 'package', :sudo? => false)
7 | @options = {:sudo => true}
8 | end
9 |
10 | def create_text(text, path, options={}, &block)
11 | # the old default
12 | options.reverse_merge!(:idempotent => false)
13 | Sprinkle::Installers::PushText.new(@package, text, path, options, &block)
14 | end
15 |
16 | describe 'when created' do
17 |
18 | it 'should accept a single package to install' do
19 | @installer = create_text 'crazy-configuration-methods', '/etc/doomed/file.conf'
20 | @installer.text.should eq 'crazy-configuration-methods'
21 | @installer.path.should eq '/etc/doomed/file.conf'
22 | end
23 |
24 | end
25 |
26 | describe 'during installation' do
27 |
28 | before do
29 | @installer = create_text 'another-hair-brained-idea', '/dev/mind/late-night' do
30 | pre :install, 'op1'
31 | post :install, 'op2'
32 | end
33 | @install_commands = @installer.send :install_commands
34 | end
35 |
36 | describe 'with idempotent' do
37 | before do
38 | @installer = create_text 'another-hair-brained-idea', '/dev/mind/late-night', :idempotent => true
39 | @install_commands = @installer.send :install_commands
40 | end
41 | it "should grep for existing of the string" do
42 | @install_commands.should eq %q
43 | end
44 | end
45 |
46 | describe 'with multiline idempotent' do
47 | before do
48 | mline = <<-MULTI
49 | ^search( [adnor]{2,3} rescue)?$
50 | ^fries( [adnor]{2,3} barbecue)?
51 | ^songs( [adnor]{2,3} autocue)?
52 | MULTI
53 | @installer = create_text mline.strip, '/dev/mind/late-night', :idempotent => true
54 | @install_commands = @installer.send :install_commands
55 | end
56 | it "should grep for existence of the string" do
57 | @install_commands.should eq %q
58 | end
59 | end
60 |
61 | describe 'with sudo' do
62 | before do
63 | @installer = create_text 'another-hair-brained-idea', '/dev/mind/late-night', :sudo => true
64 | @install_commands = @installer.send :install_commands
65 | end
66 | it 'should invoke sudo if sudo option passed' do
67 | @install_commands.should eq %q[/bin/echo -e 'another-hair-brained-idea' |sudo tee -a /dev/mind/late-night]
68 | end
69 | end
70 |
71 | it 'should invoke the push text installer for all specified packages' do
72 | @install_commands.should eq %q[/bin/echo -e 'another-hair-brained-idea' |tee -a /dev/mind/late-night]
73 | end
74 |
75 | it 'should automatically insert pre/post commands for the specified package' do
76 | @installer.send(:install_sequence).should eq [ 'op1', "/bin/echo -e 'another-hair-brained-idea' |tee -a /dev/mind/late-night", 'op2' ]
77 | end
78 |
79 | end
80 |
81 | describe 'running with sudo' do
82 | before do
83 | @installer = create_text "a special user", "/dev/mind/the-day-after", :sudo => true
84 | @install_commands = @installer.send :install_commands
85 | end
86 |
87 | it "should invoke the push installer with sudo" do
88 | @install_commands.should eq %q[/bin/echo -e 'a special user' |sudo tee -a /dev/mind/the-day-after]
89 | end
90 | end
91 |
92 | describe 'sending a string with special characters' do
93 |
94 | it "should not escape an ampersand" do
95 | @installer = create_text "bob & lucy", "/dev/mind/the-day-after"
96 | @install_commands = @installer.send :install_commands
97 | @install_commands.should eq %q[/bin/echo -e 'bob & lucy' |tee -a /dev/mind/the-day-after]
98 | end
99 |
100 | it "should not escape a slash" do
101 | @installer = create_text "bob/lucy", "/dev/mind/the-day-after"
102 | @install_commands = @installer.send :install_commands
103 | @install_commands.should eq %q[/bin/echo -e 'bob/lucy' |tee -a /dev/mind/the-day-after]
104 | end
105 | end
106 |
107 | describe 'sending a string with single quotes' do
108 | before do
109 | @installer = create_text "I'm a string with a single quote", "/dev/mind/the-day-after"
110 | @install_commands = @installer.send :install_commands
111 | end
112 |
113 | it "should correctly encode the single quote character" do
114 | @install_commands.should eq %q[/bin/echo -e 'I'\''m a string with a single quote' |tee -a /dev/mind/the-day-after]
115 | end
116 | end
117 |
118 | end
119 |
--------------------------------------------------------------------------------
/lib/sprinkle/verify.rb:
--------------------------------------------------------------------------------
1 | module Sprinkle
2 | # = Verify Blocks
3 | #
4 | # As documented in Sprinkle::Package, you may define a block on a package
5 | # which verifies that a package was installed correctly. If this verification
6 | # block fails, Sprinkle will stop the script gracefully, raising the error.
7 | #
8 | # In addition to checking post install if it was successfully, verification
9 | # blocks are also run before an install to see if a package is already
10 | # installed. If this is the case, the package is skipped and Sprinkle continues
11 | # with the next package. This behavior can be overriden by setting the -f flag on
12 | # the sprinkle script or setting Sprinkle::OPTIONS[:force] to true if you're
13 | # using sprinkle programmatically.
14 | #
15 | # == An Example
16 | #
17 | # The following verifies that rails was installed correctly be checking to see
18 | # if the 'rails' command is available on the command line:
19 | #
20 | # package :rails do
21 | # gem 'rails'
22 | #
23 | # verify do
24 | # has_executable 'rails'
25 | # end
26 | # end
27 | #
28 | # == Available Verifiers
29 | #
30 | # There are a variety of available methods for use in the verification block.
31 | # The standard methods are defined in the Sprinkle::Verifiers module, so see
32 | # their corresponding documentation.
33 | #
34 | # == Custom Verifiers
35 | #
36 | # If you feel that the built-in verifiers do not offer a certain aspect of
37 | # verification which you need, you may create your own verifier! Simply wrap
38 | # any method in a module which you want to use:
39 | #
40 | # module MagicBeansVerifier
41 | # def has_magic_beans(sauce)
42 | # @commands << '[ -z "`echo $' + sauce + '`"]'
43 | # end
44 | # end
45 | #
46 | # The method can append as many commands as it wishes to the @commands array.
47 | # These commands will be run on the remote server and MUST give an
48 | # exit status of 0 if successful or other if unsuccessful.
49 | #
50 | # To register your verifier, call the register method on Sprinkle::Verify:
51 | #
52 | # Sprinkle::Verify.register(MagicBeansVerifier)
53 | #
54 | # And now you may use it like any other verifier:
55 | #
56 | # package :magic_beans do
57 | # gem 'magic_beans'
58 | #
59 | # verify { has_magic_beans('ranch') }
60 | # end
61 | class Verify
62 | include Sprinkle::Attributes
63 | include Sprinkle::Package::Rendering::Helpers
64 | include Sprinkle::Sudo
65 | attr_accessor :package, :description, :options #:nodoc:
66 |
67 | delegate :opts, :to => :package
68 | delegate :args, :to => :package
69 | delegate :version, :to => :package
70 | delegate :description, :to => :package
71 |
72 | class < Verifying #{description}..."
128 |
129 | unless @delivery.verify(self, roles)
130 | # Verification failed, halt sprinkling gracefully.
131 | raise Sprinkle::VerificationFailed.new(@package, description)
132 | end
133 | end
134 | end
135 | end
136 |
137 | class VerificationFailed < Exception #:nodoc:
138 | attr_accessor :package, :description
139 |
140 | def initialize(package, description)
141 | super("Verifying #{package.name}#{description} failed.")
142 |
143 | @package = package
144 | @description = description
145 | end
146 | end
147 | end
148 |
--------------------------------------------------------------------------------
/lib/sprinkle/policy.rb:
--------------------------------------------------------------------------------
1 | require 'highline/import'
2 |
3 | module Sprinkle
4 | class NoMatchingServersError < StandardError #:nodoc:
5 | def initialize(name, roles)
6 | @name = name
7 | @roles = roles
8 | end
9 |
10 | def to_s
11 | "Policy #{@name} is to be installed on #{@roles.inspect} but no server has such a role."
12 | end
13 | end
14 |
15 | class MissingPackageError < StandardError #:nodoc:
16 | def initialize(name)
17 | @name = name
18 | end
19 |
20 | def to_s
21 | "Package definition not found for key: #{@name}"
22 | end
23 | end
24 |
25 | # = Policies
26 | #
27 | # Policies define a set of packages which are required for a certain
28 | # role (app, database, etc.). All policies defined will be run and all
29 | # packages required by the policy will be installed. So whereas defining
30 | # a Sprinkle::Package merely defines it, defining a Sprinkle::Policy
31 | # actually causes those packages to install.
32 | #
33 | # == Example
34 | #
35 | # policy :blog, :roles => :app do
36 | # requires :webserver
37 | # requires :database
38 | # requires :rails
39 | # end
40 | #
41 | # This says that for the blog on the app role, it requires certain
42 | # packages. The :roles option is exactly the same as a capistrano
43 | # or vlad role. A role merely defines what server the commands are run
44 | # on. This way, a single Sprinkle script can provision an entire group
45 | # of servers.
46 | #
47 | # To define a role, put in your actor specific configuration file (recipe or
48 | # script file):
49 | #
50 | # role :app, "208.28.38.44"
51 | #
52 | # The capistrano and vlad syntax is the same for that. If you're using a
53 | # custom actor, you may have to do it differently.
54 | #
55 | # == Requiring a package more than once with different options
56 | #
57 | # This works exactly as you might expect:
58 | #
59 | # policy :bootstrap, :roles => :app do
60 | # require :user_settings, :for => "john"
61 | # require :user_settings, :for => "suzy"
62 | # require :user_settings, :for => "dorothy"
63 | # end
64 | #
65 | # Multiple requires for a package with no options will be
66 | # collapsed; that package will be installed once.
67 | #
68 | # policy :apache, :roles => :app do
69 | # require :devtools
70 | # ...
71 | # end
72 | # policy :git, :roles => :app do
73 | # require :devtools
74 | # ...
75 | # end
76 | #
77 | # In this example devtools will only be installed once, prior to
78 | # apache and git.
79 | #
80 | # == Multiple Policies
81 | #
82 | # You may specify as many policies as you'd like. If the packages you're
83 | # requiring are properly defined with verification blocks, then
84 | # no software will be installed twice, so you may require a webserver on
85 | # multiple packages within the same role without having to wait for
86 | # that package to install repeatedly.
87 | class Policy
88 | attr_reader :name
89 | # roles for which a policy should be installed [required]
90 | attr_reader :roles
91 |
92 | # creates a new policy,
93 | # although policies are typically not created directly but
94 | # rather via the Core#policy helper.
95 | def initialize(name, metadata = {}, &block)
96 | raise 'No name provided' unless name
97 | raise 'No roles provided' unless metadata[:roles]
98 |
99 | @name = name
100 | @roles = metadata[:roles]
101 | @packages = []
102 | self.instance_eval(&block)
103 | end
104 |
105 | # tell a policy which packages are required
106 | def requires(package, *args)
107 | @packages << [package, args]
108 | end
109 |
110 | def packages #:nodoc:
111 | @packages.map {|x| x.first }
112 | end
113 |
114 | def to_s #:nodoc:
115 | name; end
116 |
117 | def process(deployment) #:nodoc:
118 | raise NoMatchingServersError.new(@name, @roles) unless deployment.style.servers_for_role?(@roles)
119 |
120 | logger.info "[#{name}]"
121 |
122 | package_install_tree.each do |package|
123 | package.process(deployment, @roles)
124 | end
125 | end
126 |
127 | def package_install_tree
128 | @install_tree ||= normalize(tree)
129 | end
130 |
131 | private
132 |
133 | def tree()
134 | all = []
135 |
136 | cloud_info "--> Cloud hierarchy for policy #{@name}"
137 |
138 | @packages.each do |p, args|
139 | cloud_info " * requires package #{p}"
140 |
141 | opts = args.clone.extract_options!
142 | package = Sprinkle::Package::PACKAGES.find_all(p, opts)
143 | raise MissingPackageError.new(p) unless package.any?
144 | package = Sprinkle::Package::Chooser.select_package(p, package) if package.is_a? Array # handle virtual package selection
145 | # get an instance of the package and pass our config options
146 | package = package.instance(*args)
147 | tree = package.tree do |parent, child, depth|
148 | indent = "\t" * depth; cloud_info "#{indent}Package #{parent.name} requires #{child.name}"
149 | end
150 |
151 | all << tree
152 | end
153 | all
154 |
155 | end
156 |
157 | def normalize(all, &block)
158 | all = all.flatten.uniq {|x| [x.name, x.version, x.opts] }
159 | cloud_info "--> Normalized installation order for all packages: #{all.collect(&:name).join(', ')}\n"
160 | all
161 | end
162 |
163 | def cloud_info(message)
164 | logger.info(message) if Sprinkle::OPTIONS[:cloud] or logger.debug?
165 | end
166 |
167 | end
168 | end
169 |
--------------------------------------------------------------------------------
/spec/sprinkle/installers/file_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Installers::FileInstaller do
4 | include Sprinkle::Deployment
5 |
6 | before do
7 | @package = double(Sprinkle::Package, :name => 'package', :sudo? => false)
8 | @empty = Proc.new { }
9 | @delivery = double(Sprinkle::Deployment, :install => true)
10 | @destination = 'destination'
11 | @contents = "hi"
12 | @installer = create_file_installer(@destination, :contents => @contents)
13 | @roles = []
14 | @deployment = deployment do
15 | delivery :capistrano
16 | source do; prefix '/usr/bin'; end
17 | end
18 | end
19 |
20 | def simplify(seq)
21 | seq.map do |cmd|
22 | cmd.is_a?(Sprinkle::Commands::Transfer) ? :TRANSFER : cmd
23 | end
24 | end
25 |
26 | def create_file_installer(dest, options={}, &block)
27 | i = Sprinkle::Installers::FileInstaller.new(@package, dest, options, &block)
28 | i.delivery = @delivery
29 | i
30 | end
31 |
32 | describe 'when created' do
33 | before do
34 | @source = "/tmp/file"
35 | Tempfile.any_instance.stub(:path).and_return(@source)
36 | @installer = create_file_installer(@destination, :contents => @contents)
37 | @transfer = @installer.install_sequence.detect {|x| x.is_a? Sprinkle::Commands::Transfer }
38 | end
39 |
40 | it 'should accept a source and destination to install' do
41 | @installer.contents.should eq @contents
42 | @installer.destination.should eq @destination
43 | end
44 |
45 | it 'should create a transfer command' do
46 | @transfer.source.should eq @source
47 | @transfer.destination.should eq @destination
48 | end
49 |
50 | it 'should not be using recursive' do
51 | @transfer.recursive?.should eq false
52 | end
53 | end
54 |
55 | describe 'during installation' do
56 |
57 | context "post process hooks" do
58 |
59 | it "should remove its temporary file" do
60 | @tmp = double(:print => true, :close => true, :path => "/file")
61 | Tempfile.stub(:new).and_return(@tmp)
62 | @installer = create_file_installer(@destination, :contents => @contents)
63 | @tmp.should_receive(:unlink)
64 | end
65 |
66 | end
67 |
68 | context "setting mode and owner" do
69 | before do
70 | @installer = create_file_installer @destination, :content => @contents do
71 | mode "744"
72 | owner "root"
73 | end
74 | @installer_commands = @installer.install_sequence
75 | end
76 |
77 | it "should include command to set owner" do
78 | @installer_commands.should include("chmod 744 #{@destination}")
79 | end
80 |
81 | it "should include command to set mode" do
82 | @installer_commands.should include("chown root #{@destination}")
83 | end
84 |
85 | end
86 |
87 | context "setting mode and owner with sudo" do
88 | before do
89 | @installer = create_file_installer @destination, :content => @contents do
90 | @options[:sudo]= true
91 | mode "744"
92 | owner "root"
93 | end
94 | @installer_commands = simplify @installer.install_sequence
95 | end
96 |
97 | it "should run commands in correct order" do
98 | @installer_commands.should eq [
99 | :TRANSFER,
100 | "sudo mv /tmp/sprinkle_#{@destination} #{@destination}",
101 | "sudo chmod 744 #{@destination}",
102 | "sudo chown root #{@destination}"
103 | ]
104 | end
105 | end
106 |
107 | context "setting mode and owner with sudo as options" do
108 | before do
109 | @installer = create_file_installer @destination, :content => @contents,
110 | :mode => "744", :owner => "root" do
111 | @options[:sudo]= true
112 | end
113 | @installer_commands = simplify @installer.install_sequence
114 | end
115 |
116 | it "should run commands in correct order" do
117 | @installer_commands.should eq [
118 | :TRANSFER,
119 | "sudo mv /tmp/sprinkle_#{@destination} #{@destination}",
120 | "sudo chown root #{@destination}",
121 | "sudo chmod 744 #{@destination}"
122 | ]
123 | end
124 |
125 | end
126 |
127 |
128 | context 'single pre/post commands' do
129 | before do
130 | @installer = create_file_installer @destination, :content => @contents do
131 | pre :install, 'op1'
132 | post :install, 'op2'
133 | end
134 | @installer_commands = simplify @installer.install_sequence
135 | @delivery = @installer.delivery
136 | end
137 |
138 | it "should call the pre and post install commands around the file transfer" do
139 | @installer_commands.should eq ["op1",:TRANSFER, "op2"]
140 | end
141 |
142 | end
143 |
144 | context 'pre/post with sudo' do
145 | before do
146 | @installer = create_file_installer @destination, :content => @contents do
147 | @options[:sudo]= true
148 | pre :install, 'op1'
149 | post :install, 'op2'
150 | end
151 | @installer_commands = simplify @installer.install_sequence
152 | @delivery = @installer.delivery
153 | end
154 |
155 | it "should call the pre and post install commands around the file transfer" do
156 | @installer_commands.should eq ["op1",:TRANSFER,
157 | "sudo mv /tmp/sprinkle_destination destination", "op2"]
158 | end
159 | end
160 |
161 | context 'multiple pre/post commands' do
162 | before do
163 | @installer = create_file_installer @destination, :content => @contents do
164 | pre :install, 'op1', 'op1-1'
165 | post :install, 'op2', 'op2-1'
166 | end
167 | @installer_commands = simplify @installer.install_sequence
168 | @delivery = @installer.delivery
169 | end
170 |
171 | it "should call the pre and post install commands around the file transfer" do
172 | @installer_commands.should eq ["op1","op1-1",:TRANSFER, "op2","op2-1"]
173 | end
174 |
175 | end
176 |
177 | after do
178 | @installer.process @roles
179 | end
180 | end
181 |
182 | end
183 |
--------------------------------------------------------------------------------
/spec/sprinkle/policy_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path("../spec_helper", File.dirname(__FILE__))
2 |
3 | describe Sprinkle::Policy do
4 | include Sprinkle::Core
5 |
6 | before do
7 | @name = 'a policy'
8 | end
9 |
10 | describe 'with a role with no matching servers' do
11 | before do
12 | @policy = policy @name, :roles => :app do; end
13 | end
14 |
15 | it "should raise an error" do
16 | @deployment = double(:style => Sprinkle::Actors::Dummy.new {})
17 | lambda { @policy.process(@deployment) }.should raise_error(Sprinkle::NoMatchingServersError)
18 | end
19 | end
20 |
21 | describe 'when created' do
22 |
23 | it 'should be invalid without a name' do
24 | lambda { policy nil }.should raise_error
25 | end
26 |
27 | it 'should be invalid without role definitions' do
28 | lambda { policy @name do; end }.should raise_error
29 | lambda { policy @name, :roles => :app do; end }.should_not raise_error
30 | end
31 |
32 | it 'should optionally accept package dependencies' do
33 | p = policy @name, :roles => :app do; end
34 | p.should respond_to(:requires)
35 | p.requires :appserver
36 | p.packages.should eq [ :appserver ]
37 | end
38 |
39 | it 'should optionally accept package dependencies with versions' do
40 | p = policy @name, :roles => :app do; end
41 | p.requires :appserver, :version => 2
42 | p.packages.should eq [ :appserver ]
43 | # pending 'requires version checking implementation'
44 | end
45 |
46 | it 'should add itself to the global policy list' do
47 | sz = Sprinkle::POLICIES.size
48 | p = policy @name, :roles => :app do; end
49 | Sprinkle::POLICIES.size.should eq sz + 1
50 | Sprinkle::POLICIES.last.should eq p
51 | end
52 |
53 | end
54 |
55 | describe 'with the same package multiple times' do
56 | include Sprinkle::Package
57 |
58 | before do
59 | @deployment = double(Sprinkle::Deployment)
60 | actor = double(:servers_for_role? => true)
61 | @deployment.stub(:style).and_return(actor)
62 | Sprinkle::Package::PACKAGES.clear # reset full package list before each spec is run
63 |
64 | @user = package :user do; end
65 |
66 | @policy = policy :test, :roles => :app do
67 | requires :user, :name => "josh"
68 | requires :user, :name => "bill"
69 | end
70 | end
71 |
72 | it "should call process on both users" do
73 | (all=@policy.package_install_tree).size.should == 2
74 | all.each do |p|
75 | p.should_receive(:process).and_return
76 | end
77 | end
78 |
79 | after do
80 | @policy.process(@deployment)
81 | end
82 | end
83 |
84 | describe 'with packages' do
85 | include Sprinkle::Package
86 |
87 | before do
88 | @deployment = double(Sprinkle::Deployment)
89 | actor = double(:servers_for_role? => true)
90 | @deployment.stub(:style).and_return(actor)
91 | Sprinkle::Package::PACKAGES.clear # reset full package list before each spec is run
92 |
93 | @a = package :a do; requires :b; requires :c; end
94 | @b = package :b, :provides => :xyz do; end
95 | @c = package :c, :provides => :abc do; end
96 | @d = package :d, :provides => :abc do; end
97 |
98 | @a.stub(:instance).and_return(@a)
99 | @b.stub(:instance).and_return(@b)
100 | @c.stub(:instance).and_return(@c)
101 | @d.stub(:instance).and_return(@d)
102 |
103 | @policy = policy :test, :roles => :app do; requires :a; end
104 | $terminal.stub(:choose).and_return(@c) # stub out highline asking questions
105 | end
106 |
107 | describe 'when applying' do
108 | include Sprinkle::Package
109 |
110 | it 'should determine the packages to install via the hierarchy dependency tree of each package in the policy' do
111 | @a.should_receive(:process).and_return
112 | @b.should_receive(:process).and_return
113 | @c.should_receive(:process).and_return
114 | @d.should_not_receive(:process)
115 | end
116 |
117 | it 'should normalize (ie remove duplicates from) the installation order of all packages including dependencies' do
118 | @e = package :e do; requires :b; end
119 | @policy.requires :e
120 | @e.stub(:instance).and_return(@e)
121 |
122 | @a.should_receive(:process).once.and_return
123 | @b.should_receive(:process).once.and_return
124 | @c.should_receive(:process).once.and_return
125 | @d.should_not_receive(:process)
126 | @e.should_receive(:process).once.and_return
127 | end
128 | end
129 |
130 | describe 'containing package dependencies with versions' do
131 |
132 | it 'should select the correct package version when applying' do
133 | @my3 = package :mysql do; version 3; end
134 | @my4 = package :mysql do; version 4; end
135 | @my5 = package :mysql do; version 5; end
136 | @e = package :e do; requires :mysql, :version => "4"; end
137 | @policy.requires :e
138 | @e.stub(:instance).and_return @e
139 | @my4.stub(:instance).and_return @my4
140 | @my3.should_not_receive(:process)
141 | @my5.should_not_receive(:process)
142 | @my4.should_receive(:process)
143 | end
144 | end
145 |
146 | describe 'containing virtual packages' do
147 |
148 | it 'should automatically select a concrete package implementation for a virtual one when there exists only one possible selection' do
149 | @policy = policy :virtual, :roles => :app do; requires :xyz; end
150 | @b.should_receive(:process)
151 | end
152 |
153 | it 'should ask the user for the concrete package implementation to use for a virtual one when more than one possible choice exists' do
154 | @policy = policy :virtual, :roles => :app do; requires :abc; end
155 | $terminal.should_receive(:choose).and_return(@c)
156 | @c.should_receive(:process)
157 | end
158 |
159 | end
160 |
161 | after do
162 | @policy.process(@deployment)
163 | end
164 | end
165 | end
166 |
167 | describe Sprinkle::Policy, 'with missing packages' do
168 |
169 | before do
170 | @deployment = double(Sprinkle::Deployment)
171 | actor = double(:servers_for_role? => true)
172 | @deployment.stub(:style).and_return(actor)
173 | Sprinkle::Package::PACKAGES.clear # reset full package list before each spec is run
174 |
175 | @policy = policy :test, :roles => :app do; requires :z; end
176 | $terminal.stub(:choose).and_return(:c) # stub out highline asking questions
177 | end
178 |
179 | it 'should raise an error if a package is missing' do
180 | lambda { @policy.process(@deployment) }.should raise_error
181 | end
182 |
183 | end
184 |
--------------------------------------------------------------------------------
/lib/sprinkle/actors/capistrano.rb:
--------------------------------------------------------------------------------
1 | require 'capistrano/cli'
2 |
3 | module Sprinkle
4 | module Actors
5 | # The Capistrano actor uses Capistrano to define your roles and deliver
6 | # commands to your remote servers. You'll need the capistrano gem installed.
7 | #
8 | # The only configuration option is to specify a recipe.
9 | #
10 | # deployment do
11 | # delivery :capistrano do
12 | # recipe 'deploy'
13 | # recipe 'more'
14 | # end
15 | # end
16 | #
17 | # Recipes is given a list of files which capistrano will include and load.
18 | # These recipes are mainly to set variables such as :user, :password, and to
19 | # set the app domain which will be sprinkled.
20 | class Capistrano < Actor
21 | attr_accessor :config, :loaded_recipes #:nodoc:
22 |
23 | def initialize(&block) #:nodoc:
24 | @installer = nil
25 | @config = ::Capistrano::Configuration.new
26 | @config.logger.level = Sprinkle::OPTIONS[:verbose] ? ::Capistrano::Logger::INFO : ::Capistrano::Logger::IMPORTANT
27 | @config.set(:password) { ::Capistrano::CLI.password_prompt }
28 | # default sudo to false, we must turn it on
29 | @config.set(:run_method) { @config.fetch(:use_sudo, false) ? :sudo : :run }
30 |
31 | @config.set(:_sprinkle_actor, self)
32 |
33 | def @config.recipes(script)
34 | _sprinkle_actor.recipes(script)
35 | end
36 |
37 | if block
38 | @config.instance_eval(&block)
39 | else
40 | @config.load "Capfile" if File.exist?("Capfile")
41 | end
42 | end
43 |
44 | def sudo? #:nodoc:
45 | @config.fetch(:run_method) == :sudo
46 | end
47 |
48 | def sudo_command #:nodoc:
49 | @config.sudo
50 | end
51 |
52 | # Determines if there are any servers for the given roles
53 | def servers_for_role?(roles) #:nodoc:
54 | roles=Array(roles)
55 | roles.any? { |r| @config.roles.keys.include?(r) }
56 | end
57 |
58 |
59 | # Defines a recipe file which will be included by capistrano. Use these
60 | # recipe files to set capistrano specific configurations. Default recipe
61 | # included is "deploy." But if any other recipe is specified, it will
62 | # include that instead. Multiple recipes may be specified through multiple
63 | # recipes calls, an example:
64 | #
65 | # deployment do
66 | # delivery :capistrano do
67 | # recipe 'deploy'
68 | # recipes 'magic_beans', 'normal_beans'
69 | # end
70 | # end
71 | def recipe(scripts)
72 | @loaded_recipes ||= []
73 | Array(scripts).each do |script|
74 | @config.load script
75 | @loaded_recipes << script
76 | end
77 | end
78 |
79 | def recipes(scripts) #:nodoc:
80 | recipe(scripts)
81 | end
82 |
83 | def install(installer, roles, opts = {}) #:nodoc:
84 | @installer = installer
85 | process(installer.package.name, installer.install_sequence, roles, opts)
86 | rescue ::Capistrano::CommandError => e
87 | raise_error(e)
88 | ensure
89 | @installer = nil
90 | end
91 |
92 | def verify(verifier, roles, opts = {}) #:nodoc:
93 | process(verifier.package.name, verifier.commands, roles)
94 | rescue ::Capistrano::CommandError
95 | return false
96 | end
97 |
98 | def process(name, commands, roles, opts = {}) #:nodoc:
99 | inst=@installer
100 | @log_recorder = log_recorder = Sprinkle::Utility::LogRecorder.new
101 | commands = commands.map {|x| rewrite_command(x)}
102 | define_task(name, roles) do
103 | via = fetch(:run_method)
104 | commands.each do |command|
105 | if command.is_a? Commands::Transfer
106 | upload command.source, command.destination, :via => :scp,
107 | :recursive => command.recursive?
108 | elsif command.is_a? Commands::Reconnect
109 | teardown_connections_to(sessions.keys)
110 | else
111 | # this reset the log
112 | log_recorder.reset command
113 | invoke_command(command, {:via => via}) do |ch, stream, out|
114 | ::Capistrano::Configuration.default_io_proc.call(ch, stream, out) if Sprinkle::OPTIONS[:verbose]
115 | log_recorder.log(stream, out)
116 | end
117 | end
118 | end
119 | end
120 | run_task(name, opts)
121 | end
122 |
123 | private
124 |
125 | # rip out any double sudos from the beginning of the command
126 | def rewrite_command(cmd)
127 | return cmd if cmd.is_a?(Symbol)
128 | via = @config.fetch(:run_method)
129 | if via == :sudo and cmd =~ /^#{sudo_command}/
130 | cmd.gsub(/^#{sudo_command}\s?/,"")
131 | else
132 | cmd
133 | end
134 | end
135 |
136 | def raise_error(e)
137 | details={:command => @log_recorder.command, :code => "??",
138 | :message => e.message,
139 | :hosts => e.hosts,
140 | :error => @log_recorder.err, :stdout => @log_recorder.out}
141 | raise Sprinkle::Errors::RemoteCommandFailure.new(@installer, details, e)
142 | end
143 |
144 | def run_task(task, opts={})
145 | run(task)
146 | true
147 | end
148 |
149 | # REVISIT: can we set the description somehow?
150 | def define_task(name, roles, &block)
151 | @config.task task_sym(name), :roles => roles, &block
152 | end
153 |
154 | def run(task)
155 | @config.send task_sym(task)
156 | end
157 |
158 | def task_sym(name)
159 | "install_#{name.to_task_name}".to_sym
160 | end
161 | end
162 | end
163 | end
164 |
165 |
166 | =begin
167 |
168 | # channel: the SSH channel object used for this response
169 | # stream: either :err or :out, for stderr or stdout responses
170 | # output: the text that the server is sending, might be in chunks
171 | run "apt-get update" do |channel, stream, output|
172 | if output =~ /Are you sure?/
173 | answer = Capistrano::CLI.ui.ask("Are you sure: ")
174 | channel.send_data(answer + "\n")
175 | else
176 | # allow the default callback to be processed
177 | Capistrano::Configuration.default_io_proc.call[channel, stream, output]
178 | end
179 | end
180 |
181 |
182 |
183 | You can tell subversion to use a different username+password by
184 | setting a couple variables:
185 | set :svn_username, "my svn username"
186 | set :svn_password, "my svn password"
187 | If you don't want to set the password explicitly in your recipe like
188 | that, you can make capistrano prompt you for it like this:
189 | set(:svn_password) { Capistrano::CLI.password_prompt("Subversion
190 | password: ") }
191 | - Jamis
192 | =end
193 |
--------------------------------------------------------------------------------