omnibus-sonian 1.2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.travis.yml +5 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +96 -0
- data/Gemfile +9 -0
- data/LICENSE +201 -0
- data/NOTICE +9 -0
- data/README.md +195 -0
- data/Rakefile +7 -0
- data/bin/makeself-header.sh +401 -0
- data/bin/makeself.sh +407 -0
- data/bin/omnibus +11 -0
- data/lib/omnibus.rb +304 -0
- data/lib/omnibus/artifact.rb +151 -0
- data/lib/omnibus/build_version.rb +285 -0
- data/lib/omnibus/builder.rb +328 -0
- data/lib/omnibus/clean_tasks.rb +30 -0
- data/lib/omnibus/cli.rb +35 -0
- data/lib/omnibus/cli/application.rb +140 -0
- data/lib/omnibus/cli/base.rb +118 -0
- data/lib/omnibus/cli/build.rb +62 -0
- data/lib/omnibus/cli/cache.rb +60 -0
- data/lib/omnibus/cli/release.rb +49 -0
- data/lib/omnibus/config.rb +224 -0
- data/lib/omnibus/exceptions.rb +143 -0
- data/lib/omnibus/fetcher.rb +184 -0
- data/lib/omnibus/fetchers.rb +22 -0
- data/lib/omnibus/fetchers/git_fetcher.rb +212 -0
- data/lib/omnibus/fetchers/net_fetcher.rb +193 -0
- data/lib/omnibus/fetchers/path_fetcher.rb +65 -0
- data/lib/omnibus/fetchers/s3_cache_fetcher.rb +42 -0
- data/lib/omnibus/health_check.rb +356 -0
- data/lib/omnibus/library.rb +62 -0
- data/lib/omnibus/overrides.rb +69 -0
- data/lib/omnibus/package_release.rb +163 -0
- data/lib/omnibus/project.rb +715 -0
- data/lib/omnibus/reports.rb +99 -0
- data/lib/omnibus/s3_cacher.rb +138 -0
- data/lib/omnibus/software.rb +441 -0
- data/lib/omnibus/templates/Berksfile.erb +3 -0
- data/lib/omnibus/templates/Gemfile.erb +4 -0
- data/lib/omnibus/templates/README.md.erb +102 -0
- data/lib/omnibus/templates/Vagrantfile.erb +95 -0
- data/lib/omnibus/templates/gitignore.erb +8 -0
- data/lib/omnibus/templates/omnibus.rb.example.erb +5 -0
- data/lib/omnibus/templates/package_scripts/makeselfinst.erb +27 -0
- data/lib/omnibus/templates/package_scripts/postinst.erb +17 -0
- data/lib/omnibus/templates/package_scripts/postrm.erb +9 -0
- data/lib/omnibus/templates/project.rb.erb +21 -0
- data/lib/omnibus/templates/software/c-example.rb.erb +42 -0
- data/lib/omnibus/templates/software/erlang-example.rb.erb +38 -0
- data/lib/omnibus/templates/software/ruby-example.rb.erb +24 -0
- data/lib/omnibus/util.rb +61 -0
- data/lib/omnibus/version.rb +20 -0
- data/omnibus.gemspec +34 -0
- data/spec/artifact_spec.rb +106 -0
- data/spec/build_version_spec.rb +266 -0
- data/spec/data/overrides/bad_line.overrides +3 -0
- data/spec/data/overrides/good.overrides +5 -0
- data/spec/data/overrides/with_dupes.overrides +4 -0
- data/spec/data/software/erchef.rb +40 -0
- data/spec/fetchers/net_fetcher_spec.rb +16 -0
- data/spec/overrides_spec.rb +114 -0
- data/spec/package_release_spec.rb +197 -0
- data/spec/s3_cacher_spec.rb +47 -0
- data/spec/software_spec.rb +85 -0
- data/spec/spec_helper.rb +28 -0
- metadata +252 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
module Omnibus
|
4
|
+
module Overrides
|
5
|
+
|
6
|
+
DEFAULT_OVERRIDE_FILE_NAME = "omnibus.overrides"
|
7
|
+
|
8
|
+
# Parses a file of override information into a Hash.
|
9
|
+
#
|
10
|
+
# Each line of the file must be of the form
|
11
|
+
#
|
12
|
+
#
|
13
|
+
# <package_name> <version>
|
14
|
+
#
|
15
|
+
# where the two pieces of data are separated by whitespace.
|
16
|
+
#
|
17
|
+
# @param file [String] the path to an overrides file
|
18
|
+
# @return [Hash, nil]
|
19
|
+
def self.parse_file(file)
|
20
|
+
if file
|
21
|
+
File.readlines(file).inject({}) do |acc, line|
|
22
|
+
info = line.split
|
23
|
+
|
24
|
+
unless info.count == 2
|
25
|
+
raise ArgumentError, "Invalid overrides line: '#{line.chomp}'"
|
26
|
+
end
|
27
|
+
|
28
|
+
package, version = info
|
29
|
+
|
30
|
+
if acc[package]
|
31
|
+
raise ArgumentError, "Multiple overrides present for '#{package}' in overrides file #{file}!"
|
32
|
+
end
|
33
|
+
|
34
|
+
acc[package] = version
|
35
|
+
acc
|
36
|
+
end
|
37
|
+
else
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return the full path to an overrides file, or +nil+ if no such
|
43
|
+
# file exists.
|
44
|
+
def self.resolve_override_file
|
45
|
+
file = ENV['OMNIBUS_OVERRIDE_FILE'] || DEFAULT_OVERRIDE_FILE_NAME
|
46
|
+
path = File.expand_path(file)
|
47
|
+
File.exist?(path) ? path : nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return a hash of override information. If no such information
|
51
|
+
# can be found, the hash will be empty
|
52
|
+
#
|
53
|
+
# @return [Hash]
|
54
|
+
def self.overrides
|
55
|
+
file = resolve_override_file
|
56
|
+
overrides = parse_file(file)
|
57
|
+
|
58
|
+
if overrides
|
59
|
+
puts "********************************************************************************"
|
60
|
+
puts "Using Overrides from #{Omnibus::Overrides.resolve_override_file}"
|
61
|
+
pp overrides
|
62
|
+
puts "********************************************************************************"
|
63
|
+
end
|
64
|
+
|
65
|
+
overrides || {}
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2012 Opscode, Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
# internal
|
19
|
+
require 'omnibus/exceptions'
|
20
|
+
|
21
|
+
# stdlib
|
22
|
+
require 'json'
|
23
|
+
|
24
|
+
# external
|
25
|
+
require 'uber-s3'
|
26
|
+
|
27
|
+
module Omnibus
|
28
|
+
class PackageRelease
|
29
|
+
|
30
|
+
attr_reader :package_path
|
31
|
+
attr_reader :access_policy
|
32
|
+
|
33
|
+
# @param package_path [String] file system path to the package artifact
|
34
|
+
# @option opts [:private, :public_read] :access specifies access control on
|
35
|
+
# uploaded files
|
36
|
+
# @yield callback triggered by successful upload. Allows users of this
|
37
|
+
# class to add UI feedback.
|
38
|
+
# @yieldparam s3_object_key [String] the S3 key of the uploaded object.
|
39
|
+
def initialize(package_path, opts={:access=>:private}, &block)
|
40
|
+
@package_path = package_path
|
41
|
+
@metadata = nil
|
42
|
+
@s3_client = nil
|
43
|
+
|
44
|
+
@after_upload = if block_given?
|
45
|
+
block
|
46
|
+
else
|
47
|
+
lambda { |item_key| nil }
|
48
|
+
end
|
49
|
+
|
50
|
+
# sets @access_policy
|
51
|
+
handle_opts(opts)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Primary API for this class. Validates S3 configuration and package files,
|
55
|
+
# then runs the upload.
|
56
|
+
# @return [void]
|
57
|
+
# @raise [NoPackageFile, NoPackageMetadataFile] when the package or
|
58
|
+
# associated metadata file do not exist.
|
59
|
+
# @raise [InvalidS3ReleaseConfiguration] when the Omnibus configuration is
|
60
|
+
# missing required settings.
|
61
|
+
# @raise Also may raise errors from uber-s3 or net/http.
|
62
|
+
def release
|
63
|
+
validate_config!
|
64
|
+
validate_package!
|
65
|
+
s3_client.store(metadata_key, metadata_json, :access => access_policy)
|
66
|
+
uploaded(metadata_key)
|
67
|
+
s3_client.store(package_key, package_content, :access => access_policy, :content_md5 => md5)
|
68
|
+
uploaded(package_key)
|
69
|
+
end
|
70
|
+
|
71
|
+
def uploaded(key)
|
72
|
+
@after_upload.call(key)
|
73
|
+
end
|
74
|
+
|
75
|
+
def package_key
|
76
|
+
File.join(platform_path, File.basename(package_path))
|
77
|
+
end
|
78
|
+
|
79
|
+
def metadata_key
|
80
|
+
File.join(platform_path, File.basename(package_metadata_path))
|
81
|
+
end
|
82
|
+
|
83
|
+
def platform_path
|
84
|
+
File.join(metadata["platform"], metadata["platform_version"], metadata["arch"])
|
85
|
+
end
|
86
|
+
|
87
|
+
def md5
|
88
|
+
metadata["md5"]
|
89
|
+
end
|
90
|
+
|
91
|
+
def metadata
|
92
|
+
@metadata ||= JSON.parse(metadata_json)
|
93
|
+
end
|
94
|
+
|
95
|
+
def metadata_json
|
96
|
+
IO.read(package_metadata_path)
|
97
|
+
end
|
98
|
+
|
99
|
+
def package_content
|
100
|
+
IO.read(package_path)
|
101
|
+
end
|
102
|
+
|
103
|
+
def package_metadata_path
|
104
|
+
"#{package_path}.metadata.json"
|
105
|
+
end
|
106
|
+
|
107
|
+
def validate_package!
|
108
|
+
if !File.exist?(package_path)
|
109
|
+
raise NoPackageFile.new(package_path)
|
110
|
+
elsif !File.exist?(package_metadata_path)
|
111
|
+
raise NoPackageMetadataFile.new(package_metadata_path)
|
112
|
+
else
|
113
|
+
true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def validate_config!
|
118
|
+
if s3_access_key && s3_secret_key && s3_bucket
|
119
|
+
true
|
120
|
+
else
|
121
|
+
err = InvalidS3ReleaseConfiguration.new(s3_bucket, s3_access_key, s3_secret_key)
|
122
|
+
raise err
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def s3_client
|
127
|
+
@s3_client ||= UberS3.new(
|
128
|
+
:access_key => s3_access_key,
|
129
|
+
:secret_access_key => s3_secret_key,
|
130
|
+
:bucket => s3_bucket,
|
131
|
+
:adaper => :net_http
|
132
|
+
)
|
133
|
+
end
|
134
|
+
|
135
|
+
def s3_access_key
|
136
|
+
config[:release_s3_access_key]
|
137
|
+
end
|
138
|
+
|
139
|
+
def s3_secret_key
|
140
|
+
config[:release_s3_secret_key]
|
141
|
+
end
|
142
|
+
|
143
|
+
def s3_bucket
|
144
|
+
config[:release_s3_bucket]
|
145
|
+
end
|
146
|
+
|
147
|
+
def config
|
148
|
+
Omnibus.config
|
149
|
+
end
|
150
|
+
|
151
|
+
def handle_opts(opts)
|
152
|
+
access_policy = opts[:access]
|
153
|
+
if access_policy.nil?
|
154
|
+
raise ArgumentError, "options to #{self.class} must specify `:access' (given: #{opts.inspect})"
|
155
|
+
elsif not [:private, :public_read].include?(access_policy)
|
156
|
+
raise ArgumentError, "option `:access' must be one of `[:private, :public_read]' (given: #{access_policy.inspect})"
|
157
|
+
else
|
158
|
+
@access_policy = access_policy
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,715 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2012 Opscode, Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
require 'omnibus/artifact'
|
18
|
+
require 'omnibus/exceptions'
|
19
|
+
require 'omnibus/library'
|
20
|
+
require 'omnibus/util'
|
21
|
+
|
22
|
+
module Omnibus
|
23
|
+
|
24
|
+
# Omnibus project DSL reader
|
25
|
+
#
|
26
|
+
# @todo It seems like there's a bit of a conflation between a
|
27
|
+
# "project" and a "package" in this class... perhaps the
|
28
|
+
# package-building portions should be extracted to a separate
|
29
|
+
# class.
|
30
|
+
# @todo: Reorder DSL methods to fit in the same YARD group
|
31
|
+
# @todo: Generate the DSL methods via metaprogramming... they're all so similar
|
32
|
+
class Project
|
33
|
+
include Rake::DSL
|
34
|
+
include Util
|
35
|
+
|
36
|
+
NULL_ARG = Object.new
|
37
|
+
|
38
|
+
attr_reader :library
|
39
|
+
|
40
|
+
# Convenience method to initialize a Project from a DSL file.
|
41
|
+
#
|
42
|
+
# @param filename [String] the filename of the Project DSL file to load.
|
43
|
+
def self.load(filename)
|
44
|
+
new(IO.read(filename), filename)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Create a new Project from the contents of a DSL file. Prefer
|
48
|
+
# calling {Omnibus::Project#load} instead of using this method
|
49
|
+
# directly.
|
50
|
+
#
|
51
|
+
# @param io [String] the contents of a Project DSL (_not_ the filename!)
|
52
|
+
# @param filename [String] unused!
|
53
|
+
#
|
54
|
+
# @see Omnibus::Project#load
|
55
|
+
#
|
56
|
+
# @todo Remove filename parameter, as it is unused.
|
57
|
+
def initialize(io, filename)
|
58
|
+
@output_package = nil
|
59
|
+
@name = nil
|
60
|
+
@package_name = nil
|
61
|
+
@install_path = nil
|
62
|
+
@homepage = nil
|
63
|
+
@description = nil
|
64
|
+
@replaces = nil
|
65
|
+
|
66
|
+
@exclusions = Array.new
|
67
|
+
@conflicts = Array.new
|
68
|
+
@dependencies = Array.new
|
69
|
+
@runtime_dependencies = Array.new
|
70
|
+
instance_eval(io)
|
71
|
+
validate
|
72
|
+
|
73
|
+
@library = Omnibus::Library.new(self)
|
74
|
+
render_tasks
|
75
|
+
end
|
76
|
+
|
77
|
+
# Ensures that certain project information has been set
|
78
|
+
#
|
79
|
+
# @raise [MissingProjectConfiguration] if a required parameter has
|
80
|
+
# not been set
|
81
|
+
# @return [void]
|
82
|
+
def validate
|
83
|
+
name && install_path && maintainer && homepage
|
84
|
+
end
|
85
|
+
|
86
|
+
# @!group DSL methods
|
87
|
+
# Here is some broad documentation for the DSL methods as a whole.
|
88
|
+
|
89
|
+
# Set or retrieve the name of the project
|
90
|
+
#
|
91
|
+
# @param val [String] the name to set
|
92
|
+
# @return [String]
|
93
|
+
#
|
94
|
+
# @raise [MissingProjectConfiguration] if a value was not set
|
95
|
+
# before being subsequently retrieved (i.e., a name
|
96
|
+
# must be set in order to build a project)
|
97
|
+
def name(val=NULL_ARG)
|
98
|
+
@name = val unless val.equal?(NULL_ARG)
|
99
|
+
@name || raise(MissingProjectConfiguration.new("name", "my_project"))
|
100
|
+
end
|
101
|
+
|
102
|
+
# Set or retrieve the package name of the project. Unless
|
103
|
+
# explicitly set, the package name defaults to the project name
|
104
|
+
#
|
105
|
+
# @param val [String] the package name to set
|
106
|
+
# @return [String]
|
107
|
+
def package_name(val=NULL_ARG)
|
108
|
+
@package_name = val unless val.equal?(NULL_ARG)
|
109
|
+
@package_name.nil? ? @name : @package_name
|
110
|
+
end
|
111
|
+
|
112
|
+
# Set or retrieve the path at which the project should be
|
113
|
+
# installed by the generated package.
|
114
|
+
#
|
115
|
+
# @param val [String]
|
116
|
+
# @return [String]
|
117
|
+
#
|
118
|
+
# @raise [MissingProjectConfiguration] if a value was not set
|
119
|
+
# before being subsequently retrieved (i.e., an install_path
|
120
|
+
# must be set in order to build a project)
|
121
|
+
def install_path(val=NULL_ARG)
|
122
|
+
@install_path = val unless val.equal?(NULL_ARG)
|
123
|
+
@install_path || raise(MissingProjectConfiguration.new("install_path", "/opt/opscode"))
|
124
|
+
end
|
125
|
+
|
126
|
+
# Set or retrieve the the package maintainer.
|
127
|
+
#
|
128
|
+
# @param val [String]
|
129
|
+
# @return [String]
|
130
|
+
#
|
131
|
+
# @raise [MissingProjectConfiguration] if a value was not set
|
132
|
+
# before being subsequently retrieved (i.e., a maintainer must
|
133
|
+
# be set in order to build a project)
|
134
|
+
def maintainer(val=NULL_ARG)
|
135
|
+
@maintainer = val unless val.equal?(NULL_ARG)
|
136
|
+
@maintainer || raise(MissingProjectConfiguration.new("maintainer", "Opscode, Inc."))
|
137
|
+
end
|
138
|
+
|
139
|
+
# Set or retrive the package homepage.
|
140
|
+
#
|
141
|
+
# @param val [String]
|
142
|
+
# @return [String]
|
143
|
+
#
|
144
|
+
# @raise [MissingProjectConfiguration] if a value was not set
|
145
|
+
# before being subsequently retrieved (i.e., a homepage must be
|
146
|
+
# set in order to build a project)
|
147
|
+
def homepage(val=NULL_ARG)
|
148
|
+
@homepage = val unless val.equal?(NULL_ARG)
|
149
|
+
@homepage || raise(MissingProjectConfiguration.new("homepage", "http://www.opscode.com"))
|
150
|
+
end
|
151
|
+
|
152
|
+
# Defines the iteration for the package to be generated. Adheres
|
153
|
+
# to the conventions of the platform for which the package is
|
154
|
+
# being built.
|
155
|
+
#
|
156
|
+
# All iteration strings begin with the value set in {#build_iteration}
|
157
|
+
#
|
158
|
+
# @return [String]
|
159
|
+
def iteration
|
160
|
+
case platform_family
|
161
|
+
when 'rhel'
|
162
|
+
platform_version =~ /^(\d+)/
|
163
|
+
maj = $1
|
164
|
+
"#{build_iteration}.el#{maj}"
|
165
|
+
when 'freebsd'
|
166
|
+
platform_version =~ /^(\d+)/
|
167
|
+
maj = $1
|
168
|
+
"#{build_iteration}.#{platform}.#{maj}.#{machine}"
|
169
|
+
when 'windows'
|
170
|
+
"#{build_iteration}.windows"
|
171
|
+
when 'aix'
|
172
|
+
"#{build_iteration}"
|
173
|
+
else
|
174
|
+
"#{build_iteration}.#{platform}.#{platform_version}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Set or retrieve the project description. Defaults to `"The full
|
179
|
+
# stack of #{name}"`
|
180
|
+
#
|
181
|
+
# Corresponds to the `--description` flag of
|
182
|
+
# {https://github.com/jordansissel/fpm fpm}.
|
183
|
+
#
|
184
|
+
# @param val [String] the project description
|
185
|
+
# @return [String]
|
186
|
+
#
|
187
|
+
# @see #name
|
188
|
+
def description(val=NULL_ARG)
|
189
|
+
@description = val unless val.equal?(NULL_ARG)
|
190
|
+
@description || "The full stack of #{name}"
|
191
|
+
end
|
192
|
+
|
193
|
+
# Set or retrieve the name of the package this package will replace.
|
194
|
+
#
|
195
|
+
# Ultimately used as the value for the `--replaces` flag in
|
196
|
+
# {https://github.com/jordansissel/fpm fpm}.
|
197
|
+
#
|
198
|
+
# @param val [String] the name of the package to replace
|
199
|
+
# @return [String]
|
200
|
+
#
|
201
|
+
# @todo Consider having this default to {#package_name}; many uses of this
|
202
|
+
# method effectively do this already.
|
203
|
+
def replaces(val=NULL_ARG)
|
204
|
+
@replaces = val unless val.equal?(NULL_ARG)
|
205
|
+
@replaces
|
206
|
+
end
|
207
|
+
|
208
|
+
# Add to the list of packages this one conflicts with.
|
209
|
+
#
|
210
|
+
# Specifying conflicts is optional. See the `--conflicts` flag in
|
211
|
+
# {https://github.com/jordansissel/fpm fpm}.
|
212
|
+
#
|
213
|
+
# @param val [String]
|
214
|
+
# @return [void]
|
215
|
+
def conflict(val)
|
216
|
+
@conflicts << val
|
217
|
+
end
|
218
|
+
|
219
|
+
# Set or retrieve the version of the project.
|
220
|
+
#
|
221
|
+
# @param val [String] the version to set
|
222
|
+
# @return [String]
|
223
|
+
#
|
224
|
+
# @see Omnibus::BuildVersion
|
225
|
+
def build_version(val=NULL_ARG)
|
226
|
+
@build_version = val unless val.equal?(NULL_ARG)
|
227
|
+
@build_version
|
228
|
+
end
|
229
|
+
|
230
|
+
# Set or retrieve the build iteration of the project. Defaults to
|
231
|
+
# `1` if not otherwise set.
|
232
|
+
#
|
233
|
+
# @param val [Fixnum]
|
234
|
+
# @return [Fixnum]
|
235
|
+
#
|
236
|
+
# @todo Is there a better name for this than "build_iteration"?
|
237
|
+
# Would be nice to cut down confusiton with {#iteration}.
|
238
|
+
def build_iteration(val=NULL_ARG)
|
239
|
+
@build_iteration = val unless val.equal?(NULL_ARG)
|
240
|
+
@build_iteration || 1
|
241
|
+
end
|
242
|
+
|
243
|
+
# Add an Omnibus software dependency.
|
244
|
+
#
|
245
|
+
# Note that this is a *build time* dependency. If you need to
|
246
|
+
# specify an external dependency that is required at runtime, see
|
247
|
+
# {#runtime_dependency} instead.
|
248
|
+
#
|
249
|
+
# @param val [String] the name of a Software dependency
|
250
|
+
# @return [void]
|
251
|
+
def dependency(val)
|
252
|
+
@dependencies << val
|
253
|
+
end
|
254
|
+
|
255
|
+
# Add a package that is a runtime dependency of this
|
256
|
+
# project.
|
257
|
+
#
|
258
|
+
# This is distinct from a build-time dependency, which should
|
259
|
+
# correspond to an Omnibus software definition.
|
260
|
+
#
|
261
|
+
# Corresponds to the `--depends` flag of
|
262
|
+
# {https://github.com/jordansissel/fpm fpm}.
|
263
|
+
#
|
264
|
+
# @param val [String] the name of the runtime dependency
|
265
|
+
# @return [void]
|
266
|
+
def runtime_dependency(val)
|
267
|
+
@runtime_dependencies << val
|
268
|
+
end
|
269
|
+
|
270
|
+
# Set or retrieve the list of software dependencies for this
|
271
|
+
# project. As this is a DSL method, only pass the names of
|
272
|
+
# software components, not {Omnibus::Software} objects.
|
273
|
+
#
|
274
|
+
# These is the software that comprises your project, and is
|
275
|
+
# distinct from runtime dependencies.
|
276
|
+
#
|
277
|
+
# @note This will reinitialize the internal depdencies Array
|
278
|
+
# and overwrite any dependencies that may have been set using
|
279
|
+
# {#dependency}.
|
280
|
+
#
|
281
|
+
# @param val [Array<String>] a list of names of Software components
|
282
|
+
# @return [Array<String>]
|
283
|
+
def dependencies(val=NULL_ARG)
|
284
|
+
@dependencies = val unless val.equal?(NULL_ARG)
|
285
|
+
@dependencies
|
286
|
+
end
|
287
|
+
|
288
|
+
# Add a new exclusion pattern.
|
289
|
+
#
|
290
|
+
# Corresponds to the `--exclude` flag of {https://github.com/jordansissel/fpm fpm}.
|
291
|
+
#
|
292
|
+
# @param pattern [String]
|
293
|
+
# @return void
|
294
|
+
def exclude(pattern)
|
295
|
+
@exclusions << pattern
|
296
|
+
end
|
297
|
+
|
298
|
+
# Returns the platform version of the machine on which Omnibus is
|
299
|
+
# running, as determined by Ohai.
|
300
|
+
#
|
301
|
+
# @return [String]
|
302
|
+
def platform_version
|
303
|
+
OHAI.platform_version
|
304
|
+
end
|
305
|
+
|
306
|
+
# Returns the platform of the machine on which Omnibus is running,
|
307
|
+
# as determined by Ohai.
|
308
|
+
#
|
309
|
+
# @return [String]
|
310
|
+
def platform
|
311
|
+
OHAI.platform
|
312
|
+
end
|
313
|
+
|
314
|
+
# Returns the platform family of the machine on which Omnibus is
|
315
|
+
# running, as determined by Ohai.
|
316
|
+
#
|
317
|
+
# @return [String]
|
318
|
+
def platform_family
|
319
|
+
OHAI.platform_family
|
320
|
+
end
|
321
|
+
|
322
|
+
def machine
|
323
|
+
OHAI['kernel']['machine']
|
324
|
+
end
|
325
|
+
|
326
|
+
# Convenience method for accessing the global Omnibus configuration object.
|
327
|
+
#
|
328
|
+
# @return Omnibus::Config
|
329
|
+
#
|
330
|
+
# @see Omnibus::Config
|
331
|
+
def config
|
332
|
+
Omnibus.config
|
333
|
+
end
|
334
|
+
|
335
|
+
# The path to the package scripts directory for this project.
|
336
|
+
# These are optional scripts that can be bundled into the
|
337
|
+
# resulting package for running at various points in the package
|
338
|
+
# management lifecycle.
|
339
|
+
#
|
340
|
+
# Currently supported scripts include:
|
341
|
+
#
|
342
|
+
# * postinst
|
343
|
+
#
|
344
|
+
# A post-install script
|
345
|
+
# * prerm
|
346
|
+
#
|
347
|
+
# A pre-uninstall script
|
348
|
+
# * postrm
|
349
|
+
#
|
350
|
+
# A post-uninstall script
|
351
|
+
#
|
352
|
+
# Any scripts with these names that are present in the package
|
353
|
+
# scripts directory will be incorporated into the package that is
|
354
|
+
# built. This only applies to fpm-built packages.
|
355
|
+
#
|
356
|
+
# Additionally, there may be a `makeselfinst` script.
|
357
|
+
#
|
358
|
+
# @return [String]
|
359
|
+
#
|
360
|
+
# @todo This documentation really should be up at a higher level,
|
361
|
+
# particularly since the user has no way to change the path.
|
362
|
+
def package_scripts_path
|
363
|
+
"#{Omnibus.project_root}/package-scripts/#{name}"
|
364
|
+
end
|
365
|
+
|
366
|
+
# Determine the package type(s) to be built, based on the platform
|
367
|
+
# family for which the package is being built.
|
368
|
+
#
|
369
|
+
# If specific types cannot be determined, default to `["makeself"]`.
|
370
|
+
#
|
371
|
+
# @return [Array<(String)>]
|
372
|
+
#
|
373
|
+
# @todo Why does this only ever return a single-element array,
|
374
|
+
# instead of just a string, or symbol?
|
375
|
+
def package_types
|
376
|
+
case platform_family
|
377
|
+
when 'debian'
|
378
|
+
[ "deb" ]
|
379
|
+
when 'fedora', 'rhel'
|
380
|
+
[ "rpm" ]
|
381
|
+
when 'aix'
|
382
|
+
[ "bff" ]
|
383
|
+
when 'solaris2'
|
384
|
+
[ "solaris" ]
|
385
|
+
when 'windows'
|
386
|
+
[ "msi" ]
|
387
|
+
else
|
388
|
+
[ "makeself" ]
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# Indicates whether `software` is defined as a software component
|
393
|
+
# of this project.
|
394
|
+
#
|
395
|
+
# @param software [String, Omnibus::Software, #name]
|
396
|
+
# @return [Boolean]
|
397
|
+
#
|
398
|
+
# @see #dependencies
|
399
|
+
def dependency?(software)
|
400
|
+
name = if software.respond_to?(:name)
|
401
|
+
software.send(:name)
|
402
|
+
else
|
403
|
+
software
|
404
|
+
end
|
405
|
+
@dependencies.include?(name)
|
406
|
+
end
|
407
|
+
|
408
|
+
# @!endgroup
|
409
|
+
|
410
|
+
private
|
411
|
+
|
412
|
+
# An Array of platform data suitable for `Artifact.new`. This will go into
|
413
|
+
# metadata generated for the artifact, and be used for the file hierarchy
|
414
|
+
# of released packages if the default release scripts are used.
|
415
|
+
# @return [Array<String>] platform_shortname, platform_version_for_package,
|
416
|
+
# machine architecture.
|
417
|
+
def platform_tuple
|
418
|
+
[platform_shortname, platform_version_for_package, machine]
|
419
|
+
end
|
420
|
+
|
421
|
+
# Platform version to be used in package metadata. For rhel, the minor
|
422
|
+
# version is removed, e.g., "5.6" becomes "5". For all other platforms,
|
423
|
+
# this is just the platform_version.
|
424
|
+
# @return [String] the platform version
|
425
|
+
def platform_version_for_package
|
426
|
+
if platform == "rhel"
|
427
|
+
platform_version[/([\d]+)\..+/, 1]
|
428
|
+
else
|
429
|
+
platform_version
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
# Platform name to be used when creating metadata for the artifact.
|
434
|
+
# rhel/centos become "el", all others are just platform
|
435
|
+
# @return [String] the platform family short name
|
436
|
+
def platform_shortname
|
437
|
+
if platform_family == "rhel"
|
438
|
+
"el"
|
439
|
+
else
|
440
|
+
platform
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
def render_metadata(pkg_type)
|
445
|
+
basename = output_package(pkg_type)
|
446
|
+
pkg_path = "#{config.package_dir}/#{basename}"
|
447
|
+
artifact = Artifact.new(pkg_path, [ platform_tuple ], :version => build_version)
|
448
|
+
metadata = artifact.flat_metadata
|
449
|
+
File.open("#{pkg_path}.metadata.json", "w+") do |f|
|
450
|
+
f.print(JSON.pretty_generate(metadata))
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
# The basename of the resulting package file.
|
455
|
+
# @return [String] the basename of the package file
|
456
|
+
def output_package(pkg_type)
|
457
|
+
case pkg_type
|
458
|
+
when "makeself"
|
459
|
+
"#{package_name}-#{build_version}_#{iteration}.sh"
|
460
|
+
when "msi"
|
461
|
+
"#{package_name}-#{build_version}-#{iteration}.msi"
|
462
|
+
when "bff"
|
463
|
+
"#{package_name}.#{bff_version}.bff"
|
464
|
+
else # fpm
|
465
|
+
require "fpm/package/#{pkg_type}"
|
466
|
+
pkg = FPM::Package.types[pkg_type].new
|
467
|
+
pkg.version = build_version
|
468
|
+
pkg.name = package_name
|
469
|
+
pkg.iteration = iteration
|
470
|
+
if pkg_type == "solaris"
|
471
|
+
pkg.to_s("NAME.FULLVERSION.ARCH.TYPE")
|
472
|
+
else
|
473
|
+
pkg.to_s
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
# The command to generate an MSI package on Windows platforms.
|
479
|
+
#
|
480
|
+
# Does not execute the command, only assembles it.
|
481
|
+
#
|
482
|
+
# @return [Array<(String, Hash)>] The complete MSI command, plus a
|
483
|
+
# Hash of options to be passed on to Mixlib::ShellOut
|
484
|
+
#
|
485
|
+
# @see Mixlib::ShellOut
|
486
|
+
#
|
487
|
+
# @todo For this and all the *_command methods, just return a
|
488
|
+
# Mixlib::ShellOut object ready for execution. Using Arrays
|
489
|
+
# makes downstream processing needlessly complicated.
|
490
|
+
def msi_command
|
491
|
+
msi_command = ["light.exe",
|
492
|
+
"-nologo",
|
493
|
+
"-ext WixUIExtension",
|
494
|
+
"-cultures:en-us",
|
495
|
+
"-loc #{install_path}\\msi-tmp\\#{package_name}-en-us.wxl",
|
496
|
+
"#{install_path}\\msi-tmp\\#{package_name}-Files.wixobj",
|
497
|
+
"#{install_path}\\msi-tmp\\#{package_name}.wixobj",
|
498
|
+
"-out #{config.package_dir}\\#{output_package("msi")}"]
|
499
|
+
|
500
|
+
# Don't care about the 204 return code from light.exe since it's
|
501
|
+
# about some expected warnings...
|
502
|
+
[msi_command.join(" "), {:returns => [0, 204]}]
|
503
|
+
end
|
504
|
+
|
505
|
+
def bff_command
|
506
|
+
bff_command = ["mkinstallp -d / -T /tmp/bff/gen.template"]
|
507
|
+
[bff_command.join(" "), {:returns => [0]}]
|
508
|
+
end
|
509
|
+
|
510
|
+
# The {https://github.com/jordansissel/fpm fpm} command to
|
511
|
+
# generate a package for RedHat, Ubuntu, Solaris, etc. platforms.
|
512
|
+
#
|
513
|
+
# Does not execute the command, only assembles it.
|
514
|
+
#
|
515
|
+
# In contrast to {#msi_command}, command generated by
|
516
|
+
# {#fpm_command} does not require any Mixlib::Shellout options.
|
517
|
+
#
|
518
|
+
# @return [Array<String>] the components of the fpm command; need
|
519
|
+
# to be joined with " " first.
|
520
|
+
#
|
521
|
+
# @todo Just make this return a String instead of an Array
|
522
|
+
# @todo Use the long option names (i.e., the double-dash ones) in
|
523
|
+
# the fpm command for maximum clarity.
|
524
|
+
def fpm_command(pkg_type)
|
525
|
+
command_and_opts = ["fpm",
|
526
|
+
"-s dir",
|
527
|
+
"-t #{pkg_type}",
|
528
|
+
"-v #{build_version}",
|
529
|
+
"-n #{package_name}",
|
530
|
+
"-p #{output_package(pkg_type)}",
|
531
|
+
"--iteration #{iteration}",
|
532
|
+
"-m '#{maintainer}'",
|
533
|
+
"--description '#{description}'",
|
534
|
+
"--url #{homepage}"]
|
535
|
+
if File.exist?("#{package_scripts_path}/postinst")
|
536
|
+
command_and_opts << "--post-install '#{package_scripts_path}/postinst'"
|
537
|
+
end
|
538
|
+
# solaris packages don't support --pre-uninstall
|
539
|
+
if File.exist?("#{package_scripts_path}/prerm") && pkg_type != "solaris"
|
540
|
+
command_and_opts << "--pre-uninstall '#{package_scripts_path}/prerm'"
|
541
|
+
end
|
542
|
+
# solaris packages don't support --post-uninstall
|
543
|
+
if File.exist?("#{package_scripts_path}/postrm") && pkg_type != "solaris"
|
544
|
+
command_and_opts << "--post-uninstall '#{package_scripts_path}/postrm'"
|
545
|
+
end
|
546
|
+
|
547
|
+
@exclusions.each do |pattern|
|
548
|
+
command_and_opts << "--exclude '#{pattern}'"
|
549
|
+
end
|
550
|
+
|
551
|
+
@runtime_dependencies.each do |runtime_dep|
|
552
|
+
command_and_opts << "--depends '#{runtime_dep}'"
|
553
|
+
end
|
554
|
+
|
555
|
+
@conflicts.each do |conflict|
|
556
|
+
command_and_opts << "--conflicts '#{conflict}'"
|
557
|
+
end
|
558
|
+
|
559
|
+
command_and_opts << " --replaces #{@replaces}" if @replaces
|
560
|
+
command_and_opts << install_path
|
561
|
+
command_and_opts
|
562
|
+
end
|
563
|
+
|
564
|
+
# TODO: what's this do?
|
565
|
+
def makeself_command
|
566
|
+
command_and_opts = [ File.expand_path(File.join(Omnibus.source_root, "bin", "makeself.sh")),
|
567
|
+
"--gzip",
|
568
|
+
install_path,
|
569
|
+
output_package("makeself"),
|
570
|
+
"'The full stack of #{@name}'"
|
571
|
+
]
|
572
|
+
command_and_opts << "./makeselfinst" if File.exists?("#{package_scripts_path}/makeselfinst")
|
573
|
+
command_and_opts
|
574
|
+
end
|
575
|
+
|
576
|
+
# Runs the makeself commands to make a self extracting archive package.
|
577
|
+
# As a (necessary) side-effect, sets
|
578
|
+
# @return void
|
579
|
+
def run_makeself
|
580
|
+
package_commands = []
|
581
|
+
# copy the makeself installer into package
|
582
|
+
if File.exists?("#{package_scripts_path}/makeselfinst")
|
583
|
+
package_commands << "cp #{package_scripts_path}/makeselfinst #{install_path}/"
|
584
|
+
end
|
585
|
+
|
586
|
+
# run the makeself program
|
587
|
+
package_commands << makeself_command.join(" ")
|
588
|
+
|
589
|
+
# rm the makeself installer (for incremental builds)
|
590
|
+
package_commands << "rm -f #{install_path}/makeselfinst"
|
591
|
+
package_commands.each {|cmd| run_package_command(cmd) }
|
592
|
+
end
|
593
|
+
|
594
|
+
# Runs the necessary command to make an MSI. As a side-effect, sets `output_package`
|
595
|
+
# @return void
|
596
|
+
def run_msi
|
597
|
+
run_package_command(msi_command)
|
598
|
+
end
|
599
|
+
|
600
|
+
def bff_version
|
601
|
+
build_version.split(/[^\d]/)[0..2].join(".") + ".#{iteration}"
|
602
|
+
end
|
603
|
+
|
604
|
+
def run_bff
|
605
|
+
FileUtils.rm_rf "/.info"
|
606
|
+
FileUtils.rm_rf "/tmp/bff"
|
607
|
+
FileUtils.mkdir "/tmp/bff"
|
608
|
+
|
609
|
+
system "find #{install_path} -print > /tmp/bff/file.list"
|
610
|
+
|
611
|
+
system "cat #{package_scripts_path}/aix/opscode.chef.client.template | sed -e 's/TBS/#{bff_version}/' > /tmp/bff/gen.preamble"
|
612
|
+
|
613
|
+
# @todo can we just use an erb template here?
|
614
|
+
system "cat /tmp/bff/gen.preamble /tmp/bff/file.list #{package_scripts_path}/aix/opscode.chef.client.template.last > /tmp/bff/gen.template"
|
615
|
+
|
616
|
+
FileUtils.cp "#{package_scripts_path}/aix/unpostinstall.sh", "#{install_path}/bin"
|
617
|
+
FileUtils.cp "#{package_scripts_path}/aix/postinstall.sh", "#{install_path}/bin"
|
618
|
+
|
619
|
+
run_package_command(bff_command)
|
620
|
+
|
621
|
+
FileUtils.cp "/tmp/chef.#{bff_version}.bff", "/var/cache/omnibus/pkg/chef.#{bff_version}.bff"
|
622
|
+
end
|
623
|
+
|
624
|
+
# Runs the necessary command to make a package with fpm. As a side-effect,
|
625
|
+
# sets `output_package`
|
626
|
+
# @return void
|
627
|
+
def run_fpm(pkg_type)
|
628
|
+
run_package_command(fpm_command(pkg_type).join(" "))
|
629
|
+
end
|
630
|
+
|
631
|
+
# Executes the given command via mixlib-shellout.
|
632
|
+
# @return [Mixlib::ShellOut] returns the underlying Mixlib::ShellOut
|
633
|
+
# object, so the caller can inspect the stdout and stderr.
|
634
|
+
def run_package_command(cmd)
|
635
|
+
cmd_options = {
|
636
|
+
:timeout => 3600,
|
637
|
+
:cwd => config.package_dir
|
638
|
+
}
|
639
|
+
|
640
|
+
if cmd.is_a?(Array)
|
641
|
+
command = cmd[0]
|
642
|
+
cmd_options.merge!(cmd[1])
|
643
|
+
else
|
644
|
+
command = cmd
|
645
|
+
end
|
646
|
+
|
647
|
+
shellout!(command, cmd_options)
|
648
|
+
end
|
649
|
+
|
650
|
+
# Dynamically generate Rake tasks to build projects and all the software they depend on.
|
651
|
+
#
|
652
|
+
# @note Much Rake magic ahead!
|
653
|
+
#
|
654
|
+
# @return void
|
655
|
+
def render_tasks
|
656
|
+
directory config.package_dir
|
657
|
+
directory "pkg"
|
658
|
+
|
659
|
+
namespace :projects do
|
660
|
+
namespace @name do
|
661
|
+
|
662
|
+
package_types.each do |pkg_type|
|
663
|
+
dep_tasks = @dependencies.map {|dep| "software:#{dep}"}
|
664
|
+
dep_tasks << config.package_dir
|
665
|
+
dep_tasks << "health_check"
|
666
|
+
|
667
|
+
desc "package #{@name} into a #{pkg_type}"
|
668
|
+
task pkg_type => dep_tasks do
|
669
|
+
if pkg_type == "makeself"
|
670
|
+
run_makeself
|
671
|
+
elsif pkg_type == "msi"
|
672
|
+
run_msi
|
673
|
+
elsif pkg_type == "bff"
|
674
|
+
run_bff
|
675
|
+
else # pkg_type == "fpm"
|
676
|
+
run_fpm(pkg_type)
|
677
|
+
end
|
678
|
+
|
679
|
+
render_metadata(pkg_type)
|
680
|
+
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
task "copy" => package_types do
|
685
|
+
if OHAI.platform == "windows"
|
686
|
+
cp_cmd = "xcopy #{config.package_dir}\\*.msi pkg\\ /Y"
|
687
|
+
elsif OHAI.platform == "aix"
|
688
|
+
cp_cmd = "cp #{config.package_dir}/*.bff pkg/"
|
689
|
+
else
|
690
|
+
cp_cmd = "cp #{config.package_dir}/* pkg/"
|
691
|
+
end
|
692
|
+
shell = Mixlib::ShellOut.new(cp_cmd)
|
693
|
+
shell.run_command
|
694
|
+
shell.error!
|
695
|
+
end
|
696
|
+
task "copy" => "pkg"
|
697
|
+
|
698
|
+
desc "run the health check on the #{@name} install path"
|
699
|
+
task "health_check" do
|
700
|
+
if OHAI.platform == "windows"
|
701
|
+
puts "Skipping health check on windows..."
|
702
|
+
else
|
703
|
+
# build a list of all whitelist files from all project dependencies
|
704
|
+
whitelist_files = library.components.map{|component| component.whitelist_files }.flatten
|
705
|
+
Omnibus::HealthCheck.run(install_path, whitelist_files)
|
706
|
+
end
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
710
|
+
desc "package #{@name}"
|
711
|
+
task @name => "#{@name}:copy"
|
712
|
+
end
|
713
|
+
end
|
714
|
+
end
|
715
|
+
end
|