omnibus 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +7 -0
  5. data/CHANGELOG.md +3 -0
  6. data/Gemfile +9 -0
  7. data/LICENSE +201 -0
  8. data/NOTICE +9 -0
  9. data/README.md +186 -0
  10. data/Rakefile +7 -0
  11. data/bin/makeself-header.sh +401 -0
  12. data/bin/makeself.sh +407 -0
  13. data/bin/omnibus +11 -0
  14. data/lib/omnibus.rb +280 -0
  15. data/lib/omnibus/build_version.rb +281 -0
  16. data/lib/omnibus/builder.rb +323 -0
  17. data/lib/omnibus/clean_tasks.rb +30 -0
  18. data/lib/omnibus/cli.rb +35 -0
  19. data/lib/omnibus/cli/application.rb +136 -0
  20. data/lib/omnibus/cli/base.rb +112 -0
  21. data/lib/omnibus/cli/build.rb +66 -0
  22. data/lib/omnibus/cli/cache.rb +60 -0
  23. data/lib/omnibus/config.rb +186 -0
  24. data/lib/omnibus/exceptions.rb +54 -0
  25. data/lib/omnibus/fetcher.rb +184 -0
  26. data/lib/omnibus/fetchers.rb +22 -0
  27. data/lib/omnibus/fetchers/git_fetcher.rb +212 -0
  28. data/lib/omnibus/fetchers/net_fetcher.rb +191 -0
  29. data/lib/omnibus/fetchers/path_fetcher.rb +65 -0
  30. data/lib/omnibus/fetchers/s3_cache_fetcher.rb +42 -0
  31. data/lib/omnibus/health_check.rb +260 -0
  32. data/lib/omnibus/library.rb +70 -0
  33. data/lib/omnibus/overrides.rb +69 -0
  34. data/lib/omnibus/project.rb +566 -0
  35. data/lib/omnibus/reports.rb +99 -0
  36. data/lib/omnibus/s3_cacher.rb +136 -0
  37. data/lib/omnibus/software.rb +430 -0
  38. data/lib/omnibus/templates/Berksfile.erb +3 -0
  39. data/lib/omnibus/templates/Gemfile.erb +4 -0
  40. data/lib/omnibus/templates/README.md.erb +102 -0
  41. data/lib/omnibus/templates/Vagrantfile.erb +95 -0
  42. data/lib/omnibus/templates/gitignore.erb +8 -0
  43. data/lib/omnibus/templates/omnibus.rb.example.erb +5 -0
  44. data/lib/omnibus/templates/package_scripts/makeselfinst.erb +27 -0
  45. data/lib/omnibus/templates/package_scripts/postinst.erb +17 -0
  46. data/lib/omnibus/templates/package_scripts/postrm.erb +9 -0
  47. data/lib/omnibus/templates/project.rb.erb +21 -0
  48. data/lib/omnibus/templates/software/c-example.rb.erb +42 -0
  49. data/lib/omnibus/templates/software/erlang-example.rb.erb +38 -0
  50. data/lib/omnibus/templates/software/ruby-example.rb.erb +24 -0
  51. data/lib/omnibus/util.rb +61 -0
  52. data/lib/omnibus/version.rb +20 -0
  53. data/omnibus.gemspec +34 -0
  54. data/spec/build_version_spec.rb +228 -0
  55. data/spec/data/overrides/bad_line.overrides +3 -0
  56. data/spec/data/overrides/good.overrides +5 -0
  57. data/spec/data/overrides/with_dupes.overrides +4 -0
  58. data/spec/data/software/erchef.rb +40 -0
  59. data/spec/overrides_spec.rb +114 -0
  60. data/spec/software_spec.rb +71 -0
  61. data/spec/spec_helper.rb +28 -0
  62. metadata +239 -0
@@ -0,0 +1,323 @@
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
+ require 'forwardable'
19
+
20
+ module Omnibus
21
+ class Builder
22
+
23
+ # Proxies method calls to either a Builder object or the Software that the
24
+ # builder belongs to. Provides compatibility with our DSL where we never
25
+ # yield objects to blocks and hopefully hides some of the confusion that
26
+ # can arise from instance_eval.
27
+ class DSLProxy
28
+ extend Forwardable
29
+
30
+ # @todo def_delegators :@builder, :patch, :command, :ruby, ...
31
+
32
+ def_delegator :@builder, :patch
33
+ def_delegator :@builder, :command
34
+ def_delegator :@builder, :ruby
35
+ def_delegator :@builder, :gem
36
+ def_delegator :@builder, :bundle
37
+ def_delegator :@builder, :rake
38
+ def_delegator :@builder, :block
39
+ def_delegator :@builder, :name
40
+
41
+ def initialize(builder, software)
42
+ @builder, @software = builder, software
43
+ end
44
+
45
+ def eval_block(&block)
46
+ instance_eval(&block)
47
+ end
48
+
49
+ def respond_to?(method)
50
+ super || @software.respond_to?(method)
51
+ end
52
+
53
+ def methods
54
+ super | @software.methods
55
+ end
56
+
57
+ def method_missing(method_name, *args, &block)
58
+ if @software.respond_to?(method_name)
59
+ @software.send(method_name, *args, &block)
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+
68
+ # @todo code duplication with {Fetcher::ErrorReporter}
69
+ class ErrorReporter
70
+
71
+ # @todo fetcher isn't even used
72
+ def initialize(error, fetcher)
73
+ @error, @fetcher = error, fetcher
74
+ end
75
+
76
+ # @todo this isn't necessary
77
+ def e
78
+ @error
79
+ end
80
+
81
+ def explain(why)
82
+ $stderr.puts "* " * 40
83
+ $stderr.puts why
84
+ $stderr.puts "Exception:"
85
+ $stderr.puts indent("#{e.class}: #{e.message.strip}", 2)
86
+ Array(e.backtrace).each {|l| $stderr.puts indent(l, 4) }
87
+ $stderr.puts "* " * 40
88
+ end
89
+
90
+ private
91
+
92
+ def indent(string, n)
93
+ string.split("\n").map {|l| " ".rjust(n) << l }.join("\n")
94
+ end
95
+
96
+ end
97
+
98
+ # @todo Look at using Bundler.with_clean_env{ ... } instead
99
+ BUNDLER_BUSTER = { "RUBYOPT" => nil,
100
+ "BUNDLE_BIN_PATH" => nil,
101
+ "BUNDLE_GEMFILE" => nil,
102
+ "GEM_PATH" => nil,
103
+ "GEM_HOME" => nil }
104
+
105
+ attr_reader :build_commands
106
+
107
+ def initialize(software, &block)
108
+ @software = software
109
+ @build_commands = []
110
+ @dsl_proxy = DSLProxy.new(self, software)
111
+ @dsl_proxy.eval_block(&block) if block_given?
112
+ end
113
+
114
+ def name
115
+ @software.name
116
+ end
117
+
118
+ def command(*args)
119
+ @build_commands << args
120
+ end
121
+
122
+ def patch(*args)
123
+ args = args.dup.pop
124
+
125
+ # we'll search for a patch file in the project root AND
126
+ # the omnibus-software gem
127
+ candidate_roots = [Omnibus.project_root]
128
+ candidate_roots << Omnibus.omnibus_software_root if Omnibus.omnibus_software_root
129
+
130
+ candidate_paths = candidate_roots.map do |root|
131
+ File.expand_path("#{root}/config/patches/#{name}/#{args[:source]}")
132
+ end
133
+
134
+ source = candidate_paths.find{|path| File.exists?(path) }
135
+
136
+ plevel = args[:plevel] || 1
137
+ if args[:target]
138
+ target = File.expand_path("#{project_dir}/#{args[:target]}")
139
+ @build_commands <<
140
+ "cat #{source} | patch -p#{plevel} #{target}"
141
+ else
142
+ @build_commands <<
143
+ "patch -d #{project_dir} -p#{plevel} -i #{source}"
144
+ end
145
+ end
146
+
147
+ # @todo all these ruby commands (ruby, gem, bundle, rake) could
148
+ # all be collapsed into a single underlying implementation, since
149
+ # they all just differ on the executable being called
150
+ def ruby(*args)
151
+ @build_commands << bundle_bust(*prepend_cmd("#{install_dir}/embedded/bin/ruby", *args))
152
+ end
153
+
154
+ def gem(*args)
155
+ @build_commands << bundle_bust(*prepend_cmd("#{install_dir}/embedded/bin/gem", *args))
156
+ end
157
+
158
+ def bundle(*args)
159
+ @build_commands << bundle_bust(*prepend_cmd("#{install_dir}/embedded/bin/bundle", *args))
160
+ end
161
+
162
+ def rake(*args)
163
+ @build_commands << bundle_bust(*prepend_cmd("#{install_dir}/embedded/bin/rake", *args))
164
+ end
165
+
166
+ def block(&rb_block)
167
+ @build_commands << rb_block
168
+ end
169
+
170
+ def project_dir
171
+ @software.project_dir
172
+ end
173
+
174
+ def install_dir
175
+ @software.install_dir
176
+ end
177
+
178
+ def log(message)
179
+ puts "[builder:#{name}] #{message}"
180
+ end
181
+
182
+ def build
183
+ log "building #{name}"
184
+ time_it("#{name} build") do
185
+ @build_commands.each do |cmd|
186
+ execute(cmd)
187
+ end
188
+ end
189
+ end
190
+
191
+ def execute(cmd)
192
+ case cmd
193
+ when Proc
194
+ execute_proc(cmd)
195
+ else
196
+ execute_sh(cmd)
197
+ end
198
+ end
199
+
200
+ private
201
+
202
+ def execute_proc(cmd)
203
+ cmd.call
204
+ rescue Exception => e
205
+ # In Ruby 1.9, Procs have a #source_location method with file/line info.
206
+ # Too bad we can't use it :(
207
+ ErrorReporter.new(e, self).explain("Failed to build #{name} while running ruby block build step")
208
+ raise
209
+ end
210
+
211
+ def execute_sh(cmd)
212
+ retries ||= 0
213
+ shell = nil
214
+ cmd_args = Array(cmd)
215
+ options = {
216
+ :cwd => project_dir,
217
+ :timeout => 5400
218
+ }
219
+ options[:live_stream] = STDOUT if ENV['DEBUG']
220
+ if cmd_args.last.is_a? Hash
221
+ cmd_options = cmd_args.last
222
+ cmd_args[cmd_args.size - 1] = options.merge(cmd_options)
223
+ else
224
+ cmd_args << options
225
+ end
226
+
227
+ cmd_string = cmd_args[0..-2].join(' ')
228
+ cmd_opts_for_display = to_kv_str(cmd_args.last)
229
+
230
+ log "Executing: `#{cmd_string}` with #{cmd_opts_for_display}"
231
+
232
+ shell = Mixlib::ShellOut.new(*cmd)
233
+ shell.environment["HOME"] = "/tmp" unless ENV["HOME"]
234
+
235
+ cmd_name = cmd_string.split(/\s+/).first
236
+ time_it("#{cmd_name} command") do
237
+ shell.run_command
238
+ shell.error!
239
+ end
240
+ rescue Exception => e
241
+ # Getting lots of errors from github, particularly with erlang/rebar
242
+ # projects fetching tons of deps via git all the time. This isn't a
243
+ # particularly elegant way to solve that problem. But it should work.
244
+ if retries >= 3
245
+ ErrorReporter.new(e, self).explain("Failed to build #{name} while running `#{cmd_string}` with #{cmd_opts_for_display}")
246
+ raise
247
+ else
248
+ time_to_sleep = 5 * (2 ** retries)
249
+ retries +=1
250
+ log "Failed to execute cmd #{cmd} #{retries} time(s). Retrying in #{time_to_sleep}s."
251
+ sleep(time_to_sleep)
252
+ retry
253
+ end
254
+ end
255
+
256
+ def prepend_cmd(str, *cmd_args)
257
+ if cmd_args.size == 1
258
+ # command as a string, no opts
259
+ "#{str} #{cmd_args.first}"
260
+ elsif cmd_args.size == 2 && cmd_args.last.is_a?(Hash)
261
+ # command as a string w/ opts
262
+ ["#{str} #{cmd_args.first}", cmd_args.last]
263
+ elsif cmd_args.size == 0
264
+ raise ArgumentError, "I don't even"
265
+ else
266
+ # cmd given as argv array
267
+ cmd_args.dup.unshift(str)
268
+ end
269
+ end
270
+
271
+ def bundle_bust(*cmd_args)
272
+ if cmd_args.last.is_a?(Hash)
273
+ cmd_args = cmd_args.dup
274
+ cmd_opts = cmd_args.pop.dup
275
+ cmd_opts[:env] = cmd_opts[:env] ? BUNDLER_BUSTER.merge(cmd_opts[:env]) : BUNDLER_BUSTER
276
+ cmd_args << cmd_opts
277
+ else
278
+ cmd_args << {:env => BUNDLER_BUSTER}
279
+ end
280
+ end
281
+
282
+
283
+ def time_it(what)
284
+ start = Time.now
285
+ yield
286
+ rescue Exception
287
+ elapsed = Time.now - start
288
+ log "#{what} failed, #{elapsed.to_f}s"
289
+ raise
290
+ else
291
+ elapsed = Time.now - start
292
+ log "#{what} succeeded, #{elapsed.to_f}s"
293
+ end
294
+
295
+ # Convert a hash to a string in the form `key=value`. It should work with
296
+ # whatever input is given but is designed to make the options to ShellOut
297
+ # look nice.
298
+ def to_kv_str(hash, join_str=",")
299
+ hash.inject([]) do |kv_pair_strs, (k,v)|
300
+ val_str = case v
301
+ when Hash
302
+ %Q["#{to_kv_str(v, " ")}"]
303
+ else
304
+ v.to_s
305
+ end
306
+ kv_pair_strs << "#{k}=#{val_str}"
307
+ end.join(join_str)
308
+ end
309
+
310
+ end
311
+
312
+ # @todo What's the point of this class? Can we not just detect that
313
+ # there are no commands in {Omnibus::Builder#build} and output the
314
+ # appropriate message? Seems like a lot of extra ceremony.
315
+ class NullBuilder < Builder
316
+
317
+ def build
318
+ log "Nothing to build for #{name}"
319
+ end
320
+
321
+ end
322
+
323
+ end
@@ -0,0 +1,30 @@
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
+ # Adds source and build artifacts to Rake's CLEAN list, and install
19
+ # and cache artifacts to its CLOBBER list, ensuring that the `clean`
20
+ # and `clobber` Rake tasks do the right thing for Omnibus projects.
21
+ #
22
+ # @see https://github.com/jimweirich/rake/blob/master/lib/rake/clean.rb
23
+
24
+ require 'rake/clean'
25
+
26
+ ::CLEAN.include("#{Omnibus.config.source_dir}/**/*",
27
+ "#{Omnibus.config.build_dir}/**/*")
28
+
29
+ ::CLOBBER.include("#{Omnibus.config.cache_dir}/**/*",
30
+ "#{Omnibus.config.package_dir}/**/*")
@@ -0,0 +1,35 @@
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
+ require 'omnibus/cli/application'
19
+ require 'omnibus/cli/base'
20
+ require 'omnibus/cli/build'
21
+
22
+ module Omnibus
23
+ module CLI
24
+
25
+ class Error < StandardError
26
+ attr_reader :original
27
+
28
+ def initialize(msg, original=nil)
29
+ super(msg)
30
+ @original = original
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,136 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2013 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
+ require 'omnibus'
19
+ require 'omnibus/cli/base'
20
+ require 'omnibus/cli/build'
21
+ require 'omnibus/cli/cache'
22
+
23
+ module Omnibus
24
+ module CLI
25
+ class Application < Base
26
+
27
+ method_option :purge,
28
+ :type => :boolean,
29
+ :default => false,
30
+ :desc => "Remove ALL files generated during the build (including packages)."
31
+ method_option :path,
32
+ :aliases => [:p],
33
+ :type => :string,
34
+ :default => Dir.pwd,
35
+ :desc => "Path to the Omnibus project root."
36
+ desc "clean PROJECT", "Remove temporary files generated during the build process."
37
+ def clean(project_name)
38
+ project = load_project!(project_name)
39
+
40
+ deletion_list = []
41
+ deletion_list << Dir.glob("#{Omnibus.config.source_dir}/**/*")
42
+ deletion_list << Dir.glob("#{Omnibus.config.build_dir}/**/*")
43
+
44
+ if options[:purge]
45
+ deletion_list << Dir.glob("#{Omnibus.config.package_dir}/**/*")
46
+ deletion_list << Dir.glob("#{Omnibus.config.cache_dir}/**/*")
47
+ deletion_list << project.install_path if File.exist?(project.install_path)
48
+ end
49
+
50
+ deletion_list.flatten!
51
+ deletion_list.each{|f| remove_file(f) }
52
+ end
53
+
54
+ desc "project PROJECT", "Creates a skeletal Omnibus project"
55
+ def project(name)
56
+ name = name.chomp("/") # remove trailing slash if present
57
+ target = File.join(Dir.pwd, "omnibus-#{name}")
58
+ install_path = File.join("/opt", name)
59
+ opts = {
60
+ :name => name,
61
+ :install_path => install_path
62
+ }
63
+
64
+ # core project files
65
+ template(File.join("Gemfile.erb"), File.join(target, "Gemfile"), opts)
66
+ template(File.join("gitignore.erb"), File.join(target, ".gitignore"), opts)
67
+ template(File.join("README.md.erb"), File.join(target, "README.md"), opts)
68
+ template(File.join("omnibus.rb.example.erb"), File.join(target, "omnibus.rb.example"), opts)
69
+
70
+ # project definition
71
+ template(File.join("project.rb.erb"), File.join(target, "config", "projects", "#{name}.rb"), opts)
72
+
73
+ # example software definitions
74
+ config_software = File.join(target, "config", "software")
75
+ template(File.join("software", "c-example.rb.erb"),
76
+ File.join(config_software, "c-example.rb"), opts)
77
+ template(File.join("software", "erlang-example.rb.erb"),
78
+ File.join(config_software, "erlang-example.rb"), opts)
79
+ template(File.join("software", "ruby-example.rb.erb"),
80
+ File.join(config_software, "ruby-example.rb"), opts)
81
+
82
+ # Vagrant build lab
83
+ template(File.join("Berksfile.erb"), File.join(target, "Berksfile"), opts)
84
+ template(File.join("Vagrantfile.erb"), File.join(target, "Vagrantfile"), opts)
85
+
86
+ # render out stub packge scripts
87
+ %w{ makeselfinst postinst postrm }.each do |package_script|
88
+ script_path = File.join(target, "package-scripts", name, package_script)
89
+ template_path = File.join("package_scripts", "#{package_script}.erb")
90
+ # render the package script
91
+ template(template_path, script_path, opts)
92
+ # ensure the package script is executable
93
+ FileUtils.chmod(0755, script_path)
94
+ end
95
+ end
96
+
97
+ desc "version", "Display version information"
98
+ def version
99
+ say("Omnibus: #{Omnibus::VERSION}", :yellow)
100
+ end
101
+
102
+ ###########################################################################
103
+ # Subcommands
104
+ ###########################################################################
105
+
106
+ desc "build [COMMAND]", "Perform build-related tasks"
107
+ subcommand "build", Omnibus::CLI::Build
108
+
109
+ desc "cache [COMMAND]", "Perform cache management tasks"
110
+ subcommand "cache", Omnibus::CLI::Cache
111
+
112
+ ###########################################################################
113
+ # Class Methods
114
+ ###########################################################################
115
+
116
+ # Override start so we can catch and process any exceptions bubbling up
117
+ def self.start(*args)
118
+ begin
119
+ super
120
+ rescue => e
121
+ error_msg = "Something went wrong...the Omnibus just ran off the road!"
122
+ error_msg << "\n\nError raised was:\n\n\t#{$!}"
123
+ error_msg << "\n\nBacktrace:\n\n\t#{e.backtrace.join("\n\t")}"
124
+ if e.respond_to?(:original) && e.original
125
+ error_msg << "\n\nOriginal Error:\n\n\t#{e.original}"
126
+ error_msg << "\n\nOriginal Backtrace:\n\n\t#{e.original.backtrace.join("\n\t")}"
127
+ end
128
+ # TODO - we need a proper UI class
129
+ Thor::Base.shell.new.say(error_msg, :red)
130
+ exit 1
131
+ end
132
+ end
133
+
134
+ end
135
+ end
136
+ end