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.
- checksums.yaml +15 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +9 -0
- data/LICENSE +201 -0
- data/NOTICE +9 -0
- data/README.md +186 -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 +280 -0
- data/lib/omnibus/build_version.rb +281 -0
- data/lib/omnibus/builder.rb +323 -0
- data/lib/omnibus/clean_tasks.rb +30 -0
- data/lib/omnibus/cli.rb +35 -0
- data/lib/omnibus/cli/application.rb +136 -0
- data/lib/omnibus/cli/base.rb +112 -0
- data/lib/omnibus/cli/build.rb +66 -0
- data/lib/omnibus/cli/cache.rb +60 -0
- data/lib/omnibus/config.rb +186 -0
- data/lib/omnibus/exceptions.rb +54 -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 +191 -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 +260 -0
- data/lib/omnibus/library.rb +70 -0
- data/lib/omnibus/overrides.rb +69 -0
- data/lib/omnibus/project.rb +566 -0
- data/lib/omnibus/reports.rb +99 -0
- data/lib/omnibus/s3_cacher.rb +136 -0
- data/lib/omnibus/software.rb +430 -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/build_version_spec.rb +228 -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/overrides_spec.rb +114 -0
- data/spec/software_spec.rb +71 -0
- data/spec/spec_helper.rb +28 -0
- metadata +239 -0
@@ -0,0 +1,184 @@
|
|
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 'pp'
|
19
|
+
|
20
|
+
module Omnibus
|
21
|
+
|
22
|
+
# Base class for classes that fetch project sources from the internet.
|
23
|
+
#
|
24
|
+
# @abstract Subclass and override the {#clean}, {#description},
|
25
|
+
# {#fetch}, {#fetch_required?}, and {#version_guid} methods
|
26
|
+
#
|
27
|
+
# @todo Is this class supposed to be abstract or not? Pretty sure
|
28
|
+
# it's supposed to be abstract
|
29
|
+
class Fetcher
|
30
|
+
|
31
|
+
# Given an error and a fetcher that generated the error, print a
|
32
|
+
# formatted report of the error and stacktrace, along with fetcher
|
33
|
+
# details to stderr.
|
34
|
+
#
|
35
|
+
# @note Does not rethrow the error; that must currently be done manually.
|
36
|
+
#
|
37
|
+
# @todo Since this is always called from within a fetcher, and
|
38
|
+
# since the fetcher always passes itself in in the {#initialize}
|
39
|
+
# method, this really ought to be encapsulated in a method call on
|
40
|
+
# {Omnibus::Fetcher}.
|
41
|
+
# @todo Also, since we always 'raise' after calling {#explain}, we
|
42
|
+
# should just go ahead and exit from here. No need to raise,
|
43
|
+
# since we're already outputting the real error and stacktrace
|
44
|
+
# here.
|
45
|
+
class ErrorReporter
|
46
|
+
|
47
|
+
def initialize(error, fetcher)
|
48
|
+
@error, @fetcher = error, fetcher
|
49
|
+
end
|
50
|
+
|
51
|
+
# @todo Why not just make an attribute for error?
|
52
|
+
#
|
53
|
+
# @todo And for that matter, why not make an attribute for the
|
54
|
+
# fetcher as well? Or why not just use `@error` like we use
|
55
|
+
# `@fetcher`?
|
56
|
+
def e
|
57
|
+
@error
|
58
|
+
end
|
59
|
+
|
60
|
+
# @todo If {Omnibus::Fetcher#description} is meant to show
|
61
|
+
# parameters (presumably the kind of fetcher and the software it
|
62
|
+
# is fetching?),
|
63
|
+
def explain(why)
|
64
|
+
$stderr.puts "* " * 40
|
65
|
+
$stderr.puts why
|
66
|
+
$stderr.puts "Fetcher params:"
|
67
|
+
$stderr.puts indent(@fetcher.description, 2)
|
68
|
+
$stderr.puts "Exception:"
|
69
|
+
$stderr.puts indent("#{e.class}: #{e.message.strip}", 2)
|
70
|
+
Array(e.backtrace).each {|l| $stderr.puts indent(l, 4) }
|
71
|
+
$stderr.puts "* " * 40
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# Indent each line of a string with `n` spaces.
|
77
|
+
#
|
78
|
+
# Splits the string at `\n` characters and then pads the left
|
79
|
+
# side with `n` spaces. Rejoins them all again with `\n`.
|
80
|
+
#
|
81
|
+
# @param string [String] the string to indent
|
82
|
+
# @param n [Fixnum] the number of " " characters to indent each line.
|
83
|
+
# @return [String]
|
84
|
+
def indent(string, n)
|
85
|
+
string.split("\n").map {|l| " ".rjust(n) << l }.join("\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
class UnsupportedSourceLocation < ArgumentError
|
91
|
+
end
|
92
|
+
|
93
|
+
NULL_ARG = Object.new
|
94
|
+
|
95
|
+
# Returns an implementation of {Fetcher} that can retrieve the
|
96
|
+
# given software.
|
97
|
+
#
|
98
|
+
# @param software [Omnibus::Software] the software the Fetcher should fetch
|
99
|
+
# @return [Omnibus::Fetcher]
|
100
|
+
def self.for(software)
|
101
|
+
if software.source
|
102
|
+
if software.source[:url] && Omnibus.config.use_s3_caching
|
103
|
+
S3CacheFetcher.new(software)
|
104
|
+
else
|
105
|
+
without_caching_for(software)
|
106
|
+
end
|
107
|
+
else
|
108
|
+
Fetcher.new(software)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# @param software [Omnibus::Software] the software to fetch
|
113
|
+
# @raise [UnsupportedSourceLocation] if the software's source is not
|
114
|
+
# one of `:url`, `:git`, or `:path`
|
115
|
+
# @see Omnibus::Software#source
|
116
|
+
# @todo Define an enumeration of the acceptable software types
|
117
|
+
# @todo This probably ought to be folded into {#for} method above.
|
118
|
+
# It looks like this is called explicitly in
|
119
|
+
# {Omnibus::S3Cache#fetch}, but that could be handled by having a
|
120
|
+
# second optional parameter that always disables caching.
|
121
|
+
# @todo Since the software determines what fetcher must be used to
|
122
|
+
# fetch it, perhaps this should be a method on {Omnibus::Software}
|
123
|
+
# instead.
|
124
|
+
def self.without_caching_for(software)
|
125
|
+
if software.source[:url]
|
126
|
+
NetFetcher.new(software)
|
127
|
+
elsif software.source[:git]
|
128
|
+
GitFetcher.new(software)
|
129
|
+
elsif software.source[:path]
|
130
|
+
PathFetcher.new(software)
|
131
|
+
else
|
132
|
+
raise UnsupportedSourceLocation, "Don't know how to fetch software project #{software}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.name(name=NULL_ARG)
|
137
|
+
@name = name unless name.equal?(NULL_ARG)
|
138
|
+
@name
|
139
|
+
end
|
140
|
+
|
141
|
+
attr_reader :name
|
142
|
+
|
143
|
+
# @todo What is this? It doesn't appear to be used anywhere
|
144
|
+
attr_reader :source_timefile
|
145
|
+
|
146
|
+
def initialize(software)
|
147
|
+
end
|
148
|
+
|
149
|
+
def log(message)
|
150
|
+
puts "[fetcher:#{self.class.name}::#{name}] #{message}"
|
151
|
+
end
|
152
|
+
|
153
|
+
# @!group Methods for Subclasses to Implement
|
154
|
+
|
155
|
+
# @todo All extenders of this class override this method. Since
|
156
|
+
# this class appears to be intended as an abstract one, this
|
157
|
+
# should raise a NotImplementedError
|
158
|
+
def description
|
159
|
+
# Not as pretty as we'd like, but it's a sane default:
|
160
|
+
inspect
|
161
|
+
end
|
162
|
+
|
163
|
+
# @todo All extenders of this class override this method. Since
|
164
|
+
# this class appears to be intended as an abstract one, this
|
165
|
+
# should raise a NotImplementedError
|
166
|
+
def fetch_required?
|
167
|
+
false
|
168
|
+
end
|
169
|
+
|
170
|
+
# @todo Empty method is very suspicious; raise NotImplementedError instead.
|
171
|
+
def clean
|
172
|
+
end
|
173
|
+
|
174
|
+
# @todo Empty method is very suspicious; raise NotImplementedError instead.
|
175
|
+
def fetch
|
176
|
+
end
|
177
|
+
|
178
|
+
# @todo Empty method is very suspicious; raise NotImplementedError instead.
|
179
|
+
def version_guid
|
180
|
+
end
|
181
|
+
|
182
|
+
# !@endgroup
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,22 @@
|
|
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/fetcher'
|
19
|
+
require 'omnibus/fetchers/net_fetcher'
|
20
|
+
require 'omnibus/fetchers/git_fetcher'
|
21
|
+
require 'omnibus/fetchers/path_fetcher'
|
22
|
+
require 'omnibus/fetchers/s3_cache_fetcher'
|
@@ -0,0 +1,212 @@
|
|
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
|
+
module Omnibus
|
19
|
+
|
20
|
+
# Fetcher implementation for projects in git.
|
21
|
+
class GitFetcher < Fetcher
|
22
|
+
|
23
|
+
name :git
|
24
|
+
|
25
|
+
attr_reader :source
|
26
|
+
attr_reader :project_dir
|
27
|
+
attr_reader :version
|
28
|
+
|
29
|
+
def initialize(software)
|
30
|
+
@name = software.name
|
31
|
+
@source = software.source
|
32
|
+
@version = software.version
|
33
|
+
@project_dir = software.project_dir
|
34
|
+
end
|
35
|
+
|
36
|
+
def description
|
37
|
+
s=<<-E
|
38
|
+
repo URI: #{@source[:git]}
|
39
|
+
local location: #{@project_dir}
|
40
|
+
E
|
41
|
+
end
|
42
|
+
|
43
|
+
def version_guid
|
44
|
+
"git:#{current_revision}".chomp
|
45
|
+
rescue
|
46
|
+
end
|
47
|
+
|
48
|
+
def clean
|
49
|
+
if existing_git_clone?
|
50
|
+
log "cleaning existing build"
|
51
|
+
clean_cmd = "git clean -fdx"
|
52
|
+
shell = Mixlib::ShellOut.new(clean_cmd, :live_stream => STDOUT, :cwd => project_dir)
|
53
|
+
shell.run_command
|
54
|
+
shell.error!
|
55
|
+
end
|
56
|
+
rescue Exception => e
|
57
|
+
ErrorReporter.new(e, self).explain("Failed to clean git repository '#{@source[:git]}'")
|
58
|
+
raise
|
59
|
+
end
|
60
|
+
|
61
|
+
def fetch_required?
|
62
|
+
!existing_git_clone? || !current_rev_matches_target_rev?
|
63
|
+
end
|
64
|
+
|
65
|
+
def fetch
|
66
|
+
retries ||= 0
|
67
|
+
if existing_git_clone?
|
68
|
+
fetch_updates unless current_rev_matches_target_rev?
|
69
|
+
else
|
70
|
+
clone
|
71
|
+
checkout
|
72
|
+
end
|
73
|
+
rescue Exception => e
|
74
|
+
if retries >= 3
|
75
|
+
ErrorReporter.new(e, self).explain("Failed to fetch git repository '#{@source[:git]}'")
|
76
|
+
raise
|
77
|
+
else
|
78
|
+
# Deal with github failing all the time :(
|
79
|
+
time_to_sleep = 5 * (2 ** retries)
|
80
|
+
retries += 1
|
81
|
+
log "git clone/fetch failed for #{@source} #{retries} time(s), retrying in #{time_to_sleep}s"
|
82
|
+
sleep(time_to_sleep)
|
83
|
+
retry
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def clone
|
90
|
+
puts "cloning the source from git"
|
91
|
+
clone_cmd = "git clone #{@source[:git]} #{project_dir}"
|
92
|
+
shell = Mixlib::ShellOut.new(clone_cmd, :live_stream => STDOUT)
|
93
|
+
shell.run_command
|
94
|
+
shell.error!
|
95
|
+
end
|
96
|
+
|
97
|
+
def checkout
|
98
|
+
sha_ref = target_revision
|
99
|
+
|
100
|
+
checkout_cmd = "git checkout #{sha_ref}"
|
101
|
+
shell = Mixlib::ShellOut.new(checkout_cmd, :live_stream => STDOUT, :cwd => project_dir)
|
102
|
+
shell.run_command
|
103
|
+
shell.error!
|
104
|
+
end
|
105
|
+
|
106
|
+
def fetch_updates
|
107
|
+
puts "fetching updates and resetting to revision #{target_revision}"
|
108
|
+
fetch_cmd = "git fetch origin && git fetch origin --tags && git reset --hard #{target_revision}"
|
109
|
+
shell = Mixlib::ShellOut.new(fetch_cmd, :live_stream => STDOUT, :cwd => project_dir)
|
110
|
+
shell.run_command
|
111
|
+
shell.error!
|
112
|
+
end
|
113
|
+
|
114
|
+
def existing_git_clone?
|
115
|
+
File.exist?("#{project_dir}/.git")
|
116
|
+
end
|
117
|
+
|
118
|
+
def current_rev_matches_target_rev?
|
119
|
+
current_revision && current_revision.strip.to_i(16) == target_revision.strip.to_i(16)
|
120
|
+
end
|
121
|
+
|
122
|
+
def current_revision
|
123
|
+
@current_rev ||= begin
|
124
|
+
rev_cmd = "git rev-parse HEAD"
|
125
|
+
shell = Mixlib::ShellOut.new(rev_cmd, :live_stream => STDOUT, :cwd => project_dir)
|
126
|
+
shell.run_command
|
127
|
+
shell.error!
|
128
|
+
output = shell.stdout
|
129
|
+
|
130
|
+
sha_hash?(output) ? output : nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def target_revision
|
135
|
+
@target_rev ||= begin
|
136
|
+
if sha_hash?(version)
|
137
|
+
version
|
138
|
+
else
|
139
|
+
revision_from_remote_reference(version)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def sha_hash?(rev)
|
145
|
+
rev =~ /^[0-9a-f]{40}$/
|
146
|
+
end
|
147
|
+
|
148
|
+
# Return the SHA corresponding to ref. If ref is an annotated tag,
|
149
|
+
# return the SHA that was tagged not the SHA of the tag itself.
|
150
|
+
def revision_from_remote_reference(ref)
|
151
|
+
retries ||= 0
|
152
|
+
# execute `git ls-remote` the trailing '*' does globbing. This
|
153
|
+
# allows us to return the SHA of the tagged commit for annotated
|
154
|
+
# tags. We take care to only return exact matches in
|
155
|
+
# process_remote_list.
|
156
|
+
cmd = "git ls-remote origin #{ref}*"
|
157
|
+
shell = Mixlib::ShellOut.new(cmd, :live_stream => STDOUT, :cwd => project_dir)
|
158
|
+
shell.run_command
|
159
|
+
shell.error!
|
160
|
+
commit_ref = process_remote_list(shell.stdout, ref)
|
161
|
+
if !commit_ref
|
162
|
+
raise "Could not parse SHA reference"
|
163
|
+
end
|
164
|
+
commit_ref
|
165
|
+
rescue Exception => e
|
166
|
+
if retries >= 3
|
167
|
+
ErrorReporter.new(e, self).explain("Failed to fetch git repository '#{@source[:git]}'")
|
168
|
+
raise
|
169
|
+
else
|
170
|
+
# Deal with github failing all the time :(
|
171
|
+
time_to_sleep = 5 * (2 ** retries)
|
172
|
+
retries += 1
|
173
|
+
log "git ls-remote failed for #{@source} #{retries} time(s), retrying in #{time_to_sleep}s"
|
174
|
+
sleep(time_to_sleep)
|
175
|
+
retry
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def process_remote_list(stdout, ref)
|
180
|
+
# Dereference annotated tags.
|
181
|
+
#
|
182
|
+
# Output will look like this:
|
183
|
+
#
|
184
|
+
# a2ed66c01f42514bcab77fd628149eccb4ecee28 refs/tags/rel-0.11.0
|
185
|
+
# f915286abdbc1907878376cce9222ac0b08b12b8 refs/tags/rel-0.11.0^{}
|
186
|
+
#
|
187
|
+
# The SHA with ^{} is the commit pointed to by an annotated
|
188
|
+
# tag. If ref isn't an annotated tag, there will not be a line
|
189
|
+
# with trailing ^{}.
|
190
|
+
#
|
191
|
+
# We'll return the SHA corresponding to the ^{} which is the
|
192
|
+
# commit pointed to by an annotated tag. If no such commit
|
193
|
+
# exists (not an annotated tag) then we return the SHA of the
|
194
|
+
# ref. If nothing matches, return "".
|
195
|
+
lines = stdout.split("\n")
|
196
|
+
matches = lines.map { |line| line.split("\t") }
|
197
|
+
# first try for ^{} indicating the commit pointed to by an
|
198
|
+
# annotated tag
|
199
|
+
tagged_commit = matches.find { |m| m[1].end_with?("#{ref}^{}") }
|
200
|
+
if tagged_commit
|
201
|
+
tagged_commit[0]
|
202
|
+
else
|
203
|
+
found = matches.find { |m| m[1].end_with?("#{ref}") }
|
204
|
+
if found
|
205
|
+
found[0]
|
206
|
+
else
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,191 @@
|
|
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
|
+
module Omnibus
|
19
|
+
|
20
|
+
class UnsupportedURIScheme < ArgumentError
|
21
|
+
end
|
22
|
+
|
23
|
+
class InvalidSourceFile < RuntimeError
|
24
|
+
end
|
25
|
+
|
26
|
+
# Fetcher Implementation for HTTP and FTP hosted tarballs
|
27
|
+
class NetFetcher < Fetcher
|
28
|
+
|
29
|
+
name :net
|
30
|
+
|
31
|
+
attr_reader :name
|
32
|
+
attr_reader :project_file
|
33
|
+
attr_reader :source
|
34
|
+
attr_reader :source_uri
|
35
|
+
attr_reader :source_dir
|
36
|
+
attr_reader :project_dir
|
37
|
+
|
38
|
+
def initialize(software)
|
39
|
+
@name = software.name
|
40
|
+
@checksum = software.checksum
|
41
|
+
@source = software.source
|
42
|
+
@project_file = software.project_file
|
43
|
+
@source_uri = software.source_uri
|
44
|
+
@source_dir = software.source_dir
|
45
|
+
@project_dir = software.project_dir
|
46
|
+
end
|
47
|
+
|
48
|
+
def description
|
49
|
+
s=<<-E
|
50
|
+
source URI: #{source_uri}
|
51
|
+
checksum: #{@checksum}
|
52
|
+
local location: #@project_file
|
53
|
+
E
|
54
|
+
end
|
55
|
+
|
56
|
+
def version_guid
|
57
|
+
"md5:#{@checksum}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def fetch_required?
|
61
|
+
!File.exists?(project_file) || Digest::MD5.file(project_file) != @checksum
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def clean
|
66
|
+
if File.exists?(project_dir)
|
67
|
+
log "cleaning existing build from #{project_dir}"
|
68
|
+
FileUtils.rm_rf(project_dir)
|
69
|
+
end
|
70
|
+
extract
|
71
|
+
end
|
72
|
+
|
73
|
+
def fetch
|
74
|
+
if fetch_required?
|
75
|
+
download
|
76
|
+
verify_checksum!
|
77
|
+
else
|
78
|
+
log "Cached copy of source tarball up to date"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_with_redirect(url, headers, limit = 10)
|
83
|
+
raise ArgumentError, 'HTTP redirect too deep' if limit == 0
|
84
|
+
log "getting from #{url} with #{limit} redirects left"
|
85
|
+
|
86
|
+
if !url.kind_of?(URI)
|
87
|
+
url = URI.parse(url)
|
88
|
+
end
|
89
|
+
|
90
|
+
req = Net::HTTP::Get.new(url.request_uri, headers)
|
91
|
+
http_client = Net::HTTP.new(url.host, url.port)
|
92
|
+
http_client.use_ssl = (url.scheme == "https")
|
93
|
+
|
94
|
+
response = http_client.start { |http| http.request(req) }
|
95
|
+
case response
|
96
|
+
when Net::HTTPSuccess
|
97
|
+
open(project_file, "wb") do |f|
|
98
|
+
f.write(response.body)
|
99
|
+
end
|
100
|
+
when Net::HTTPRedirection
|
101
|
+
get_with_redirect(response['location'], headers, limit - 1)
|
102
|
+
else
|
103
|
+
response.error!
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def download
|
108
|
+
tries = 5
|
109
|
+
begin
|
110
|
+
log "\033[1;31m#{source[:warning]}\033[0m" if source.has_key?(:warning)
|
111
|
+
log "fetching #{project_file} from #{source_uri}"
|
112
|
+
|
113
|
+
case source_uri.scheme
|
114
|
+
when /https?/
|
115
|
+
headers = {
|
116
|
+
'accept-encoding' => '',
|
117
|
+
}
|
118
|
+
if source.has_key?(:cookie)
|
119
|
+
headers['Cookie'] = source[:cookie]
|
120
|
+
end
|
121
|
+
get_with_redirect(source_uri, headers)
|
122
|
+
when "ftp"
|
123
|
+
Net::FTP.open(source_uri.host) do |ftp|
|
124
|
+
ftp.passive = true
|
125
|
+
ftp.login
|
126
|
+
ftp.getbinaryfile(source_uri.path, project_file)
|
127
|
+
ftp.close
|
128
|
+
end
|
129
|
+
else
|
130
|
+
raise UnsupportedURIScheme, "Don't know how to download from #{source_uri}"
|
131
|
+
end
|
132
|
+
rescue Exception => e
|
133
|
+
if ( tries -= 1 ) != 0
|
134
|
+
log "retrying failed download..."
|
135
|
+
retry
|
136
|
+
else
|
137
|
+
raise
|
138
|
+
end
|
139
|
+
end
|
140
|
+
rescue Exception => e
|
141
|
+
ErrorReporter.new(e, self).explain("Failed to fetch source from #source_uri (#{e.class}: #{e.message.strip})")
|
142
|
+
raise
|
143
|
+
end
|
144
|
+
|
145
|
+
def verify_checksum!
|
146
|
+
actual_md5 = Digest::MD5.file(project_file)
|
147
|
+
unless actual_md5 == @checksum
|
148
|
+
log "Invalid MD5 for #@name"
|
149
|
+
log "Expected: #{@checksum}"
|
150
|
+
log "Actual: #{actual_md5}"
|
151
|
+
raise InvalidSourceFile, "Checksum of downloaded file #{project_file} doesn't match expected"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def extract
|
156
|
+
log "extracting the source in #{project_file} to #{source_dir}"
|
157
|
+
cmd = extract_cmd
|
158
|
+
case cmd
|
159
|
+
when Proc
|
160
|
+
cmd.call
|
161
|
+
when String
|
162
|
+
shell = Mixlib::ShellOut.new(cmd, :live_stream => STDOUT)
|
163
|
+
shell.run_command
|
164
|
+
shell.error!
|
165
|
+
else
|
166
|
+
raise "Don't know how to extract command for #{cmd.class} class"
|
167
|
+
end
|
168
|
+
rescue Exception => e
|
169
|
+
ErrorReporter.new(e, self).explain("Failed to unpack archive at #{project_file} (#{e.class}: #{e.message.strip})")
|
170
|
+
raise
|
171
|
+
end
|
172
|
+
|
173
|
+
def extract_cmd
|
174
|
+
if project_file.end_with?(".gz") || project_file.end_with?(".tgz")
|
175
|
+
"gzip -dc #{project_file} | ( cd #{source_dir} && tar -xf - )"
|
176
|
+
elsif project_file.end_with?(".bz2")
|
177
|
+
"bzip2 -dc #{project_file} | ( cd #{source_dir} && tar -xf - )"
|
178
|
+
elsif project_file.end_with?(".7z")
|
179
|
+
"7z.exe x #{project_file} -o#{source_dir} -r -y"
|
180
|
+
else
|
181
|
+
#if we don't recognize the extension, simply copy over the file
|
182
|
+
Proc.new do
|
183
|
+
log "#{project_file} not an archive. Copying to #{project_dir}"
|
184
|
+
# hack hack hack, no project dir yet
|
185
|
+
FileUtils.mkdir_p(project_dir)
|
186
|
+
FileUtils.cp(project_file, project_dir)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|