vanagon 0.3.18

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 (80) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +13 -0
  3. data/README.md +175 -0
  4. data/bin/build +33 -0
  5. data/bin/devkit +22 -0
  6. data/bin/repo +26 -0
  7. data/bin/ship +15 -0
  8. data/lib/vanagon.rb +8 -0
  9. data/lib/vanagon/common.rb +2 -0
  10. data/lib/vanagon/common/pathname.rb +87 -0
  11. data/lib/vanagon/common/user.rb +25 -0
  12. data/lib/vanagon/component.rb +157 -0
  13. data/lib/vanagon/component/dsl.rb +307 -0
  14. data/lib/vanagon/component/source.rb +66 -0
  15. data/lib/vanagon/component/source/git.rb +60 -0
  16. data/lib/vanagon/component/source/http.rb +158 -0
  17. data/lib/vanagon/driver.rb +112 -0
  18. data/lib/vanagon/engine/base.rb +82 -0
  19. data/lib/vanagon/engine/docker.rb +40 -0
  20. data/lib/vanagon/engine/local.rb +40 -0
  21. data/lib/vanagon/engine/pooler.rb +85 -0
  22. data/lib/vanagon/errors.rb +28 -0
  23. data/lib/vanagon/extensions/string.rb +11 -0
  24. data/lib/vanagon/optparse.rb +62 -0
  25. data/lib/vanagon/platform.rb +245 -0
  26. data/lib/vanagon/platform/deb.rb +71 -0
  27. data/lib/vanagon/platform/dsl.rb +293 -0
  28. data/lib/vanagon/platform/osx.rb +100 -0
  29. data/lib/vanagon/platform/rpm.rb +76 -0
  30. data/lib/vanagon/platform/rpm/wrl.rb +39 -0
  31. data/lib/vanagon/platform/solaris_10.rb +182 -0
  32. data/lib/vanagon/platform/solaris_11.rb +138 -0
  33. data/lib/vanagon/platform/swix.rb +35 -0
  34. data/lib/vanagon/project.rb +251 -0
  35. data/lib/vanagon/project/dsl.rb +218 -0
  36. data/lib/vanagon/utilities.rb +299 -0
  37. data/spec/fixures/component/invalid-test-fixture.json +3 -0
  38. data/spec/fixures/component/mcollective.service +1 -0
  39. data/spec/fixures/component/test-fixture.json +4 -0
  40. data/spec/lib/vanagon/common/pathname_spec.rb +103 -0
  41. data/spec/lib/vanagon/common/user_spec.rb +36 -0
  42. data/spec/lib/vanagon/component/dsl_spec.rb +443 -0
  43. data/spec/lib/vanagon/component/source/git_spec.rb +19 -0
  44. data/spec/lib/vanagon/component/source/http_spec.rb +43 -0
  45. data/spec/lib/vanagon/component/source_spec.rb +99 -0
  46. data/spec/lib/vanagon/component_spec.rb +22 -0
  47. data/spec/lib/vanagon/engine/base_spec.rb +40 -0
  48. data/spec/lib/vanagon/engine/docker_spec.rb +40 -0
  49. data/spec/lib/vanagon/engine/pooler_spec.rb +54 -0
  50. data/spec/lib/vanagon/platform/deb_spec.rb +60 -0
  51. data/spec/lib/vanagon/platform/dsl_spec.rb +128 -0
  52. data/spec/lib/vanagon/platform/rpm_spec.rb +41 -0
  53. data/spec/lib/vanagon/platform/solaris_11_spec.rb +44 -0
  54. data/spec/lib/vanagon/platform_spec.rb +53 -0
  55. data/spec/lib/vanagon/project/dsl_spec.rb +203 -0
  56. data/spec/lib/vanagon/project_spec.rb +44 -0
  57. data/spec/lib/vanagon/utilities_spec.rb +140 -0
  58. data/templates/Makefile.erb +116 -0
  59. data/templates/deb/changelog.erb +5 -0
  60. data/templates/deb/conffiles.erb +3 -0
  61. data/templates/deb/control.erb +21 -0
  62. data/templates/deb/dirs.erb +3 -0
  63. data/templates/deb/docs.erb +1 -0
  64. data/templates/deb/install.erb +3 -0
  65. data/templates/deb/postinst.erb +46 -0
  66. data/templates/deb/postrm.erb +15 -0
  67. data/templates/deb/prerm.erb +17 -0
  68. data/templates/deb/rules.erb +25 -0
  69. data/templates/osx/postinstall.erb +24 -0
  70. data/templates/osx/preinstall.erb +19 -0
  71. data/templates/osx/project-installer.xml.erb +19 -0
  72. data/templates/rpm/project.spec.erb +217 -0
  73. data/templates/solaris/10/depend.erb +3 -0
  74. data/templates/solaris/10/pkginfo.erb +13 -0
  75. data/templates/solaris/10/postinstall.erb +37 -0
  76. data/templates/solaris/10/preinstall.erb +7 -0
  77. data/templates/solaris/10/preremove.erb +6 -0
  78. data/templates/solaris/10/proto.erb +5 -0
  79. data/templates/solaris/11/p5m.erb +73 -0
  80. 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