fpm-fry 0.2.2 → 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,32 +4,67 @@ require 'net/http'
4
4
  require 'forwardable'
5
5
  require 'zlib'
6
6
  require 'fpm/fry/source'
7
+ require 'fpm/fry/exec'
7
8
  require 'cabin'
9
+ require 'cabin/channel'
10
+
8
11
  module FPM; module Fry ; module Source
9
- class Package
12
+ # Used to build from an archive.
13
+ #
14
+ # @example in a recipe
15
+ # source 'http://curl.haxx.se/download/curl-7.36.0.tar.gz',
16
+ # checksum: '33015795d5650a2bfdd9a4a28ce4317cef944722a5cfca0d1563db8479840e90'
17
+ #
18
+ # It is highly advised to supply a checksum ( althought it's not mandatory ).
19
+ # This checksum will be used to test for cache validity and data integrity. The
20
+ # checksum algorithm is automatically guessed based on the length of the checksum.
21
+ #
22
+ # - 40 characters = sha1
23
+ # - 64 characters = sha256
24
+ #
25
+ # Let's be honest: all other checksum algorithms aren't or shouldn't be in use anyway.
26
+ class Archive
10
27
 
11
28
  REGEX = %r!\Ahttps?:!
12
29
 
30
+ # @return [:archive]
13
31
  def self.name
14
32
  :package
15
33
  end
16
34
 
17
35
  def self.aliases
18
- [:http]
36
+ [:package,:http]
19
37
  end
20
38
 
39
+ # Guesses if the given url is an archive.
40
+ #
41
+ # @example not an archive
42
+ # FPM::Fry::Source::Archive.guess("bzr://something") # => nil
43
+ #
44
+ # @example an archive
45
+ # FPM::Fry::Source::Archive.guess("https://some/thing.tar.gz") #=> 6
46
+ #
47
+ # @return [nil] when it's not an archive
48
+ # @return [Numeric] number of characters used
21
49
  def self.guess( url )
22
50
  Source::guess_regex(REGEX, url)
23
51
  end
24
52
 
53
+ # Raised when too many redirects happened.
25
54
  class RedirectError < CacheFailed
26
55
  end
27
56
 
57
+ # Raised when the archive type is not known.
58
+ class UnknownArchiveType < StandardError
59
+ include WithData
60
+ end
61
+
28
62
  class Cache < Struct.new(:package,:tempdir)
29
63
  extend Forwardable
30
64
 
31
- def_delegators :package, :url, :checksum, :checksum_algorithm, :agent, :logger, :file_map
65
+ def_delegators :package, :url, :checksum, :checksum_algorithm, :logger, :file_map, :to
32
66
 
67
+ # @return [String] cachekey which is equal to the checksum
33
68
  def cachekey
34
69
  @observed_checksum || checksum
35
70
  end
@@ -92,7 +127,7 @@ module FPM; module Fry ; module Source
92
127
  case(resp)
93
128
  when Net::HTTPRedirection
94
129
  if redirs == 0
95
- raise RedirectError, "Too many redirects"
130
+ raise RedirectError.new("Too many redirects", url: url.to_s, location: resp['location'])
96
131
  end
97
132
  logger.debug("Following redirect", url: url.to_s , location: resp['location'])
98
133
  return fetch_url( resp['location'], redirs - 1, &block)
@@ -119,9 +154,33 @@ module FPM; module Fry ; module Source
119
154
 
120
155
  def copy_to(dst)
121
156
  update!
122
- cmd = ['tar','-xf',tempfile,'-C',dst]
123
- logger.debug("Running tar",cmd: cmd)
124
- system(*cmd)
157
+ Exec['tar','-xf',tempfile,'-C',dst, logger: logger]
158
+ end
159
+
160
+ def prefix
161
+ update!
162
+ @prefix ||= prefix!
163
+ end
164
+
165
+ def prefix!
166
+ longest = nil
167
+ Exec.popen('tar','-tf',tempfile, logger: logger).each_line.map do |line|
168
+ line = line.chomp
169
+ parts = line.split('/')
170
+ parts.pop unless line[-1] == '/'
171
+ if longest.nil?
172
+ longest = parts
173
+ else
174
+ longest.each_with_index do | e, i |
175
+ if parts[i] != e
176
+ longest = longest[0...i]
177
+ break
178
+ end
179
+ end
180
+ break if longest.none?
181
+ end
182
+ end
183
+ return Array(longest).join('/')
125
184
  end
126
185
 
127
186
  protected
@@ -142,9 +201,7 @@ module FPM; module Fry ; module Source
142
201
 
143
202
  def tar_io
144
203
  update!
145
- cmd = ['bzcat',tempfile]
146
- logger.debug("Running bzcat",cmd: cmd)
147
- return IO.popen(cmd)
204
+ return Exec::popen('bzcat', tempfile, logger: logger)
148
205
  end
149
206
 
150
207
  end
@@ -152,6 +209,22 @@ module FPM; module Fry ; module Source
152
209
  class ZipCache < Cache
153
210
 
154
211
  def tar_io
212
+ unpack!
213
+ return Exec::popen('tar','-c','.', chdir: unpacked_tmpdir)
214
+ end
215
+
216
+ def copy_to(dst)
217
+ update!
218
+ Exec['unzip', tempfile, '-d', dst ]
219
+ end
220
+
221
+ def prefix
222
+ unpack!
223
+ Source::prefix(unpacked_tmpdir)
224
+ end
225
+ private
226
+
227
+ def unpack!
155
228
  if !::File.directory?( unpacked_tmpdir )
156
229
  workdir = unpacked_tmpdir + '.tmp'
157
230
  begin
@@ -163,18 +236,6 @@ module FPM; module Fry ; module Source
163
236
  copy_to( workdir )
164
237
  File.rename(workdir, unpacked_tmpdir)
165
238
  end
166
- cmd = ['tar','-c','.']
167
- logger.debug("Running tar",cmd: cmd, dir: unpacked_tmpdir)
168
- ::Dir.chdir(unpacked_tmpdir) do
169
- return IO.popen(cmd)
170
- end
171
- end
172
-
173
- def copy_to(dst)
174
- update!
175
- cmd = ['unzip', tempfile, '-d', dst ]
176
- logger.debug("Running unzip",cmd: cmd)
177
- system(*cmd, out: '/dev/null')
178
239
  end
179
240
 
180
241
  def unpacked_tmpdir
@@ -186,12 +247,8 @@ module FPM; module Fry ; module Source
186
247
 
187
248
  def tar_io
188
249
  update!
189
- cmd = ['tar','-c',::File.basename(tempfile)]
190
250
  dir = File.dirname(tempfile)
191
- logger.debug("Running tar",cmd: cmd, dir: dir)
192
- ::Dir.chdir(dir) do
193
- return IO.popen(cmd)
194
- end
251
+ Exec::popen('tar','-c',::File.basename(tempfile), logger: logger, chdir: dir)
195
252
  end
196
253
 
197
254
  def copy_to(dst)
@@ -199,6 +256,10 @@ module FPM; module Fry ; module Source
199
256
  FileUtils.cp( tempfile, dst )
200
257
  end
201
258
 
259
+ def prefix
260
+ ""
261
+ end
262
+
202
263
  end
203
264
 
204
265
  CACHE_CLASSES = {
@@ -211,26 +272,45 @@ module FPM; module Fry ; module Source
211
272
  '.bundle' => PlainCache
212
273
  }
213
274
 
214
- attr :file_map, :data, :url, :extension, :checksum, :checksum_algorithm, :agent, :logger
275
+ attr :file_map, :data, :url, :checksum, :checksum_algorithm, :logger, :to
215
276
 
277
+ # @param [URI] url
278
+ # @param [Hash] options
279
+ # @option options [Cabin::Channel] :logger (default cabin channel)
280
+ # @option options [String] :checksum a checksum of the archive
281
+ # @raise [UnknownArchiveType] when the archive type is unknown
216
282
  def initialize( url, options = {} )
217
283
  @url = URI(url)
218
- @extension = options.fetch(:extension){
219
- CACHE_CLASSES.keys.find{|ext|
220
- @url.path.end_with?(ext)
221
- }
222
- }
284
+ @cache_class = guess_cache_class(@url)
223
285
  @logger = options.fetch(:logger){ Cabin::Channel.get }
224
286
  @checksum = options[:checksum]
225
287
  @checksum_algorithm = guess_checksum_algorithm(options[:checksum])
226
- @file_map = options.fetch(:file_map){ {'' => ''} }
288
+ @file_map = options[:file_map]
289
+ @to = options[:to]
227
290
  end
228
291
 
292
+ # Creates a cache.
293
+ #
294
+ # @param [String] tempdir
295
+ # @return [TarCache] for plain .tar files
296
+ # @return [TarGzCache] for .tar.gz files
297
+ # @return [TarBz2Cache] for .tar.bz2 files
298
+ # @return [ZipCache] for .zip files
299
+ # @return [PlainCache] for .bin files
229
300
  def build_cache(tempdir)
230
- CACHE_CLASSES.fetch(extension).new(self, tempdir)
301
+ @cache_class.new(self, tempdir)
231
302
  end
232
303
  private
233
304
 
305
+ def guess_cache_class( url )
306
+ CACHE_CLASSES.each do |ext,klass|
307
+ if url.path.end_with?(ext)
308
+ return klass
309
+ end
310
+ end
311
+ raise UnknownArchiveType.new("Unknown archive type", url: url.to_s, known_extensions: CACHE_CLASSES.keys)
312
+ end
313
+
234
314
  def guess_checksum_algorithm( checksum )
235
315
  case(checksum)
236
316
  when nil
@@ -1,6 +1,10 @@
1
1
  require 'fpm/fry/source'
2
+ require 'fpm/fry/exec'
2
3
  require 'fileutils'
3
4
  require 'digest'
5
+ require 'cabin/channel'
6
+ require 'fpm/fry/tar'
7
+
4
8
  module FPM; module Fry ; module Source
5
9
  class Dir
6
10
 
@@ -17,14 +21,10 @@ module FPM; module Fry ; module Source
17
21
  class Cache < Struct.new(:package, :dir)
18
22
  extend Forwardable
19
23
 
20
- def_delegators :package, :url, :logger, :file_map
24
+ def_delegators :package, :url, :logger, :file_map, :to
21
25
 
22
26
  def tar_io
23
- cmd = ['tar','-c','.']
24
- logger.debug("Running tar",cmd: cmd, dir: dir)
25
- ::Dir.chdir(dir) do
26
- return IO.popen(cmd)
27
- end
27
+ Exec::popen('tar','-c','.', chdir: dir, logger: logger)
28
28
  end
29
29
 
30
30
  def copy_to(dst)
@@ -39,9 +39,13 @@ module FPM; module Fry ; module Source
39
39
  end
40
40
  return dig.hexdigest
41
41
  end
42
+
43
+ def prefix
44
+ Source::prefix(dir)
45
+ end
42
46
  end
43
47
 
44
- attr :url, :logger, :file_map
48
+ attr :url, :logger, :file_map, :to
45
49
 
46
50
  def initialize( url, options = {} )
47
51
  @url = URI(url)
@@ -49,7 +53,8 @@ module FPM; module Fry ; module Source
49
53
  @url.path = File.expand_path(@url.path)
50
54
  end
51
55
  @logger = options.fetch(:logger){ Cabin::Channel.get }
52
- @file_map = options.fetch(:file_map){ {'' => ''} }
56
+ @file_map = options[:file_map]
57
+ @to = options[:to]
53
58
  end
54
59
 
55
60
  def build_cache(_)
@@ -1,16 +1,41 @@
1
1
  require 'fileutils'
2
2
  require 'forwardable'
3
- require 'open3'
3
+ require 'fpm/fry/exec'
4
4
  require 'fpm/fry/source'
5
5
  module FPM; module Fry ; module Source
6
+ # Used to build directly from git.
7
+ #
8
+ # @example in a recipe
9
+ # source 'https://github.com/ggreer/the_silver_searcher.git'
10
+ #
11
+ # It automatically recognizes the following url patterns:
12
+ #
13
+ # - git://…
14
+ # - git+…://…
15
+ # - user@host:….git
16
+ # - https://….git
17
+ # - https://git.…
18
+ #
6
19
  class Git
7
20
 
8
- REGEX = %r!\A(?:git:|\S+@\S+:\S+\.git\z|https?:.*\.git\z|ssh:.*\.git\z)!
21
+ REGEX = %r!\A(?:git:|\S+@\S+:\S+\.git\z|https?:(?://git\.|.*\.git\z)|ssh:.*\.git\z|git\+[a-z0-9]+:)!
9
22
 
23
+ # @return [:git]
10
24
  def self.name
11
25
  :git
12
26
  end
13
27
 
28
+ # Guesses if this url is a git url.
29
+ #
30
+ # @example not a git url
31
+ # FPM::Fry::Source::Git.guess( "bzr://something" ) #=> nil
32
+ #
33
+ # @example a git url
34
+ # FPM::Fry::Source::Git.guess( "git://something" ) #=> 4
35
+ #
36
+ # @param [URI,String] url
37
+ # @return [nil] when this uri doesn't match
38
+ # @return [Numeric] number of characters that were used
14
39
  def self.guess( url )
15
40
  Source::guess_regex(REGEX, url)
16
41
  end
@@ -18,76 +43,87 @@ module FPM; module Fry ; module Source
18
43
  class Cache < Struct.new(:package, :tempdir)
19
44
  extend Forwardable
20
45
 
21
- def_delegators :package, :url, :rev, :logger, :file_map
22
-
23
- def update
24
- begin
25
- if !File.exists? repodir
26
- if (ecode = git('init', '--bare')) != 0
27
- raise CacheFailed.new("Initializing git repository failed", exit_code: ecode)
28
- end
29
- end
30
- if (ecode = git('fetch','--depth=1', url.to_s, rev)) != 0
31
- raise CacheFailed.new("Failed to fetch from remote", exit_code: ecode, url: url.to_s, rev: rev)
32
- end
33
- return self
34
- rescue Errno::ENOENT
35
- raise "Cannot find git binary. Is it installed?"
36
- end
37
- end
46
+ def_delegators :package, :url, :rev, :logger, :file_map, :to
38
47
 
39
48
  def tar_io
40
- cmd = [package.git, "--git-dir=#{repodir}",'archive','--format=tar','FETCH_HEAD']
41
- logger.debug("Running git",cmd: cmd)
42
- IO.popen(cmd)
49
+ Exec::popen(package.git, "--git-dir=#{repodir}",'archive','--format=tar','FETCH_HEAD', logger: logger)
43
50
  end
44
51
 
45
52
  def copy_to(dst)
46
- cmd = [package.git, "--git-dir=#{repodir}", "--work-tree=#{dst}",'checkout','FETCH_HEAD','--','*']
47
- logger.debug("Running git",cmd: cmd)
48
- system(*cmd, chdir: dst)
53
+ Exec[
54
+ package.git, "--git-dir=#{repodir}", "--work-tree=#{dst}",'checkout','FETCH_HEAD','--','*',
55
+ chdir: dst, logger: logger
56
+ ]
49
57
  end
50
58
 
51
59
  def cachekey
52
- cmd = [package.git, "--git-dir=#{repodir}",'rev-parse','FETCH_HEAD^{tree}']
53
- logger.debug("Running git",cmd: cmd)
54
- return IO.popen(cmd).read.chomp
60
+ Exec::exec(package.git, "--git-dir=#{repodir}",'rev-parse','FETCH_HEAD^{tree}', logger: logger).chomp
61
+ end
62
+
63
+ def prefix
64
+ ""
55
65
  end
66
+
56
67
  private
57
- def repodir
58
- File.join(tempdir,File.basename(url.path))
68
+ def initialize(*_)
69
+ super
70
+ update
59
71
  end
60
72
 
61
- def git(*args)
62
- cmd = [package.git, "--git-dir=#{repodir}",*args]
63
- logger.debug("Running git",cmd: cmd.join(' '))
64
- Open3.popen3(*cmd) do |sin, out, err, thr|
65
- sin.close
66
- out.each_line do |line|
67
- logger.debug(line.chomp)
68
- end
69
- err.each_line do |line|
70
- logger.debug(line.chomp)
73
+ def update
74
+ begin
75
+ if !File.exists? repodir
76
+ Exec::exec(package.git, "--git-dir=#{repodir}",'init', '--bare', description: "initializing git repository", logger: logger)
71
77
  end
72
- return thr.value.exitstatus
78
+ Exec::exec(package.git, "--git-dir=#{repodir}",'fetch','--depth=1', url.to_s, rev, description: 'fetching from remote', logger: logger)
79
+ return self
80
+ rescue => e
81
+ raise CacheFailed.new(e, url: url.to_s, rev: rev)
73
82
  end
74
83
  end
75
-
84
+ def repodir
85
+ File.join(tempdir,File.basename(url.path))
86
+ end
76
87
  end
77
88
 
78
- attr :logger, :git, :rev, :file_map, :url
89
+ # @return [Cabin::Channel] logger
90
+ attr :logger
91
+
92
+ # @return [String] the git binary (default: "git")
93
+ attr :git
94
+
95
+ # @return [String] the git rev to pull (default "HEAD")
96
+ attr :rev
97
+
98
+ # @return [Hash<String,String>,nil] the file map for generating a docker file
99
+ attr :file_map
100
+
101
+ # @return [URI] the uri to pull from
102
+ attr :url
103
+
104
+ # @return [String,nil]
105
+ attr :to
79
106
 
107
+ # @param [URI] url the url to pull from
108
+ # @param [Hash] options
109
+ # @option options [Cabin::Channel] :logger (cabin default channel)
110
+ # @option options [String] :branch git branch to pull
111
+ # @option options [String] :tag git tag to pull
112
+ # @option options [Hash<String,String>] :file_map ({""=>""}) the file map to create the docker file from
80
113
  def initialize( url, options = {} )
81
114
  url = url.sub(/\A(\S+@\S+):(\S+\.git)\z/,'ssh://\1/\2')
82
115
  @url = URI(url)
83
116
  @logger = options.fetch(:logger){ Cabin::Channel.get }
84
117
  @rev = options[:branch] || options[:tag] || options[:rev] || 'HEAD'
85
- @file_map = options.fetch(:file_map){ {'' => ''} }
118
+ @file_map = options[:file_map]
86
119
  @git = options[:git] || 'git'
120
+ @to = options[:to]
87
121
  end
88
122
 
123
+ # @param [String] tempdir
124
+ # @return [Cache]
89
125
  def build_cache(tempdir)
90
- Cache.new(self, tempdir).update
126
+ Cache.new(self, tempdir)
91
127
  end
92
128
 
93
129
  end