class Omnibus::Project

Omnibus project DSL reader

@todo It seems like there’s a bit of a conflation between a

"project" and a "package" in this class... perhaps the
package-building portions should be extracted to a separate
class.

@todo: Reorder DSL methods to fit in the same YARD group @todo: Generate the DSL methods via metaprogramming… they’re all so similar

Constants

NULL_ARG

Attributes

library[R]

Public Class Methods

load(filename) click to toggle source

Convenience method to initialize a Project from a DSL file.

@param filename [String] the filename of the Project DSL file to load.

# File lib/omnibus/project.rb, line 43
def self.load(filename)
  new(IO.read(filename), filename)
end
new(io, filename) click to toggle source

Create a new Project from the contents of a DSL file. Prefer calling {Omnibus::Project#load} instead of using this method directly.

@param io [String] the contents of a Project DSL (not the filename!) @param filename [String] unused!

@see Omnibus::Project#load

@todo Remove filename parameter, as it is unused.

# File lib/omnibus/project.rb, line 57
def initialize(io, filename)
  @output_package = nil
  @name = nil
  @package_name = nil
  @install_path = nil
  @homepage = nil
  @description = nil
  @replaces = nil

  @exclusions = Array.new
  @conflicts = Array.new
  @dependencies = Array.new
  @runtime_dependencies = Array.new
  instance_eval(io)
  validate

  @library = Omnibus::Library.new(self)
  render_tasks
end

Public Instance Methods

build_iteration(val=NULL_ARG) click to toggle source

Set or retrieve the build iteration of the project. Defaults to ‘1` if not otherwise set.

@param val [Fixnum] @return [Fixnum]

@todo Is there a better name for this than “build_iteration”?

Would be nice to cut down confusiton with {#iteration}.
# File lib/omnibus/project.rb, line 238
def build_iteration(val=NULL_ARG)
  @build_iteration = val unless val.equal?(NULL_ARG)
  @build_iteration || 1
end
build_version(val=NULL_ARG) click to toggle source

Set or retrieve the version of the project.

@param val [String] the version to set @return [String]

@see Omnibus::BuildVersion

# File lib/omnibus/project.rb, line 225
def build_version(val=NULL_ARG)
  @build_version = val unless val.equal?(NULL_ARG)
  @build_version
end
config() click to toggle source

Convenience method for accessing the global Omnibus configuration object.

@return Omnibus::Config

@see Omnibus::Config

# File lib/omnibus/project.rb, line 331
def config
  Omnibus.config
end
conflict(val) click to toggle source

Add to the list of packages this one conflicts with.

Specifying conflicts is optional. See the ‘–conflicts` flag in {github.com/jordansissel/fpm fpm}.

@param val [String] @return [void]

# File lib/omnibus/project.rb, line 215
def conflict(val)
  @conflicts << val
end
dependencies(val=NULL_ARG) click to toggle source

Set or retrieve the list of software dependencies for this project. As this is a DSL method, only pass the names of software components, not {Omnibus::Software} objects.

These is the software that comprises your project, and is distinct from runtime dependencies.

@note This will reinitialize the internal depdencies Array

and overwrite any dependencies that may have been set using
{#dependency}.

@param val [Array<String>] a list of names of Software components @return [Array<String>]

# File lib/omnibus/project.rb, line 283
def dependencies(val=NULL_ARG)
  @dependencies = val unless val.equal?(NULL_ARG)
  @dependencies
end
dependency(val) click to toggle source

Add an Omnibus software dependency.

Note that this is a *build time* dependency. If you need to specify an external dependency that is required at runtime, see {#runtime_dependency} instead.

@param val [String] the name of a Software dependency @return [void]

# File lib/omnibus/project.rb, line 251
def dependency(val)
  @dependencies << val
end
dependency?(software) click to toggle source

Indicates whether ‘software` is defined as a software component of this project.

@param software [String, Omnibus::Software, name] @return [Boolean]

@see dependencies

# File lib/omnibus/project.rb, line 399
def dependency?(software)
  name = if software.respond_to?(:name)
           software.send(:name)
         else
           software
         end
  @dependencies.include?(name)
end
description(val=NULL_ARG) click to toggle source

Set or retrieve the project description. Defaults to ‘“The full stack of #{name}”`

Corresponds to the ‘–description` flag of {github.com/jordansissel/fpm fpm}.

@param val [String] the project description @return [String]

@see name

# File lib/omnibus/project.rb, line 188
def description(val=NULL_ARG)
  @description = val unless val.equal?(NULL_ARG)
  @description || "The full stack of #{name}"
end
exclude(pattern) click to toggle source

Add a new exclusion pattern.

Corresponds to the ‘–exclude` flag of {github.com/jordansissel/fpm fpm}.

@param pattern [String] @return void

# File lib/omnibus/project.rb, line 294
def exclude(pattern)
  @exclusions << pattern
end
homepage(val=NULL_ARG) click to toggle source

Set or retrive the package homepage.

@param val [String] @return [String]

@raise [MissingProjectConfiguration] if a value was not set

before being subsequently retrieved (i.e., a homepage must be
set in order to build a project)
# File lib/omnibus/project.rb, line 147
def homepage(val=NULL_ARG)
  @homepage = val unless val.equal?(NULL_ARG)
  @homepage || raise(MissingProjectConfiguration.new("homepage", "http://www.opscode.com"))
end
install_path(val=NULL_ARG) click to toggle source

Set or retrieve the path at which the project should be installed by the generated package.

@param val [String] @return [String]

@raise [MissingProjectConfiguration] if a value was not set

before being subsequently retrieved (i.e., an install_path
must be set in order to build a project)
# File lib/omnibus/project.rb, line 121
def install_path(val=NULL_ARG)
  @install_path = val unless val.equal?(NULL_ARG)
  @install_path || raise(MissingProjectConfiguration.new("install_path", "/opt/opscode"))
end
iteration() click to toggle source

Defines the iteration for the package to be generated. Adheres to the conventions of the platform for which the package is being built.

All iteration strings begin with the value set in {#build_iteration}

@return [String]

# File lib/omnibus/project.rb, line 159
def iteration
  case platform_family
  when 'rhel'
    platform_version =~ /^(\d+)/
    maj = $1
    "#{build_iteration}.el#{maj}"
  when 'freebsd'
    platform_version =~ /^(\d+)/
    maj = $1
    "#{build_iteration}.#{platform}.#{maj}.#{machine}"
  when 'windows'
    "#{build_iteration}.windows"
  when 'aix'
    "#{build_iteration}"
  else
    "#{build_iteration}.#{platform}.#{platform_version}"
  end
end
machine() click to toggle source
# File lib/omnibus/project.rb, line 322
def machine
  OHAI['kernel']['machine']
end
maintainer(val=NULL_ARG) click to toggle source

Set or retrieve the the package maintainer.

@param val [String] @return [String]

@raise [MissingProjectConfiguration] if a value was not set

before being subsequently retrieved (i.e., a maintainer must
be set in order to build a project)
# File lib/omnibus/project.rb, line 134
def maintainer(val=NULL_ARG)
  @maintainer = val unless val.equal?(NULL_ARG)
  @maintainer || raise(MissingProjectConfiguration.new("maintainer", "Opscode, Inc."))
end
name(val=NULL_ARG) click to toggle source

Set or retrieve the name of the project

@param val [String] the name to set @return [String]

@raise [MissingProjectConfiguration] if a value was not set

before being subsequently retrieved (i.e., a name
must be set in order to build a project)
# File lib/omnibus/project.rb, line 97
def name(val=NULL_ARG)
  @name = val unless val.equal?(NULL_ARG)
  @name || raise(MissingProjectConfiguration.new("name", "my_project"))
end
package_name(val=NULL_ARG) click to toggle source

Set or retrieve the package name of the project. Unless explicitly set, the package name defaults to the project name

@param val [String] the package name to set @return [String]

# File lib/omnibus/project.rb, line 107
def package_name(val=NULL_ARG)
  @package_name = val unless val.equal?(NULL_ARG)
  @package_name.nil? ? @name : @package_name
end
package_scripts_path() click to toggle source

The path to the package scripts directory for this project. These are optional scripts that can be bundled into the resulting package for running at various points in the package management lifecycle.

Currently supported scripts include:

  • postinst

    A post-install script

  • prerm

    A pre-uninstall script

  • postrm

    A post-uninstall script

Any scripts with these names that are present in the package scripts directory will be incorporated into the package that is built. This only applies to fpm-built packages.

Additionally, there may be a ‘makeselfinst` script.

@return [String]

@todo This documentation really should be up at a higher level,

particularly since the user has no way to change the path.
# File lib/omnibus/project.rb, line 362
def package_scripts_path
  "#{Omnibus.project_root}/package-scripts/#{name}"
end
package_types() click to toggle source

Determine the package type(s) to be built, based on the platform family for which the package is being built.

If specific types cannot be determined, default to ‘[“makeself”]`.

@return [Array<(String)>]

@todo Why does this only ever return a single-element array,

instead of just a string, or symbol?
# File lib/omnibus/project.rb, line 375
def package_types
  case platform_family
  when 'debian'
    [ "deb" ]
  when 'fedora', 'rhel'
    [ "rpm" ]
  when 'aix'
    [ "bff" ]
  when 'solaris2'
    [ "solaris" ]
  when 'windows'
    [ "msi" ]
  else
    [ "makeself" ]
  end
end
platform() click to toggle source

Returns the platform of the machine on which Omnibus is running, as determined by Ohai.

@return [String]

# File lib/omnibus/project.rb, line 310
def platform
  OHAI.platform
end
platform_family() click to toggle source

Returns the platform family of the machine on which Omnibus is running, as determined by Ohai.

@return [String]

# File lib/omnibus/project.rb, line 318
def platform_family
  OHAI.platform_family
end
platform_version() click to toggle source

Returns the platform version of the machine on which Omnibus is running, as determined by Ohai.

@return [String]

# File lib/omnibus/project.rb, line 302
def platform_version
  OHAI.platform_version
end
replaces(val=NULL_ARG) click to toggle source

Set or retrieve the name of the package this package will replace.

Ultimately used as the value for the ‘–replaces` flag in {github.com/jordansissel/fpm fpm}.

@param val [String] the name of the package to replace @return [String]

@todo Consider having this default to {#package_name}; many uses of this

method effectively do this already.
# File lib/omnibus/project.rb, line 203
def replaces(val=NULL_ARG)
  @replaces = val unless val.equal?(NULL_ARG)
  @replaces
end
runtime_dependency(val) click to toggle source

Add a package that is a runtime dependency of this project.

This is distinct from a build-time dependency, which should correspond to an Omnibus software definition.

Corresponds to the ‘–depends` flag of {github.com/jordansissel/fpm fpm}.

@param val [String] the name of the runtime dependency @return [void]

# File lib/omnibus/project.rb, line 266
def runtime_dependency(val)
  @runtime_dependencies << val
end
validate() click to toggle source

Ensures that certain project information has been set

@raise [MissingProjectConfiguration] if a required parameter has

not been set

@return [void]

# File lib/omnibus/project.rb, line 82
def validate
  name && install_path && maintainer && homepage
end

Private Instance Methods

bff_command() click to toggle source
# File lib/omnibus/project.rb, line 505
def bff_command
  bff_command = ["mkinstallp -d / -T /tmp/bff/gen.template"]
  [bff_command.join(" "), {:returns => [0]}]
end
bff_version() click to toggle source
# File lib/omnibus/project.rb, line 600
def bff_version
  build_version.split(/[^\d]/)[0..2].join(".") + ".#{iteration}"
end
fpm_command(pkg_type) click to toggle source

The {github.com/jordansissel/fpm fpm} command to generate a package for RedHat, Ubuntu, Solaris, etc. platforms.

Does not execute the command, only assembles it.

In contrast to {#msi_command}, command generated by {#fpm_command} does not require any Mixlib::Shellout options.

@return [Array<String>] the components of the fpm command; need

to be joined with " " first.

@todo Just make this return a String instead of an Array @todo Use the long option names (i.e., the double-dash ones) in

the fpm command for maximum clarity.
# File lib/omnibus/project.rb, line 524
def fpm_command(pkg_type)
  command_and_opts = ["fpm",
                      "-s dir",
                      "-t #{pkg_type}",
                      "-v #{build_version}",
                      "-n #{package_name}",
                      "-p #{output_package(pkg_type)}",
                      "--iteration #{iteration}",
                      "-m '#{maintainer}'",
                      "--description '#{description}'",
                      "--url #{homepage}"]
  if File.exist?("#{package_scripts_path}/postinst")
    command_and_opts << "--post-install '#{package_scripts_path}/postinst'"
  end
  # solaris packages don't support --pre-uninstall
  if File.exist?("#{package_scripts_path}/prerm") && pkg_type != "solaris"
    command_and_opts << "--pre-uninstall '#{package_scripts_path}/prerm'"
  end
  # solaris packages don't support --post-uninstall
  if File.exist?("#{package_scripts_path}/postrm") && pkg_type != "solaris"
    command_and_opts << "--post-uninstall '#{package_scripts_path}/postrm'"
  end

  @exclusions.each do |pattern|
    command_and_opts << "--exclude '#{pattern}'"
  end

  @runtime_dependencies.each do |runtime_dep|
    command_and_opts << "--depends '#{runtime_dep}'"
  end

  @conflicts.each do |conflict|
    command_and_opts << "--conflicts '#{conflict}'"
  end

  command_and_opts << " --replaces #{@replaces}" if @replaces
  command_and_opts << install_path
  command_and_opts
end
makeself_command() click to toggle source

TODO: what’s this do?

# File lib/omnibus/project.rb, line 565
def makeself_command
  command_and_opts = [ File.expand_path(File.join(Omnibus.source_root, "bin", "makeself.sh")),
                       "--gzip",
                       install_path,
                       output_package("makeself"),
                       "'The full stack of #{@name}'"
                     ]
  command_and_opts << "./makeselfinst" if File.exists?("#{package_scripts_path}/makeselfinst")
  command_and_opts
end
msi_command() click to toggle source

The command to generate an MSI package on Windows platforms.

Does not execute the command, only assembles it.

@return [Array<(String, Hash)>] The complete MSI command, plus a

Hash of options to be passed on to Mixlib::ShellOut

@see Mixlib::ShellOut

@todo For this and all the *_command methods, just return a

Mixlib::ShellOut object ready for execution.  Using Arrays
makes downstream processing needlessly complicated.
# File lib/omnibus/project.rb, line 490
def msi_command
  msi_command = ["light.exe",
                 "-nologo",
                 "-ext WixUIExtension",
                 "-cultures:en-us",
                 "-loc #{install_path}\\msi-tmp\\#{package_name}-en-us.wxl",
                 "#{install_path}\\msi-tmp\\#{package_name}-Files.wixobj",
                 "#{install_path}\\msi-tmp\\#{package_name}.wixobj",
                 "-out #{config.package_dir}\\#{output_package("msi")}"]

  # Don't care about the 204 return code from light.exe since it's
  # about some expected warnings...
  [msi_command.join(" "), {:returns => [0, 204]}]
end
output_package(pkg_type) click to toggle source

The basename of the resulting package file. @return [String] the basename of the package file

# File lib/omnibus/project.rb, line 456
def output_package(pkg_type)
  case pkg_type
  when "makeself"
    "#{package_name}-#{build_version}_#{iteration}.sh"
  when "msi"
    "#{package_name}-#{build_version}-#{iteration}.msi"
  when "bff"
    "#{package_name}.#{bff_version}.bff"
  else # fpm
    require "fpm/package/#{pkg_type}"
    pkg = FPM::Package.types[pkg_type].new
    pkg.version = build_version
    pkg.name = package_name
    pkg.iteration = iteration
    if pkg_type == "solaris"
      pkg.to_s("NAME.FULLVERSION.ARCH.TYPE")
    else
      pkg.to_s
    end
  end
end
platform_shortname() click to toggle source

Platform name to be used when creating metadata for the artifact. rhel/centos become “el”, all others are just platform @return [String] the platform family short name

# File lib/omnibus/project.rb, line 436
def platform_shortname
  if platform_family == "rhel"
    "el"
  else
    platform
  end
end
platform_tuple() click to toggle source

An Array of platform data suitable for ‘Artifact.new`. This will go into metadata generated for the artifact, and be used for the file hierarchy of released packages if the default release scripts are used. @return [Array<String>] platform_shortname, platform_version_for_package,

machine architecture.
# File lib/omnibus/project.rb, line 417
def platform_tuple
  [platform_shortname, platform_version_for_package, machine]
end
platform_version_for_package() click to toggle source

Platform version to be used in package metadata. For rhel, the minor version is removed, e.g., “5.6” becomes “5”. For all other platforms, this is just the platform_version. @return [String] the platform version

# File lib/omnibus/project.rb, line 425
def platform_version_for_package
  if platform == "rhel"
    platform_version[/([\d]+)\..+/, 1]
  else
    platform_version
  end
end
render_metadata(pkg_type) click to toggle source
# File lib/omnibus/project.rb, line 444
def render_metadata(pkg_type)
  basename = output_package(pkg_type)
  pkg_path = "#{config.package_dir}/#{basename}"
  artifact = Artifact.new(pkg_path, [ platform_tuple ], :version => build_version)
  metadata = artifact.flat_metadata
  File.open("#{pkg_path}.metadata.json", "w+") do |f|
    f.print(JSON.pretty_generate(metadata))
  end
end
render_tasks() click to toggle source

Dynamically generate Rake tasks to build projects and all the software they depend on.

@note Much Rake magic ahead!

@return void

# File lib/omnibus/project.rb, line 655
def render_tasks
  directory config.package_dir
  directory "pkg"

  namespace :projects do
    namespace @name do

      package_types.each do |pkg_type|
        dep_tasks = @dependencies.map {|dep| "software:#{dep}"}
        dep_tasks << config.package_dir
        dep_tasks << "health_check"

        desc "package #{@name} into a #{pkg_type}"
        task pkg_type => dep_tasks do
          if pkg_type == "makeself"
            run_makeself
          elsif pkg_type == "msi"
            run_msi
          elsif pkg_type == "bff"
            run_bff
          else # pkg_type == "fpm"
            run_fpm(pkg_type)
          end

          render_metadata(pkg_type)

        end
      end

      task "copy" => package_types do
        if OHAI.platform == "windows"
          cp_cmd = "xcopy #{config.package_dir}\\*.msi pkg\\ /Y"
        elsif OHAI.platform == "aix"
          cp_cmd = "cp #{config.package_dir}/*.bff pkg/"
        else
          cp_cmd = "cp #{config.package_dir}/* pkg/"
        end
        shell = Mixlib::ShellOut.new(cp_cmd)
        shell.run_command
        shell.error!
      end
      task "copy" => "pkg"

      desc "run the health check on the #{@name} install path"
      task "health_check" do
        if OHAI.platform == "windows"
          puts "Skipping health check on windows..."
        else
          # build a list of all whitelist files from all project dependencies
          whitelist_files = library.components.map{|component| component.whitelist_files }.flatten
          Omnibus::HealthCheck.run(install_path, whitelist_files)
        end
      end
    end

    desc "package #{@name}"
    task @name => "#{@name}:copy"
  end
end
run_bff() click to toggle source
# File lib/omnibus/project.rb, line 604
def run_bff
  FileUtils.rm_rf "/.info"
  FileUtils.rm_rf "/tmp/bff"
  FileUtils.mkdir "/tmp/bff"

  system "find #{install_path} -print > /tmp/bff/file.list"

  system "cat #{package_scripts_path}/aix/opscode.chef.client.template | sed -e 's/TBS/#{bff_version}/' > /tmp/bff/gen.preamble"

  # @todo can we just use an erb template here?
  system "cat /tmp/bff/gen.preamble /tmp/bff/file.list #{package_scripts_path}/aix/opscode.chef.client.template.last > /tmp/bff/gen.template"

  FileUtils.cp "#{package_scripts_path}/aix/unpostinstall.sh", "#{install_path}/bin"
  FileUtils.cp "#{package_scripts_path}/aix/postinstall.sh", "#{install_path}/bin"

  run_package_command(bff_command)

  FileUtils.cp "/tmp/chef.#{bff_version}.bff", "/var/cache/omnibus/pkg/chef.#{bff_version}.bff"
end
run_fpm(pkg_type) click to toggle source

Runs the necessary command to make a package with fpm. As a side-effect, sets ‘output_package` @return void

# File lib/omnibus/project.rb, line 627
def run_fpm(pkg_type)
  run_package_command(fpm_command(pkg_type).join(" "))
end
run_makeself() click to toggle source

Runs the makeself commands to make a self extracting archive package. As a (necessary) side-effect, sets @return void

# File lib/omnibus/project.rb, line 579
def run_makeself
  package_commands = []
  # copy the makeself installer into package
  if File.exists?("#{package_scripts_path}/makeselfinst")
    package_commands << "cp #{package_scripts_path}/makeselfinst #{install_path}/"
  end

  # run the makeself program
  package_commands << makeself_command.join(" ")

  # rm the makeself installer (for incremental builds)
  package_commands << "rm -f #{install_path}/makeselfinst"
  package_commands.each {|cmd| run_package_command(cmd) }
end
run_msi() click to toggle source

Runs the necessary command to make an MSI. As a side-effect, sets ‘output_package` @return void

# File lib/omnibus/project.rb, line 596
def run_msi
  run_package_command(msi_command)
end
run_package_command(cmd) click to toggle source

Executes the given command via mixlib-shellout. @return [Mixlib::ShellOut] returns the underlying Mixlib::ShellOut

object, so the caller can inspect the stdout and stderr.
# File lib/omnibus/project.rb, line 634
def run_package_command(cmd)
  cmd_options = {
    :timeout => 3600,
    :cwd => config.package_dir
  }

  if cmd.is_a?(Array)
    command = cmd[0]
    cmd_options.merge!(cmd[1])
  else
    command = cmd
  end

  shellout!(command, cmd_options)
end