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
Public Class Methods
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
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
Set or retrieve the version of the project.
@param val [String] the version to set @return [String]
# File lib/omnibus/project.rb, line 225 def build_version(val=NULL_ARG) @build_version = val unless val.equal?(NULL_ARG) @build_version end
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
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
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
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
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
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
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
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
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
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
# File lib/omnibus/project.rb, line 322 def machine OHAI['kernel']['machine'] end
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
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
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
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
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
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
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
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
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
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
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
# 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
# File lib/omnibus/project.rb, line 600 def bff_version build_version.split(/[^\d]/)[0..2].join(".") + ".#{iteration}" end
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
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
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
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 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
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 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
# 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
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
# 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
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
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
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
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