vanagon 0.3.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +13 -0
- data/README.md +175 -0
- data/bin/build +33 -0
- data/bin/devkit +22 -0
- data/bin/repo +26 -0
- data/bin/ship +15 -0
- data/lib/vanagon.rb +8 -0
- data/lib/vanagon/common.rb +2 -0
- data/lib/vanagon/common/pathname.rb +87 -0
- data/lib/vanagon/common/user.rb +25 -0
- data/lib/vanagon/component.rb +157 -0
- data/lib/vanagon/component/dsl.rb +307 -0
- data/lib/vanagon/component/source.rb +66 -0
- data/lib/vanagon/component/source/git.rb +60 -0
- data/lib/vanagon/component/source/http.rb +158 -0
- data/lib/vanagon/driver.rb +112 -0
- data/lib/vanagon/engine/base.rb +82 -0
- data/lib/vanagon/engine/docker.rb +40 -0
- data/lib/vanagon/engine/local.rb +40 -0
- data/lib/vanagon/engine/pooler.rb +85 -0
- data/lib/vanagon/errors.rb +28 -0
- data/lib/vanagon/extensions/string.rb +11 -0
- data/lib/vanagon/optparse.rb +62 -0
- data/lib/vanagon/platform.rb +245 -0
- data/lib/vanagon/platform/deb.rb +71 -0
- data/lib/vanagon/platform/dsl.rb +293 -0
- data/lib/vanagon/platform/osx.rb +100 -0
- data/lib/vanagon/platform/rpm.rb +76 -0
- data/lib/vanagon/platform/rpm/wrl.rb +39 -0
- data/lib/vanagon/platform/solaris_10.rb +182 -0
- data/lib/vanagon/platform/solaris_11.rb +138 -0
- data/lib/vanagon/platform/swix.rb +35 -0
- data/lib/vanagon/project.rb +251 -0
- data/lib/vanagon/project/dsl.rb +218 -0
- data/lib/vanagon/utilities.rb +299 -0
- data/spec/fixures/component/invalid-test-fixture.json +3 -0
- data/spec/fixures/component/mcollective.service +1 -0
- data/spec/fixures/component/test-fixture.json +4 -0
- data/spec/lib/vanagon/common/pathname_spec.rb +103 -0
- data/spec/lib/vanagon/common/user_spec.rb +36 -0
- data/spec/lib/vanagon/component/dsl_spec.rb +443 -0
- data/spec/lib/vanagon/component/source/git_spec.rb +19 -0
- data/spec/lib/vanagon/component/source/http_spec.rb +43 -0
- data/spec/lib/vanagon/component/source_spec.rb +99 -0
- data/spec/lib/vanagon/component_spec.rb +22 -0
- data/spec/lib/vanagon/engine/base_spec.rb +40 -0
- data/spec/lib/vanagon/engine/docker_spec.rb +40 -0
- data/spec/lib/vanagon/engine/pooler_spec.rb +54 -0
- data/spec/lib/vanagon/platform/deb_spec.rb +60 -0
- data/spec/lib/vanagon/platform/dsl_spec.rb +128 -0
- data/spec/lib/vanagon/platform/rpm_spec.rb +41 -0
- data/spec/lib/vanagon/platform/solaris_11_spec.rb +44 -0
- data/spec/lib/vanagon/platform_spec.rb +53 -0
- data/spec/lib/vanagon/project/dsl_spec.rb +203 -0
- data/spec/lib/vanagon/project_spec.rb +44 -0
- data/spec/lib/vanagon/utilities_spec.rb +140 -0
- data/templates/Makefile.erb +116 -0
- data/templates/deb/changelog.erb +5 -0
- data/templates/deb/conffiles.erb +3 -0
- data/templates/deb/control.erb +21 -0
- data/templates/deb/dirs.erb +3 -0
- data/templates/deb/docs.erb +1 -0
- data/templates/deb/install.erb +3 -0
- data/templates/deb/postinst.erb +46 -0
- data/templates/deb/postrm.erb +15 -0
- data/templates/deb/prerm.erb +17 -0
- data/templates/deb/rules.erb +25 -0
- data/templates/osx/postinstall.erb +24 -0
- data/templates/osx/preinstall.erb +19 -0
- data/templates/osx/project-installer.xml.erb +19 -0
- data/templates/rpm/project.spec.erb +217 -0
- data/templates/solaris/10/depend.erb +3 -0
- data/templates/solaris/10/pkginfo.erb +13 -0
- data/templates/solaris/10/postinstall.erb +37 -0
- data/templates/solaris/10/preinstall.erb +7 -0
- data/templates/solaris/10/preremove.erb +6 -0
- data/templates/solaris/10/proto.erb +5 -0
- data/templates/solaris/11/p5m.erb +73 -0
- metadata +172 -0
@@ -0,0 +1,307 @@
|
|
1
|
+
require 'vanagon/component'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class Vanagon
|
6
|
+
class Component
|
7
|
+
class DSL
|
8
|
+
# Constructor for the DSL object
|
9
|
+
#
|
10
|
+
# @param name [String] name of the component
|
11
|
+
# @param settings [Hash] settings to use in building the component
|
12
|
+
# @param platform [Vanagon::Platform] platform to build the component for
|
13
|
+
# @return [Vanagon::Component::DSL] A DSL object to describe the {Vanagon::Component}
|
14
|
+
def initialize(name, settings, platform)
|
15
|
+
@name = name
|
16
|
+
@component = Vanagon::Component.new(@name, settings, platform)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Primary way of interacting with the DSL
|
20
|
+
#
|
21
|
+
# @param name [String] name of the componennt
|
22
|
+
# @param block [Proc] DSL definition of the component to call
|
23
|
+
def component(name, &block)
|
24
|
+
block.call(self, @component.settings, @component.platform)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Accessor for the component.
|
28
|
+
#
|
29
|
+
# @return [Vanagon::Component] the component the DSL methods will be acting against
|
30
|
+
def _component
|
31
|
+
@component
|
32
|
+
end
|
33
|
+
|
34
|
+
# All purpose getter. This object, which is passed to the component block,
|
35
|
+
# won't have easy access to the attributes of the @component, so we make a
|
36
|
+
# getter for each attribute.
|
37
|
+
#
|
38
|
+
# We only magically handle get_ methods, any other methods just get the
|
39
|
+
# standard method_missing treatment.
|
40
|
+
#
|
41
|
+
def method_missing(method, *args)
|
42
|
+
attribute_match = method.to_s.match(/get_(.*)/)
|
43
|
+
if attribute_match
|
44
|
+
attribute = attribute_match.captures.first
|
45
|
+
else
|
46
|
+
super
|
47
|
+
end
|
48
|
+
|
49
|
+
@component.send(attribute)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Set or add to the configure call for the component. The commands required to configure the component before building it.
|
53
|
+
#
|
54
|
+
# @param block [Proc] the command(s) required to configure the component
|
55
|
+
def configure(&block)
|
56
|
+
@component.configure << block.call
|
57
|
+
end
|
58
|
+
|
59
|
+
# Set or add to the build call for the component. The commands required to build the component before installing it.
|
60
|
+
#
|
61
|
+
# @param block [Proc] the command(s) required to build the component
|
62
|
+
def build(&block)
|
63
|
+
@component.build << block.call
|
64
|
+
end
|
65
|
+
|
66
|
+
# Set or add to the install call for the component. The commands required to install the component.
|
67
|
+
#
|
68
|
+
# @param block [Proc] the command(s) required to install the component
|
69
|
+
def install(&block)
|
70
|
+
@component.install << block.call
|
71
|
+
end
|
72
|
+
|
73
|
+
# Setup any specific environment required to configure, build or install the component
|
74
|
+
#
|
75
|
+
# @param block [Proc] the environment required to configure, build or install the component
|
76
|
+
def environment(&block)
|
77
|
+
@component.environment = block.call
|
78
|
+
end
|
79
|
+
|
80
|
+
# Add a patch to the list of patches to apply to the component's source after unpacking
|
81
|
+
#
|
82
|
+
# @param patch [String] Path to the patch that should be applied
|
83
|
+
# @param strip [String, Integer] directory levels to skip in applying patch
|
84
|
+
# @param fuzz [String, Integer] levels of context miss to ignore in applying patch
|
85
|
+
def apply_patch(patch, strip: 1, fuzz: 0)
|
86
|
+
@component.patches << OpenStruct.new('path' => patch, 'strip' => strip.to_s, 'fuzz' => fuzz.to_s)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Loads and parses json from a file. Will treat the keys in the
|
90
|
+
# json as methods to invoke on the component in question
|
91
|
+
#
|
92
|
+
# @param file [String] Path to the json file
|
93
|
+
# @raise [RuntimeError] exceptions are raised if there is no file, if it refers to methods that don't exist, or if it does not contain a Hash
|
94
|
+
def load_from_json(file)
|
95
|
+
if File.exists?(file)
|
96
|
+
data = JSON.parse(File.read(file))
|
97
|
+
raise "Hash required. Got '#{data.class}' when parsing '#{file}'" unless data.is_a?(Hash)
|
98
|
+
data.each do |key, value|
|
99
|
+
if self.respond_to?(key)
|
100
|
+
self.send(key, value)
|
101
|
+
else
|
102
|
+
fail "Component does not have a '#{key}' method to invoke. Maybe your bespoke json has a typo?"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
else
|
106
|
+
fail "Cannot load component data from '#{file}'. It does not exist."
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# build_requires adds a requirements to the list of build time dependencies
|
111
|
+
# that will need to be fetched from an external source before this component
|
112
|
+
# can be built. build_requires can also be satisfied by other components in
|
113
|
+
# the same project.
|
114
|
+
#
|
115
|
+
# @param build_requirement [String] a library or other component that is required to build the current component
|
116
|
+
def build_requires(build_requirement)
|
117
|
+
@component.build_requires << build_requirement
|
118
|
+
end
|
119
|
+
|
120
|
+
# requires adds a requirement to the list of runtime requirements for the
|
121
|
+
# component
|
122
|
+
#
|
123
|
+
# @param requirement [String] a package that is required at runtime for this component
|
124
|
+
def requires(requirement)
|
125
|
+
@component.requires << requirement
|
126
|
+
end
|
127
|
+
|
128
|
+
# Indicates that this component replaces a system level package. Replaces can be collected and used by the project and package.
|
129
|
+
#
|
130
|
+
# @param replacement [String] a package that is replaced with this component
|
131
|
+
# @param version [String] the version of the package that is replaced
|
132
|
+
def replaces(replacement, version = nil)
|
133
|
+
@component.replaces << OpenStruct.new(:replacement => replacement, :version => version)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Indicates that this component provides a system level package. Provides can be collected and used by the project and package.
|
137
|
+
#
|
138
|
+
# @param provide [String] a package that is provided with this component
|
139
|
+
# @param version [String] the version of the package that is provided with this component
|
140
|
+
def provides(provide, version = nil)
|
141
|
+
@component.provides << OpenStruct.new(:provide => provide, :version => version)
|
142
|
+
end
|
143
|
+
|
144
|
+
# install_service adds the commands to install the various files on
|
145
|
+
# disk during the package build and registers the service with the project
|
146
|
+
#
|
147
|
+
# @param service_file [String] path to the service file relative to the source
|
148
|
+
# @param default_file [String] path to the default file relative to the source
|
149
|
+
# @param service_name [String] name of the service
|
150
|
+
# @param service_type [String] type of the service (network, application, system, etc)
|
151
|
+
def install_service(service_file, default_file = nil, service_name = @component.name, service_type: nil)
|
152
|
+
case @component.platform.servicetype
|
153
|
+
when "sysv"
|
154
|
+
target_service_file = File.join(@component.platform.servicedir, service_name)
|
155
|
+
target_default_file = File.join(@component.platform.defaultdir, service_name)
|
156
|
+
target_mode = '0755'
|
157
|
+
default_mode = '0644'
|
158
|
+
when "systemd"
|
159
|
+
target_service_file = File.join(@component.platform.servicedir, "#{service_name}.service")
|
160
|
+
target_default_file = File.join(@component.platform.defaultdir, service_name)
|
161
|
+
target_mode = '0644'
|
162
|
+
default_mode = '0644'
|
163
|
+
when "launchd"
|
164
|
+
target_service_file = File.join(@component.platform.servicedir, "#{service_name}.plist")
|
165
|
+
target_mode = '0644'
|
166
|
+
default_mode = '0644'
|
167
|
+
when "smf"
|
168
|
+
target_service_file = File.join(@component.platform.servicedir, service_type.to_s, "#{service_name}.xml")
|
169
|
+
target_default_file = File.join(@component.platform.defaultdir, service_name)
|
170
|
+
target_mode = '0644'
|
171
|
+
default_mode = '0755'
|
172
|
+
when "aix"
|
173
|
+
@component.service = OpenStruct.new(:name => service_name, :service_command => File.read(service_file).chomp)
|
174
|
+
# Return here because there is no file to install, just a string read in
|
175
|
+
return
|
176
|
+
else
|
177
|
+
fail "Don't know how to install the #{@component.platform.servicetype}. Please teach #install_service how to do this."
|
178
|
+
end
|
179
|
+
|
180
|
+
# Install the service and default files
|
181
|
+
install_file(service_file, target_service_file, mode: target_mode)
|
182
|
+
|
183
|
+
if default_file
|
184
|
+
install_file(default_file, target_default_file, mode: default_mode)
|
185
|
+
configfile target_default_file
|
186
|
+
end
|
187
|
+
|
188
|
+
# Register the service for use in packaging
|
189
|
+
@component.service = OpenStruct.new(:name => service_name, :service_file => target_service_file)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Copies a file from source to target during the install phase of the component
|
193
|
+
#
|
194
|
+
# @param source [String] path to the file to copy
|
195
|
+
# @param target [String] path to the desired target of the file
|
196
|
+
# @param owner [String] owner of the file
|
197
|
+
# @param group [String] group owner of the file
|
198
|
+
def install_file(source, target, mode: '0644', owner: nil, group: nil)
|
199
|
+
@component.install << "#{@component.platform.install} -d '#{File.dirname(target)}'"
|
200
|
+
@component.install << "cp -p '#{source}' '#{target}'"
|
201
|
+
@component.add_file Vanagon::Common::Pathname.file(target, mode: mode, owner: owner, group: group)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Marks a file as a configfile to ensure that it is not overwritten on
|
205
|
+
# upgrade if it has been modified
|
206
|
+
#
|
207
|
+
# @param file [String] name of the configfile
|
208
|
+
def configfile(file)
|
209
|
+
# I AM SO SORRY
|
210
|
+
@component.delete_file "#{file}"
|
211
|
+
if @component.platform.name =~ /solaris-10|osx/
|
212
|
+
@component.install << "mv '#{file}' '#{file}.pristine'"
|
213
|
+
@component.add_file Vanagon::Common::Pathname.configfile("#{file}.pristine")
|
214
|
+
else
|
215
|
+
@component.add_file Vanagon::Common::Pathname.configfile(file)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Shorthand to install a file and mark it as a configfile
|
220
|
+
#
|
221
|
+
# @param source [String] path to the configfile to copy
|
222
|
+
# @param target [String] path to the desired target of the configfile
|
223
|
+
def install_configfile(source, target)
|
224
|
+
install_file(source, target)
|
225
|
+
configfile(target)
|
226
|
+
end
|
227
|
+
|
228
|
+
# link will add a command to the install to create a symlink from source to target
|
229
|
+
#
|
230
|
+
# @param source [String] path to the file to symlink
|
231
|
+
# @param target [String] path to the desired symlink
|
232
|
+
def link(source, target)
|
233
|
+
@component.install << "#{@component.platform.install} -d '#{File.dirname(target)}'"
|
234
|
+
@component.install << "ln -s '#{source}' '#{target}'"
|
235
|
+
end
|
236
|
+
|
237
|
+
# Sets the version for the component
|
238
|
+
#
|
239
|
+
# @param ver [String] version of the component
|
240
|
+
def version(ver)
|
241
|
+
@component.version = ver
|
242
|
+
end
|
243
|
+
|
244
|
+
# Sets the url for the source of this component
|
245
|
+
#
|
246
|
+
# @param the_url [String] the url to the source for this component
|
247
|
+
def url(the_url)
|
248
|
+
@component.url = the_url
|
249
|
+
end
|
250
|
+
|
251
|
+
# Sets the md5 sum to verify the sum of the source
|
252
|
+
#
|
253
|
+
# @param md5 [String] md5 sum of the source for verification
|
254
|
+
def md5sum(md5)
|
255
|
+
@component.options[:sum] = md5
|
256
|
+
end
|
257
|
+
|
258
|
+
# Sets the ref of the source for use in a git source
|
259
|
+
#
|
260
|
+
# @param the_ref [String] ref, sha, branch or tag to checkout for a git source
|
261
|
+
def ref(the_ref)
|
262
|
+
@component.options[:ref] = the_ref
|
263
|
+
end
|
264
|
+
|
265
|
+
# This will add a source to the project and put it in the workdir alongside the other sources
|
266
|
+
#
|
267
|
+
# @param url [String] url of the source
|
268
|
+
# @param ref [String] Used for git sources, must be a git ref of some sort
|
269
|
+
# @param sum [String] sum used to validate http and file sources
|
270
|
+
def add_source(url, ref: nil, sum: nil)
|
271
|
+
@component.sources << OpenStruct.new(:url => url, :ref => ref, :sum => sum)
|
272
|
+
end
|
273
|
+
|
274
|
+
# Adds a directory to the list of directories provided by the project, to be included in any packages of the project
|
275
|
+
#
|
276
|
+
# @param dir [String] directory to add to the project
|
277
|
+
# @param mode [String] octal mode to apply to the directory
|
278
|
+
# @param owner [String] owner of the directory
|
279
|
+
# @param group [String] group of the directory
|
280
|
+
def directory(dir, mode: nil, owner: nil, group: nil)
|
281
|
+
@component.directories << Vanagon::Common::Pathname.new(dir, mode: mode, owner: owner, group: group)
|
282
|
+
end
|
283
|
+
|
284
|
+
# Adds a set of environment overrides to the environment for a component.
|
285
|
+
# This environment is included in the configure, build and install steps.
|
286
|
+
#
|
287
|
+
# @param env [Hash] mapping of keys to values to add to the environment for the component
|
288
|
+
def environment(env)
|
289
|
+
@component.environment.merge!(env)
|
290
|
+
end
|
291
|
+
|
292
|
+
# Adds actions to run at the beginning of a package install
|
293
|
+
#
|
294
|
+
# @param action [String] Bourne shell compatible scriptlets to execute
|
295
|
+
def add_preinstall_action(action)
|
296
|
+
@component.preinstall_actions << action
|
297
|
+
end
|
298
|
+
|
299
|
+
# Adds actions to run at the end of a package install
|
300
|
+
#
|
301
|
+
# @param action [String] Bourne shell compatible scriptlets to execute
|
302
|
+
def add_postinstall_action(action)
|
303
|
+
@component.postinstall_actions << action
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'vanagon/component/source/http'
|
2
|
+
require 'vanagon/component/source/git'
|
3
|
+
|
4
|
+
class Vanagon
|
5
|
+
class Component
|
6
|
+
class Source
|
7
|
+
SUPPORTED_PROTOCOLS = ['file', 'http', 'git']
|
8
|
+
@@rewrite_rule = {}
|
9
|
+
|
10
|
+
def self.register_rewrite_rule(protocol, rule)
|
11
|
+
if rule.is_a?(String) or rule.is_a?(Proc)
|
12
|
+
if SUPPORTED_PROTOCOLS.include?(protocol)
|
13
|
+
@@rewrite_rule[protocol] = rule
|
14
|
+
else
|
15
|
+
raise Vanagon::Error, "#{protocol} is not a supported protocol for rewriting"
|
16
|
+
end
|
17
|
+
else
|
18
|
+
raise Vanagon::Error, "String or Proc is required as a rewrite_rule."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.rewrite(url, protocol)
|
23
|
+
rule = @@rewrite_rule[protocol]
|
24
|
+
if rule
|
25
|
+
if rule.is_a?(Proc) and rule.arity == 1
|
26
|
+
return rule.call(url)
|
27
|
+
elsif rule.is_a?(String)
|
28
|
+
target_match = url.match(/.*\/([^\/]*)$/)
|
29
|
+
if target_match
|
30
|
+
target = target_match[1]
|
31
|
+
return File.join(rule, target)
|
32
|
+
else
|
33
|
+
raise Vanagon::Error, "Unable to apply url rewrite to '#{url}', expected to find at least one '/' in the url."
|
34
|
+
end
|
35
|
+
else
|
36
|
+
end
|
37
|
+
else
|
38
|
+
return url
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Basic factory to hand back the correct {Vanagon::Component::Source} subtype to the component
|
43
|
+
#
|
44
|
+
# @param url [String] URL to the source (includes git@... style links)
|
45
|
+
# @param options [Hash] hash of the options needed for the subtype
|
46
|
+
# @param workdir [String] working directory to fetch the source into
|
47
|
+
# @return [Vanagon::Component::Source] the correct subtype for the given source
|
48
|
+
def self.source(url, options, workdir)
|
49
|
+
url_match = url.match(/^(.*)(@|:\/\/)(.*)$/)
|
50
|
+
uri_scheme = url_match[1] if url_match
|
51
|
+
local_source = case uri_scheme
|
52
|
+
when /^http/
|
53
|
+
Vanagon::Component::Source::Http.new(self.rewrite(url, 'http'), options[:sum], workdir)
|
54
|
+
when /^file/
|
55
|
+
Vanagon::Component::Source::Http.new(self.rewrite(url, 'file'), options[:sum], workdir)
|
56
|
+
when /^git/
|
57
|
+
Vanagon::Component::Source::Git.new(self.rewrite(url, 'git'), options[:ref], workdir)
|
58
|
+
else
|
59
|
+
fail "Don't know how to handle source of type '#{uri_scheme}' from url: '#{url}'"
|
60
|
+
end
|
61
|
+
|
62
|
+
return local_source
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'vanagon/utilities'
|
2
|
+
|
3
|
+
class Vanagon
|
4
|
+
class Component
|
5
|
+
class Source
|
6
|
+
class Git
|
7
|
+
include Vanagon::Utilities
|
8
|
+
attr_accessor :url, :ref, :workdir, :version, :cleanup
|
9
|
+
|
10
|
+
# Constructor for the Git source type
|
11
|
+
#
|
12
|
+
# @param url [String] url of git repo to use as source
|
13
|
+
# @param ref [String] ref to checkout from git repo
|
14
|
+
# @param workdir [String] working directory to clone into
|
15
|
+
def initialize(url, ref, workdir)
|
16
|
+
unless ref
|
17
|
+
fail "ref parameter is required for the git source"
|
18
|
+
end
|
19
|
+
@url = url
|
20
|
+
@ref = ref
|
21
|
+
@workdir = workdir
|
22
|
+
end
|
23
|
+
|
24
|
+
# Fetch the source. In this case, clone the repository into the workdir
|
25
|
+
# and check out the ref. Also sets the version if there is a git tag as
|
26
|
+
# a side effect.
|
27
|
+
def fetch
|
28
|
+
puts "Cloning ref '#{@ref}' from url '#{@url}'"
|
29
|
+
Dir.chdir(@workdir) do
|
30
|
+
git('clone', @url)
|
31
|
+
Dir.chdir(dirname) do
|
32
|
+
git('checkout', @ref)
|
33
|
+
git('submodule', 'update', '--init', '--recursive')
|
34
|
+
@version = git_version
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return the correct incantation to cleanup the source directory for a given source
|
40
|
+
#
|
41
|
+
# @return [String] command to cleanup the source
|
42
|
+
def cleanup
|
43
|
+
"rm -rf #{dirname}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# There is no md5 to manually verify here, so it is a noop.
|
47
|
+
def verify
|
48
|
+
# nothing to do here, so just return
|
49
|
+
end
|
50
|
+
|
51
|
+
# The dirname to reference when building from the repo
|
52
|
+
#
|
53
|
+
# @return [String] the directory where the repo was cloned
|
54
|
+
def dirname
|
55
|
+
File.basename(@url).sub(/\.git/, '')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'vanagon/utilities'
|
2
|
+
require 'net/http'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
class Vanagon
|
6
|
+
class Component
|
7
|
+
class Source
|
8
|
+
class Http
|
9
|
+
include Vanagon::Utilities
|
10
|
+
attr_accessor :url, :sum, :file, :extension, :workdir, :cleanup
|
11
|
+
|
12
|
+
# Extensions for files we intend to unpack during the build
|
13
|
+
ARCHIVE_EXTENSIONS = '.tar.gz', '.tgz'
|
14
|
+
|
15
|
+
# Extensions for files we aren't going to unpack during the build
|
16
|
+
NON_ARCHIVE_EXTENSIONS = '.gem', '.ru', '.txt', '.conf', '.ini', '.gpg', '.rb', '.sh', '.csh'
|
17
|
+
|
18
|
+
# Constructor for the Http source type
|
19
|
+
#
|
20
|
+
# @param url [String] url of the http source to fetch
|
21
|
+
# @param sum [String] sum to verify the download against
|
22
|
+
# @param workdir [String] working directory to download into
|
23
|
+
# @raise [RuntimeError] an exception is raised is sum is nil
|
24
|
+
def initialize(url, sum, workdir)
|
25
|
+
unless sum
|
26
|
+
fail "sum is required to validate the http source"
|
27
|
+
end
|
28
|
+
@url = url
|
29
|
+
@sum = sum
|
30
|
+
@workdir = workdir
|
31
|
+
end
|
32
|
+
|
33
|
+
# Download the source from the url specified. Sets the full path to the
|
34
|
+
# file as @file and the @extension for the file as a side effect.
|
35
|
+
def fetch
|
36
|
+
@file = download
|
37
|
+
@extension = get_extension
|
38
|
+
end
|
39
|
+
|
40
|
+
# Verify the downloaded file matches the provided sum
|
41
|
+
#
|
42
|
+
# @raise [RuntimeError] an exception is raised if the sum does not match the sum of the file
|
43
|
+
def verify
|
44
|
+
puts "Verifying file: #{@file} against sum: '#{@sum}'"
|
45
|
+
actual = get_md5sum(File.join(@workdir, @file))
|
46
|
+
unless @sum == actual
|
47
|
+
fail "Unable to verify '#{@file}'. Expected: '#{@sum}', got: '#{actual}'"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Downloads the file from @url into the @workdir
|
52
|
+
#
|
53
|
+
# @raise [RuntimeError, Vanagon::Error] an exception is raised if the URI scheme cannot be handled
|
54
|
+
def download
|
55
|
+
uri = URI.parse(@url)
|
56
|
+
target_file = File.basename(uri.path)
|
57
|
+
puts "Downloading file '#{target_file}' from url '#{@url}'"
|
58
|
+
|
59
|
+
case uri.scheme
|
60
|
+
when 'http', 'https'
|
61
|
+
Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
|
62
|
+
request = Net::HTTP::Get.new(uri)
|
63
|
+
|
64
|
+
http.request request do |response|
|
65
|
+
unless response.is_a? Net::HTTPSuccess
|
66
|
+
fail "Error: #{response.code.to_s}. Unable to get source from #{@url}"
|
67
|
+
end
|
68
|
+
open(File.join(@workdir, target_file), 'w') do |io|
|
69
|
+
response.read_body do |chunk|
|
70
|
+
io.write(chunk)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
when 'file'
|
76
|
+
uri = @url.match(/^file:\/\/(.*)$/)
|
77
|
+
if uri
|
78
|
+
source_file = uri[1]
|
79
|
+
target_file = File.basename(source_file)
|
80
|
+
FileUtils.cp(source_file, File.join(@workdir, target_file))
|
81
|
+
else
|
82
|
+
raise Vanagon::Error, "Unable to parse '#{@url}' for local file path."
|
83
|
+
end
|
84
|
+
else
|
85
|
+
fail "Unable to download files using the uri scheme '#{uri.scheme}'. Maybe you have a typo or need to teach me a new trick?"
|
86
|
+
end
|
87
|
+
|
88
|
+
target_file
|
89
|
+
|
90
|
+
rescue Errno::ETIMEDOUT, Timeout::Error, Errno::EINVAL,
|
91
|
+
Errno::EACCES, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse,
|
92
|
+
Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
|
93
|
+
raise Vanagon::Error.wrap(e, "Problem downloading #{target_file} from '#{@url}'. Please verify you have the correct uri specified.")
|
94
|
+
end
|
95
|
+
|
96
|
+
# Gets the command to extract the archive given if needed (uses @extension)
|
97
|
+
#
|
98
|
+
# @param tar [String] the tar command to use
|
99
|
+
# @return [String, nil] command to extract the source
|
100
|
+
# @raise [RuntimeError] an exception is raised if there is no known extraction method for @extension
|
101
|
+
def extract(tar)
|
102
|
+
if ARCHIVE_EXTENSIONS.include?(@extension)
|
103
|
+
return "gunzip -c '#{@file}' | '#{tar}' xf -"
|
104
|
+
elsif NON_ARCHIVE_EXTENSIONS.include?(@extension)
|
105
|
+
# Don't need to unpack gems, ru, txt, conf, ini, gpg
|
106
|
+
return nil
|
107
|
+
else
|
108
|
+
fail "Extraction unimplemented for '#{@extension}' in source '#{@file}'. Please teach me."
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Return the correct incantation to cleanup the source archive and source directory for a given source
|
113
|
+
#
|
114
|
+
# @return [String] command to cleanup the source
|
115
|
+
# @raise [RuntimeError] an exception is raised if there is no known extraction method for @extension
|
116
|
+
def cleanup
|
117
|
+
if ARCHIVE_EXTENSIONS.include?(@extension)
|
118
|
+
return "rm #{@file}; rm -r #{dirname}"
|
119
|
+
elsif NON_ARCHIVE_EXTENSIONS.include?(@extension)
|
120
|
+
# Because dirname will be ./ here, we don't want to try to nuke it
|
121
|
+
return "rm #{@file}"
|
122
|
+
else
|
123
|
+
fail "Don't know how to cleanup for '#{@file}' with extension: '#{@extension}'. Please teach me."
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns the extension for @file
|
128
|
+
#
|
129
|
+
# @return [String] the extension of @file
|
130
|
+
# @raise [RuntimeError] an exception is raised if the extension isn't in the current list
|
131
|
+
def get_extension
|
132
|
+
extension_match = @file.match(/.*(#{Regexp.union(ARCHIVE_EXTENSIONS + NON_ARCHIVE_EXTENSIONS)})/)
|
133
|
+
unless extension_match
|
134
|
+
fail "Unrecognized extension for '#{@file}'. Don't know how to extract this format. Please teach me."
|
135
|
+
end
|
136
|
+
|
137
|
+
extension_match[1]
|
138
|
+
end
|
139
|
+
|
140
|
+
# The dirname to reference when building from the source
|
141
|
+
#
|
142
|
+
# @return [String] the directory that should be traversed into to build this source
|
143
|
+
# @raise [RuntimeError] if the @extension for the @file isn't currently handled by the method
|
144
|
+
def dirname
|
145
|
+
if ARCHIVE_EXTENSIONS.include?(@extension)
|
146
|
+
return @file.chomp(@extension)
|
147
|
+
elsif NON_ARCHIVE_EXTENSIONS.include?(@extension)
|
148
|
+
# Because we cd into the source dir, using ./ here avoids special casing single file
|
149
|
+
# sources in the Makefile
|
150
|
+
return './'
|
151
|
+
else
|
152
|
+
fail "Don't know how to guess dirname for '#{@file}' with extension: '#{@extension}'. Please teach me."
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|