omnibus-sonian 1.2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +5 -0
  5. data/.yardopts +6 -0
  6. data/CHANGELOG.md +96 -0
  7. data/Gemfile +9 -0
  8. data/LICENSE +201 -0
  9. data/NOTICE +9 -0
  10. data/README.md +195 -0
  11. data/Rakefile +7 -0
  12. data/bin/makeself-header.sh +401 -0
  13. data/bin/makeself.sh +407 -0
  14. data/bin/omnibus +11 -0
  15. data/lib/omnibus.rb +304 -0
  16. data/lib/omnibus/artifact.rb +151 -0
  17. data/lib/omnibus/build_version.rb +285 -0
  18. data/lib/omnibus/builder.rb +328 -0
  19. data/lib/omnibus/clean_tasks.rb +30 -0
  20. data/lib/omnibus/cli.rb +35 -0
  21. data/lib/omnibus/cli/application.rb +140 -0
  22. data/lib/omnibus/cli/base.rb +118 -0
  23. data/lib/omnibus/cli/build.rb +62 -0
  24. data/lib/omnibus/cli/cache.rb +60 -0
  25. data/lib/omnibus/cli/release.rb +49 -0
  26. data/lib/omnibus/config.rb +224 -0
  27. data/lib/omnibus/exceptions.rb +143 -0
  28. data/lib/omnibus/fetcher.rb +184 -0
  29. data/lib/omnibus/fetchers.rb +22 -0
  30. data/lib/omnibus/fetchers/git_fetcher.rb +212 -0
  31. data/lib/omnibus/fetchers/net_fetcher.rb +193 -0
  32. data/lib/omnibus/fetchers/path_fetcher.rb +65 -0
  33. data/lib/omnibus/fetchers/s3_cache_fetcher.rb +42 -0
  34. data/lib/omnibus/health_check.rb +356 -0
  35. data/lib/omnibus/library.rb +62 -0
  36. data/lib/omnibus/overrides.rb +69 -0
  37. data/lib/omnibus/package_release.rb +163 -0
  38. data/lib/omnibus/project.rb +715 -0
  39. data/lib/omnibus/reports.rb +99 -0
  40. data/lib/omnibus/s3_cacher.rb +138 -0
  41. data/lib/omnibus/software.rb +441 -0
  42. data/lib/omnibus/templates/Berksfile.erb +3 -0
  43. data/lib/omnibus/templates/Gemfile.erb +4 -0
  44. data/lib/omnibus/templates/README.md.erb +102 -0
  45. data/lib/omnibus/templates/Vagrantfile.erb +95 -0
  46. data/lib/omnibus/templates/gitignore.erb +8 -0
  47. data/lib/omnibus/templates/omnibus.rb.example.erb +5 -0
  48. data/lib/omnibus/templates/package_scripts/makeselfinst.erb +27 -0
  49. data/lib/omnibus/templates/package_scripts/postinst.erb +17 -0
  50. data/lib/omnibus/templates/package_scripts/postrm.erb +9 -0
  51. data/lib/omnibus/templates/project.rb.erb +21 -0
  52. data/lib/omnibus/templates/software/c-example.rb.erb +42 -0
  53. data/lib/omnibus/templates/software/erlang-example.rb.erb +38 -0
  54. data/lib/omnibus/templates/software/ruby-example.rb.erb +24 -0
  55. data/lib/omnibus/util.rb +61 -0
  56. data/lib/omnibus/version.rb +20 -0
  57. data/omnibus.gemspec +34 -0
  58. data/spec/artifact_spec.rb +106 -0
  59. data/spec/build_version_spec.rb +266 -0
  60. data/spec/data/overrides/bad_line.overrides +3 -0
  61. data/spec/data/overrides/good.overrides +5 -0
  62. data/spec/data/overrides/with_dupes.overrides +4 -0
  63. data/spec/data/software/erchef.rb +40 -0
  64. data/spec/fetchers/net_fetcher_spec.rb +16 -0
  65. data/spec/overrides_spec.rb +114 -0
  66. data/spec/package_release_spec.rb +197 -0
  67. data/spec/s3_cacher_spec.rb +47 -0
  68. data/spec/software_spec.rb +85 -0
  69. data/spec/spec_helper.rb +28 -0
  70. 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