module Sprinkle # = Packages # # A package defines one or more things to provision onto the server. # There is a lot of flexibility in a way a package is defined but # let me give you a basic example: # # package :ruby do # description 'Ruby MRI' # version '1.8.6' # apt 'ruby' # # verify { has_executable 'ruby' } # end # # The above would define a package named 'ruby' and give it a description # and explicitly say its version. It is installed via apt and to verify # the installation was successful sprinkle will check for the executable # 'ruby' being availble. Pretty simple, right? # # Note: Defining a package does not INSTALL it. To install a # package, you must require it in a Sprinkle::Policy block. # # == Pre-Requirements # # Most packages have some sort of pre-requisites in order to be installed. # Sprinkle allows you to define the requirements of the package, which # will be installed before the package itself. An example below: # # package :rubygems do # source 'http://rubyforge.org/rubygems.tgz' # requires :ruby # end # # In this case, when rubygems is being installed, Sprinkle will first # provision the server with Ruby to make sure the requirements are met. # In turn, if ruby has requirements, it installs those first, and so on. # # == Verifications # # Most of the time its important to know whether the software you're # attempting to install was installed successfully or not. For this, # Sprinkle provides verifications. Verifications are one or more blocks # which define rules with which Sprinkle can check if it installed # the package successfully. If these verification blocks fail, then # Sprinkle will gracefully stop the entire process. An example below: # # package :rubygems do # source 'http://rubyforge.org/rubygems.tgz' # requires :ruby # # verify { has_executable 'gem' } # end # # In addition to verifying an installation was successfully, by default # Sprinkle runs these verifications before the installation to # check if the package is already installed. If the verifications pass # before installing the package, it skips the package. To override this # behavior, set the -f flag on the sprinkle script or set the # :force option to true in Sprinkle::OPTIONS # # For more information on verifications and to see all the available # verifications, see Sprinkle::Verify # # == Virtual Packages # # Sometimes, there are multiple packages available for a single task. An # example is a database package. It can contain mySQL, postgres, or sqlite! # This is where virtual packages come in handy. They are defined as follows: # # package :sqlite3, :provides => :database do # apt 'sqlite3' # end # # The :provides option allows you to reference this package either by :sqlite3 # or by :database. But whereas the package name is unique, multiple packages may # share the same provision. If this is the case, when running Sprinkle, the # script will ask you which provision you want to install. At this time, you # can only install one. # # == Meta-Packages # # A package doesn't require an installer. If you want to define a package which # merely encompasses other packages, that is fine too. Example: # # package :meta do # requires :magic_beans # requires :magic_sauce # end # #-- # FIXME: Should probably document recommendations. #++ module Package PACKAGES = {} def package(name, metadata = {}, &block) package = Package.new(name, metadata, &block) PACKAGES[name] = package if package.provides (PACKAGES[package.provides] ||= []) << package end package end class Package #:nodoc: include ArbitraryOptions attr_accessor :name, :provides, :installers, :dependencies, :recommends, :verifications def initialize(name, metadata = {}, &block) raise 'No package name supplied' unless name @name = name @provides = metadata[:provides] @dependencies = [] @recommends = [] @optional = [] @verifications = [] @installers = [] self.instance_eval &block end def add_user(username, options={}, &block) @installers << Sprinkle::Installers::User.new(self, username, options, &block) end def add_group(group, options={}, &block) @installers << Sprinkle::Installers::Group.new(self, group, options, &block) end def freebsd_pkg(*names, &block) @installers << Sprinkle::Installers::FreebsdPkg.new(self, *names, &block) end def freebsd_portinstall(port, &block) @installers << Sprinkle::Installers::FreebsdPortinstall.new(self, port, &block) end def openbsd_pkg(*names, &block) @installers << Sprinkle::Installers::OpenbsdPkg.new(self, *names, &block) end def opensolaris_pkg(*names, &block) @installers << Sprinkle::Installers::OpensolarisPkg.new(self, *names, &block) end def bsd_port(port, &block) @installers << Sprinkle::Installers::BsdPort.new(self, port, &block) end def mac_port(port, &block) @installers << Sprinkle::Installers::MacPort.new(self, port, &block) end def apt(*names, &block) @installers << Sprinkle::Installers::Apt.new(self, *names, &block) end def deb(*names, &block) @installers << Sprinkle::Installers::Deb.new(self, *names, &block) end def rpm(*names, &block) @installers << Sprinkle::Installers::Rpm.new(self, *names, &block) end def yum(*names, &block) @installers << Sprinkle::Installers::Yum.new(self, *names, &block) end def zypper(*names, &block) @installers << Sprinkle::Installers::Zypper.new(self, *names, &block) end def gem(name, options = {}, &block) @recommends << :rubygems @installers << Sprinkle::Installers::Gem.new(self, name, options, &block) end def source(source, options = {}, &block) @recommends << :build_essential # Ubuntu/Debian @installers << Sprinkle::Installers::Source.new(self, source, options, &block) end def binary(source, options = {}, &block) @installers << Sprinkle::Installers::Binary.new(self, source, options, &block) end def rake(name, options = {}, &block) @installers << Sprinkle::Installers::Rake.new(self, name, options, &block) end def thor(name, options = {}, &block) @installers << Sprinkle::Installers::Thor.new(self, name, options, &block) end def noop(&block) @installers << Sprinkle::Installers::Runner.new(self, "echo noop", &block) end def push_text(text, path, options = {}, &block) @installers << Sprinkle::Installers::PushText.new(self, text, path, options, &block) end def replace_text(regex, text, path, options={}, &block) @installers << Sprinkle::Installers::ReplaceText.new(self, regex, text, path, options, &block) end def transfer(source, destination, options = {}, &block) @installers << Sprinkle::Installers::Transfer.new(self, source, destination, options, &block) end def runner(cmd, &block) @installers << Sprinkle::Installers::Runner.new(self, cmd, &block) end def verify(description = '', &block) @verifications << Sprinkle::Verify.new(self, description, &block) end def pacman(*names, &block) @installers << Sprinkle::Installers::Pacman.new(self, *names, &block) end def process(deployment, roles) return if meta_package? # Run a pre-test to see if the software is already installed. If so, # we can skip it, unless we have the force option turned on! unless @verifications.empty? || Sprinkle::OPTIONS[:force] begin process_verifications(deployment, roles, true) logger.info "--> #{self.name} already installed for roles: #{roles}" return rescue Sprinkle::VerificationFailed => e # Continue end end @installers.each do |installer| installer.defaults(deployment) installer.process(roles) end process_verifications(deployment, roles) end def process_verifications(deployment, roles, pre = false) return if @verifications.blank? if pre logger.info "--> Checking if #{self.name} is already installed for roles: #{roles}" else logger.info "--> Verifying #{self.name} was properly installed for roles: #{roles}" end @verifications.each do |v| v.defaults(deployment) v.process(roles) end end def requires(*packages) @dependencies << packages @dependencies.flatten! end def recommends(*packages) @recommends << packages @recommends.flatten! end def optional(*packages) @optional << packages @optional.flatten! end def tree(depth = 1, &block) packages = [] @recommends.each do |dep| package = PACKAGES[dep] next unless package # skip missing recommended packages as they're allowed to not exist block.call(self, package, depth) if block packages << package.tree(depth + 1, &block) end @dependencies.each do |dep| package = PACKAGES[dep] package = select_package(dep, package) if package.is_a? Array raise "Package definition not found for key: #{dep}" unless package block.call(self, package, depth) if block packages << package.tree(depth + 1, &block) end packages << self @optional.each do |dep| package = PACKAGES[dep] next unless package # skip missing optional packages as they're allow to not exist block.call(self, package, depth) if block packages << package.tree(depth + 1, &block) end packages end def to_s; @name; end private def select_package(name, packages) if packages.size <= 1 package = packages.first else package = choose do |menu| menu.prompt = "Multiple choices exist for virtual package #{name}" menu.choices *packages.collect(&:to_s) end package = Sprinkle::Package::PACKAGES[package] end cloud_info "Selecting #{package.to_s} for virtual package #{name}" package end def meta_package? @installers.blank? end end end end