fpm-fry 0.3.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: de6bc3a9f29acd7c147a4016c4906fd0a35c6481
4
- data.tar.gz: afece6f8aea44147df5e357015115f9c74614071
3
+ metadata.gz: 2d0c17ec38081829a127eb9388ed2a6fe5f10976
4
+ data.tar.gz: ef89b4472306f950a3f62a48a293d844006883d4
5
5
  SHA512:
6
- metadata.gz: 3fea3ee2114b957eca0789a06faab401d88ff9269bf95ffab42ce79f367716d4b962b964e44aa03ef58087c50c1df5c904195f1ac44cde705bc17c5c0a9d2e63
7
- data.tar.gz: ccee1a75f08a186ff5a44e673d297ca41e2ce6faee2b4df57715542eafbf8640b605e0f7c619958f8b4a7967abd76b8192eadd6e72d5ffcecb07ab1ca27c9949
6
+ metadata.gz: d17bfa8b82c26885c29193d7147e132a25e2be465f88fa33b25f7b7e2a1135a8ca3ffdd461f931e7dc33517e680c82d3010508b8c81f9b90b30355ab6babefce
7
+ data.tar.gz: 2d7533f0c0a145a1f52828e6201d97c2a0d201538c2fc572b12da95ea005736d48cdd63a120f5d43606e9fe5d302cada8926eeffe37632ccf5b75e4320ffc5aa
@@ -10,12 +10,17 @@ module FPM; module Fry
10
10
  end
11
11
 
12
12
  def call(chunk, *_)
13
- json = JSON.parse(chunk)
14
- stream = json['stream']
15
- if /\ASuccessfully built (\w+)\Z/.match(stream)
16
- images << $1
13
+ # new docker for Mac results in data like this:
14
+ # "{'stream':' ---\\u003e 3bc51d6a4c46\\n'}\r\n{'stream':'Step 2 : WORKDIR /tmp/build\\n'}\r\n"
15
+ # this isn't valid JSON, of course, so we process each part individually
16
+ chunk.split("\r\n").each do |sub_chunk|
17
+ json = JSON.parse(sub_chunk)
18
+ stream = json['stream']
19
+ if /\ASuccessfully built (\w+)\Z/.match(stream)
20
+ images << $1
21
+ end
22
+ out << stream
17
23
  end
18
- out << stream
19
24
  end
20
25
 
21
26
  end
@@ -18,7 +18,7 @@ class FPM::Fry::Client
18
18
  include FPM::Fry::WithData
19
19
  end
20
20
 
21
- # Raised when trying to read file that can't be read e.g. because it's a
21
+ # Raised when trying to read file that can't be read e.g. because it's a
22
22
  # directory.
23
23
  class NotAFile < StandardError
24
24
  include FPM::Fry::WithData
@@ -84,12 +84,21 @@ class FPM::Fry::Client
84
84
 
85
85
  def read(name, resource)
86
86
  return to_enum(:read, name, resource) unless block_given?
87
- res = agent.get(
88
- path: url('containers',name,'archive'),
89
- query: {'path' => resource},
90
- headers: { 'Content-Type' => 'application/json' },
91
- expects: [200,404,500]
92
- )
87
+ res = if (server_version['ApiVersion'] < "1.20")
88
+ agent.post(
89
+ path: url('containers', name, 'copy'),
90
+ headers: { 'Content-Type' => 'application/json' },
91
+ body: JSON.generate({'Resource' => resource}),
92
+ expects: [200,404,500]
93
+ )
94
+ else
95
+ agent.get(
96
+ path: url('containers', name, 'archive'),
97
+ headers: { 'Content-Type' => 'application/json' },
98
+ query: {:path => resource},
99
+ expects: [200,404,500]
100
+ )
101
+ end
93
102
  if [404,500].include? res.status
94
103
  body_message = Hash[JSON.load(res.body).map{|k,v| ["docker.#{k}",v] }] rescue {'docker.message' => res.body}
95
104
  body_message['docker.container'] = name
@@ -99,7 +108,7 @@ class FPM::Fry::Client
99
108
  raise FileNotFound.new("file not found", {'path' => resource}.merge(body_message))
100
109
  end
101
110
  sio = StringIO.new(res.body)
102
- tar = ::Gem::Package::TarReader.new( sio )
111
+ tar = FPM::Fry::Tar::Reader.new( sio )
103
112
  tar.each do |entry|
104
113
  yield entry
105
114
  end
@@ -122,6 +122,9 @@ module FPM; module Fry
122
122
  path: client.url("build?rm=1&dockerfile=#{DockerFile::NAME}&t=#{cachetag}"),
123
123
  request_block: BlockEnumerator.new(df.tar_io)
124
124
  )
125
+ else
126
+ # Hack to trigger hints/warnings even when the cache is valid.
127
+ DockerFile::Source.new(builder.variables.merge(image: image_id),cache).send(:file_map)
125
128
  end
126
129
 
127
130
  df = DockerFile::Build.new(cachetag, builder.variables.dup,builder.recipe, update: update?)
@@ -275,6 +278,7 @@ module FPM; module Fry
275
278
 
276
279
  out_map.each do |output, package|
277
280
  package.apply_output(output)
281
+ adjust_package_settings(output)
278
282
  adjust_config_files(output)
279
283
  end
280
284
 
@@ -291,6 +295,12 @@ module FPM; module Fry
291
295
 
292
296
  end
293
297
 
298
+ def adjust_package_settings( output )
299
+ # FPM ignores the file permissions on rpm packages.
300
+ output.attributes[:rpm_use_file_permissions?] = true
301
+ output.attributes[:rpm_user] = 'root'
302
+ output.attributes[:rpm_group] = 'root'
303
+ end
294
304
 
295
305
  def adjust_config_files( output )
296
306
  # FPM flags all files in /etc as config files but only for debian :/.
@@ -13,6 +13,11 @@ module FPM; module Fry
13
13
  def initialize(variables, cache = Source::Null::Cache)
14
14
  variables = variables.dup.freeze
15
15
  super(variables, cache)
16
+ if cache.respond_to? :logger
17
+ @logger = cache.logger
18
+ else
19
+ @logger = Cabin::Channel.get
20
+ end
16
21
  end
17
22
 
18
23
  def dockerfile
@@ -21,8 +26,8 @@ module FPM; module Fry
21
26
 
22
27
  df << "RUN mkdir /tmp/build"
23
28
 
24
- cache.file_map.each do |from, to|
25
- df << "ADD #{map_from(from)} #{map_to(to)}"
29
+ file_map.each do |from, to|
30
+ df << "COPY #{map_from(from)} #{map_to(to)}"
26
31
  end
27
32
 
28
33
  df << ""
@@ -47,6 +52,33 @@ module FPM; module Fry
47
52
  return sio
48
53
  end
49
54
 
55
+ private
56
+
57
+ attr :logger
58
+
59
+ def file_map
60
+ prefix = ""
61
+ to = ""
62
+ if cache.respond_to? :prefix
63
+ prefix = cache.prefix
64
+ end
65
+ if cache.respond_to? :to
66
+ to = cache.to || ""
67
+ end
68
+ fm = cache.file_map
69
+ if fm.nil?
70
+ return { prefix => to }
71
+ end
72
+ if fm.size == 1
73
+ key, value = fm.first
74
+ key = key.gsub(%r!\A\./|/\z!,'')
75
+ if ["",".","./"].include?(value) && key == prefix
76
+ logger.hint("You can remove the file_map: #{fm.inspect} option on source. The given value is the default")
77
+ end
78
+ end
79
+ return fm
80
+ end
81
+
50
82
  def map_to(dir)
51
83
  if ['','.'].include? dir
52
84
  return '/tmp/build'
@@ -78,17 +110,27 @@ module FPM; module Fry
78
110
  end
79
111
 
80
112
  def dockerfile
81
- df = []
82
- df << "FROM #{base}"
83
- df << "WORKDIR /tmp/build"
113
+ df = {
114
+ source: [],
115
+ dependencies: [],
116
+ build: []
117
+ }
118
+ df[:source] << "FROM #{base}"
119
+ workdir = '/tmp/build'
120
+ # TODO: get this from cache, not from the source itself
121
+ if recipe.source.respond_to? :to
122
+ to = recipe.source.to || ""
123
+ workdir = File.expand_path(to, workdir)
124
+ end
125
+ df[:source] << "WORKDIR #{workdir}"
84
126
 
85
127
  # need to add external sources before running any command
86
128
  recipe.build_mounts.each do |source, target|
87
- df << "ADD #{source} /tmp/build/#{target}"
129
+ df[:dependencies] << "COPY #{source} ./#{target}"
88
130
  end
89
131
 
90
- recipe.apt_setup.each do |step|
91
- df << "RUN #{step}"
132
+ recipe.before_dependencies_steps.each do |step|
133
+ df[:dependencies] << "RUN #{step.to_s}"
92
134
  end
93
135
 
94
136
  if build_dependencies.any?
@@ -98,22 +140,22 @@ module FPM; module Fry
98
140
  if options[:update]
99
141
  update = 'apt-get update && '
100
142
  end
101
- df << "RUN #{update}apt-get install --yes #{Shellwords.join(build_dependencies)}"
143
+ df[:dependencies] << "RUN #{update}apt-get install --yes #{Shellwords.join(build_dependencies)}"
102
144
  when 'redhat'
103
- df << "RUN yum -y install #{Shellwords.join(build_dependencies)}"
145
+ df[:dependencies] << "RUN yum -y install #{Shellwords.join(build_dependencies)}"
104
146
  else
105
147
  raise "Unknown flavour: #{variables[:flavour]}"
106
148
  end
107
149
  end
108
150
 
109
151
  recipe.before_build_steps.each do |step|
110
- df << "RUN #{step.to_s}"
152
+ df[:build] << "RUN #{step.to_s}"
111
153
  end
112
154
 
113
- df << "ADD .build.sh /tmp/build/"
114
- df << "ENTRYPOINT /tmp/build/.build.sh"
115
- df << ''
116
- return df.join("\n")
155
+ df[:build] << "COPY .build.sh #{workdir}/"
156
+ df[:build] << "CMD #{workdir}/.build.sh"
157
+ recipe.apply_dockerfile_hooks(df)
158
+ return [*df[:source],*df[:dependencies],*df[:build],""].join("\n")
117
159
  end
118
160
 
119
161
  def build_sh
@@ -0,0 +1,52 @@
1
+ require 'fpm/fry/plugin'
2
+ module FPM::Fry::Plugin ;
3
+
4
+ # Allows adding a debian repository.
5
+ #
6
+ # @note experimental
7
+ #
8
+ # @example in a recipe
9
+ # plugin 'apt' do |apt|
10
+ # apt.repository "https://repo.varnish-cache.org/#{distribution}", "trusty", "varnish-4.1"
11
+ # end
12
+ #
13
+ class Apt
14
+
15
+ # Adds a debian repository
16
+ #
17
+ # @param [String] url
18
+ # @param [String] distribution
19
+ # @param [String,Array<String>] components
20
+ # @param [Hash] options
21
+ def repository(url, distribution, components, options = {} )
22
+ name = "#{url}-#{distribution}".gsub(/[^a-zA-Z0-9_\-]/,'-')
23
+ source = ['deb']
24
+ source << '[trusted=yes]'
25
+ source << url
26
+ source << distribution
27
+ source << Array(components).join(' ')
28
+ @builder.before_dependencies do
29
+ @builder.bash "echo '#{source.join(' ')}' >> /etc/apt/sources.list.d/#{name}.list && apt-get update -o Dir::Etc::sourcelist='sources.list.d/#{name}.list' -o Dir::Etc::sourceparts='-' -o APT::Get::List-Cleanup='0'"
30
+ end
31
+ end
32
+
33
+ def self.apply(builder, &block)
34
+ if builder.flavour != "debian"
35
+ builder.logger.info('skipped apt plugin')
36
+ return
37
+ end
38
+ dsl = self.new(builder)
39
+ if block.arity == 1
40
+ block.call(dsl)
41
+ else
42
+ dsl.instance_eval(&block)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def initialize(builder)
49
+ @builder = builder
50
+ end
51
+
52
+ end ; end
@@ -0,0 +1,45 @@
1
+ require 'fpm/fry/plugin'
2
+ # The env plugin sets global environment variables.
3
+ #
4
+ # @example add something to PATH in a recipe
5
+ # plugin 'env', 'PATH' => '$PATH:/usr/local/go/bin'
6
+ #
7
+ module FPM::Fry::Plugin::Env
8
+
9
+ # @api private
10
+ class AddEnv < Struct.new(:env)
11
+
12
+ def call(_, df)
13
+ df[:source] << format
14
+ end
15
+ private
16
+
17
+ def format
18
+ "ENV " + env.map{|k,v| "#{k}=#{escape(v)}" }.join(" ")
19
+ end
20
+
21
+ def escape(v)
22
+ v.gsub(/([ \n\\])/,'\\\\\\1')
23
+ end
24
+ end
25
+
26
+ def self.apply(builder, env)
27
+ unless env.kind_of? Hash
28
+ raise FPM::Fry::WithData(
29
+ ArgumentError.new("ENV must be a Hash, got #{env.inspect}"),
30
+ documentation: 'https://github.com/xing/fpm-fry/wiki/Plugin-env'
31
+ )
32
+ end
33
+ env.each do |k,v|
34
+ k = k.to_s if k.kind_of? Symbol
35
+ unless k.kind_of?(String) && k =~ /\A[A-Z][A-Z0-9_]*\z/
36
+ raise FPM::Fry::WithData(
37
+ ArgumentError.new("environment variable names must be strings consisiting of uppercase letters, numbers and underscores, got #{k.inspect}"),
38
+ documentation: 'https://github.com/xing/fpm-fry/wiki/Plugin-env'
39
+ )
40
+ end
41
+ end
42
+ builder.recipe.dockerfile_hooks << AddEnv.new(env.dup.freeze)
43
+ end
44
+
45
+ end
@@ -61,9 +61,9 @@ private
61
61
 
62
62
  def self.detect_sysv(inspector)
63
63
  features = {
64
- chkconfig: inspector.exists?('/sbin/chkconfig'),
65
- 'update-rc.d': inspector.exists?('/usr/sbin/update-rc.d'),
66
- 'invoke-rc.d': inspector.exists?('/usr/sbin/invoke-rc.d')
64
+ :chkconfig => inspector.exists?('/sbin/chkconfig'),
65
+ :'update-rc.d' => inspector.exists?('/usr/sbin/update-rc.d'),
66
+ :'invoke-rc.d' => inspector.exists?('/usr/sbin/invoke-rc.d')
67
67
  }
68
68
  return System.new(:sysv,features)
69
69
  end
@@ -0,0 +1,75 @@
1
+ require 'fpm/fry/plugin'
2
+ require 'fpm/fry/chroot'
3
+
4
+ # Automatically adds the appropriate maintainer scripts for every systemd unit.
5
+ #
6
+ # @note experimental
7
+ #
8
+ # @example in a recipe
9
+ # plugin 'systemd' # no options required, just install your units in /lib/systemd/system
10
+ module FPM::Fry::Plugin::Systemd
11
+
12
+ # @api private
13
+ VALID_UNITS = /\A[a-z_0-9\-]+@?\.(service|socket|timer)\z/
14
+
15
+ # @api private
16
+ INSTANTIATED_UNITS = /\A[a-z_0-9\-]+@\.(service|socket|timer)\z/
17
+
18
+ # @api private
19
+ class Callback < Struct.new(:script_helper)
20
+
21
+ def call(_, package)
22
+ chroot = FPM::Fry::Chroot.new(package.staging_path)
23
+ files = chroot.entries('lib/systemd/system') - ['.','..']
24
+ valid, invalid = files.partition{|file| VALID_UNITS =~ file }
25
+ if invalid.any?
26
+ package.logger.warning("Found #{invalid.size} files in systemd unit path that are no systemd units", files: invalid)
27
+ end
28
+ units = valid.grep_v(INSTANTIATED_UNITS)
29
+ return if units.none?
30
+ package.logger.info("Added #{units.size} systemd units", units: valid)
31
+ script_helper.after_install_or_upgrade install(units)
32
+ script_helper.before_remove_entirely before_remove(units)
33
+ script_helper.after_remove_entirely after_remove(units)
34
+ end
35
+
36
+ private
37
+ def install(units)
38
+ <<BASH
39
+ if systemctl is-system-running ; then
40
+ systemctl preset #{units.join(' ')}
41
+ if systemctl is-enabled #{units.join(' ')} ; then
42
+ systemctl daemon-reload
43
+ systemctl restart #{units.join(' ')}
44
+ fi
45
+ fi
46
+ BASH
47
+ end
48
+
49
+ def before_remove(units)
50
+ <<BASH
51
+ if systemctl is-system-running ; then
52
+ systemctl disable --now #{units.join(' ')}
53
+ fi
54
+ BASH
55
+ end
56
+
57
+ def after_remove(units)
58
+ <<BASH
59
+ if systemctl is-system-running ; then
60
+ systemctl daemon-reload
61
+ systemctl reset-failed #{units.join(' ')}
62
+ fi
63
+ BASH
64
+ end
65
+
66
+ end
67
+
68
+ def self.apply(builder)
69
+ return unless builder.plugin('init').systemd?
70
+ builder.plugin('script_helper') do |sh|
71
+ builder.output_hooks << Callback.new(sh)
72
+ end
73
+ end
74
+
75
+ end
@@ -1,6 +1,8 @@
1
1
  require 'fpm/fry/recipe'
2
2
  require 'fpm/fry/recipe/error'
3
3
  require 'forwardable'
4
+ require 'fpm/fry/channel'
5
+
4
6
  module FPM::Fry
5
7
  class Recipe
6
8
 
@@ -29,6 +31,8 @@ module FPM::Fry
29
31
  @inspector = options[:inspector]
30
32
  end
31
33
 
34
+ # Returns the package type ( e.g. "debian" or "redhat" ).
35
+ # @return [String]
32
36
  def flavour
33
37
  variables[:flavour]
34
38
  end
@@ -38,9 +42,13 @@ module FPM::Fry
38
42
  end
39
43
  alias platform distribution
40
44
 
41
- def distribution_version
42
- variables[:distribution_version]
45
+ # The release version of the distribution ( e.g. "12.04" or "6.0.7" )
46
+ # @return [String]
47
+ def release
48
+ variables[:release]
43
49
  end
50
+
51
+ alias distribution_version release
44
52
  alias platform_version distribution_version
45
53
 
46
54
  def codename
@@ -200,7 +208,7 @@ module FPM::Fry
200
208
  variables = variables.dup
201
209
  variables.freeze
202
210
  @recipe = recipe
203
- @before_build = false
211
+ @steps = :steps
204
212
  register_default_source_types!
205
213
  super(variables, recipe.packages[0], options)
206
214
  end
@@ -231,7 +239,9 @@ module FPM::Fry
231
239
  end
232
240
 
233
241
  def apt_setup(cmd)
234
- recipe.apt_setup << cmd
242
+ before_dependencies do
243
+ bash cmd
244
+ end
235
245
  end
236
246
 
237
247
  def run(*args)
@@ -250,7 +260,10 @@ module FPM::Fry
250
260
  code = Recipe::Step.new(name, code)
251
261
  end
252
262
  # Don't do this at home
253
- if @before_build
263
+ case(@steps)
264
+ when :before_dependencies
265
+ recipe.before_dependencies_steps << code
266
+ when :before_build
254
267
  recipe.before_build_steps << code
255
268
  else
256
269
  recipe.steps << code
@@ -258,10 +271,17 @@ module FPM::Fry
258
271
  end
259
272
 
260
273
  def before_build
261
- @before_build = true
274
+ steps, @steps = @steps, :before_build
275
+ yield
276
+ ensure
277
+ @steps = steps
278
+ end
279
+
280
+ def before_dependencies
281
+ steps, @steps = @steps, :before_dependencies
262
282
  yield
263
283
  ensure
264
- @before_build = false
284
+ @steps = steps
265
285
  end
266
286
 
267
287
  def build_depends( name , options = {} )
@@ -67,6 +67,7 @@ module FPM; module Fry
67
67
  # @return [FPM::Package] package
68
68
  # @api private
69
69
  def apply_output( package )
70
+ output_hooks.each{|h| h.call(self, package) }
70
71
  package.name = name
71
72
  package.version = version
72
73
  package.iteration = iteration
@@ -87,7 +88,6 @@ module FPM; module Fry
87
88
  end
88
89
  end
89
90
  end
90
- output_hooks.each{|h| h.call(self, package) }
91
91
  return package
92
92
  end
93
93
 
@@ -134,7 +134,9 @@ module FPM; module Fry
134
134
  attr_accessor :source
135
135
 
136
136
  attr_accessor :build_mounts
137
- attr_accessor :apt_setup
137
+
138
+ # @return [Array<#to_s>] steps that will be carried out before dependencies are installed
139
+ attr_accessor :before_dependencies_steps
138
140
 
139
141
  # @return [Array<#to_s>] steps that will be carried out before build
140
142
  attr_accessor :before_build_steps
@@ -151,16 +153,20 @@ module FPM; module Fry
151
153
  # @return [Array<#call>] hooks that will be called on the input package
152
154
  attr_accessor :input_hooks
153
155
 
156
+ # @return [Array<#call>] hooks that will be called when building the Dockerfile
157
+ attr_accessor :dockerfile_hooks
158
+
154
159
  def initialize
155
160
  @source = Source::Null
161
+ @before_dependencies_steps = []
156
162
  @before_build_steps = []
157
163
  @steps = []
158
164
  @packages = [PackageRecipe.new]
159
165
  @packages[0].files << '**'
160
166
  @build_depends = {}
161
167
  @input_hooks = []
168
+ @dockerfile_hooks = []
162
169
  @build_mounts = []
163
- @apt_setup = []
164
170
  end
165
171
 
166
172
  # Calculates all dependencies of this recipe
@@ -187,6 +193,16 @@ module FPM; module Fry
187
193
  return package
188
194
  end
189
195
 
196
+ # Filters the dockerfile
197
+ # @api experimental
198
+ # @param [Hash] df
199
+ def apply_dockerfile_hooks( df )
200
+ dockerfile_hooks.each do |hook|
201
+ hook.call(self, df)
202
+ end
203
+ return nil
204
+ end
205
+
190
206
  end
191
207
 
192
208
  end ; end
@@ -6,6 +6,8 @@ require 'zlib'
6
6
  require 'fpm/fry/source'
7
7
  require 'fpm/fry/exec'
8
8
  require 'cabin'
9
+ require 'cabin/channel'
10
+
9
11
  module FPM; module Fry ; module Source
10
12
  # Used to build from an archive.
11
13
  #
@@ -60,7 +62,7 @@ module FPM; module Fry ; module Source
60
62
  class Cache < Struct.new(:package,:tempdir)
61
63
  extend Forwardable
62
64
 
63
- def_delegators :package, :url, :checksum, :checksum_algorithm, :logger, :file_map
65
+ def_delegators :package, :url, :checksum, :checksum_algorithm, :logger, :file_map, :to
64
66
 
65
67
  # @return [String] cachekey which is equal to the checksum
66
68
  def cachekey
@@ -155,6 +157,32 @@ module FPM; module Fry ; module Source
155
157
  Exec['tar','-xf',tempfile,'-C',dst, logger: logger]
156
158
  end
157
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('/')
184
+ end
185
+
158
186
  protected
159
187
  def ioclass
160
188
  File
@@ -181,6 +209,22 @@ module FPM; module Fry ; module Source
181
209
  class ZipCache < Cache
182
210
 
183
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!
184
228
  if !::File.directory?( unpacked_tmpdir )
185
229
  workdir = unpacked_tmpdir + '.tmp'
186
230
  begin
@@ -192,14 +236,8 @@ module FPM; module Fry ; module Source
192
236
  copy_to( workdir )
193
237
  File.rename(workdir, unpacked_tmpdir)
194
238
  end
195
- return Exec::popen('tar','-c','.', chdir: unpacked_tmpdir)
196
239
  end
197
240
 
198
- def copy_to(dst)
199
- update!
200
- Exec['unzip', tempfile, '-d', dst ]
201
- end
202
- private
203
241
  def unpacked_tmpdir
204
242
  File.join(tempdir, cachekey)
205
243
  end
@@ -218,6 +256,10 @@ module FPM; module Fry ; module Source
218
256
  FileUtils.cp( tempfile, dst )
219
257
  end
220
258
 
259
+ def prefix
260
+ ""
261
+ end
262
+
221
263
  end
222
264
 
223
265
  CACHE_CLASSES = {
@@ -230,7 +272,7 @@ module FPM; module Fry ; module Source
230
272
  '.bundle' => PlainCache
231
273
  }
232
274
 
233
- attr :file_map, :data, :url, :checksum, :checksum_algorithm, :logger
275
+ attr :file_map, :data, :url, :checksum, :checksum_algorithm, :logger, :to
234
276
 
235
277
  # @param [URI] url
236
278
  # @param [Hash] options
@@ -243,7 +285,8 @@ module FPM; module Fry ; module Source
243
285
  @logger = options.fetch(:logger){ Cabin::Channel.get }
244
286
  @checksum = options[:checksum]
245
287
  @checksum_algorithm = guess_checksum_algorithm(options[:checksum])
246
- @file_map = options.fetch(:file_map){ {'' => ''} }
288
+ @file_map = options[:file_map]
289
+ @to = options[:to]
247
290
  end
248
291
 
249
292
  # Creates a cache.
@@ -2,6 +2,9 @@ require 'fpm/fry/source'
2
2
  require 'fpm/fry/exec'
3
3
  require 'fileutils'
4
4
  require 'digest'
5
+ require 'cabin/channel'
6
+ require 'fpm/fry/tar'
7
+
5
8
  module FPM; module Fry ; module Source
6
9
  class Dir
7
10
 
@@ -18,7 +21,7 @@ module FPM; module Fry ; module Source
18
21
  class Cache < Struct.new(:package, :dir)
19
22
  extend Forwardable
20
23
 
21
- def_delegators :package, :url, :logger, :file_map
24
+ def_delegators :package, :url, :logger, :file_map, :to
22
25
 
23
26
  def tar_io
24
27
  Exec::popen('tar','-c','.', chdir: dir, logger: logger)
@@ -36,9 +39,13 @@ module FPM; module Fry ; module Source
36
39
  end
37
40
  return dig.hexdigest
38
41
  end
42
+
43
+ def prefix
44
+ Source::prefix(dir)
45
+ end
39
46
  end
40
47
 
41
- attr :url, :logger, :file_map
48
+ attr :url, :logger, :file_map, :to
42
49
 
43
50
  def initialize( url, options = {} )
44
51
  @url = URI(url)
@@ -46,7 +53,8 @@ module FPM; module Fry ; module Source
46
53
  @url.path = File.expand_path(@url.path)
47
54
  end
48
55
  @logger = options.fetch(:logger){ Cabin::Channel.get }
49
- @file_map = options.fetch(:file_map){ {'' => ''} }
56
+ @file_map = options[:file_map]
57
+ @to = options[:to]
50
58
  end
51
59
 
52
60
  def build_cache(_)
@@ -43,7 +43,7 @@ module FPM; module Fry ; module Source
43
43
  class Cache < Struct.new(:package, :tempdir)
44
44
  extend Forwardable
45
45
 
46
- def_delegators :package, :url, :rev, :logger, :file_map
46
+ def_delegators :package, :url, :rev, :logger, :file_map, :to
47
47
 
48
48
  def tar_io
49
49
  Exec::popen(package.git, "--git-dir=#{repodir}",'archive','--format=tar','FETCH_HEAD', logger: logger)
@@ -59,6 +59,11 @@ module FPM; module Fry ; module Source
59
59
  def cachekey
60
60
  Exec::exec(package.git, "--git-dir=#{repodir}",'rev-parse','FETCH_HEAD^{tree}', logger: logger).chomp
61
61
  end
62
+
63
+ def prefix
64
+ ""
65
+ end
66
+
62
67
  private
63
68
  def initialize(*_)
64
69
  super
@@ -90,12 +95,15 @@ module FPM; module Fry ; module Source
90
95
  # @return [String] the git rev to pull (default "HEAD")
91
96
  attr :rev
92
97
 
93
- # @return [Hash<String,String>] the file map for generating a docker file
98
+ # @return [Hash<String,String>,nil] the file map for generating a docker file
94
99
  attr :file_map
95
100
 
96
101
  # @return [URI] the uri to pull from
97
102
  attr :url
98
103
 
104
+ # @return [String,nil]
105
+ attr :to
106
+
99
107
  # @param [URI] url the url to pull from
100
108
  # @param [Hash] options
101
109
  # @option options [Cabin::Channel] :logger (cabin default channel)
@@ -107,8 +115,9 @@ module FPM; module Fry ; module Source
107
115
  @url = URI(url)
108
116
  @logger = options.fetch(:logger){ Cabin::Channel.get }
109
117
  @rev = options[:branch] || options[:tag] || options[:rev] || 'HEAD'
110
- @file_map = options.fetch(:file_map){ {'' => ''} }
118
+ @file_map = options[:file_map]
111
119
  @git = options[:git] || 'git'
120
+ @to = options[:to]
112
121
  end
113
122
 
114
123
  # @param [String] tempdir
@@ -8,6 +8,7 @@ module FPM; module Fry ; module Source
8
8
  extend Forwardable
9
9
 
10
10
  def_delegators :package, :logger, :file_map
11
+ def_delegators :inner, :prefix, :to
11
12
 
12
13
  attr :inner
13
14
 
@@ -18,39 +19,64 @@ module FPM; module Fry ; module Source
18
19
  end
19
20
 
20
21
  def update!
21
- @updated ||= begin
22
- if !File.directory?(unpacked_tmpdir)
23
- workdir = unpacked_tmpdir + '.tmp'
22
+ return if @updated
23
+ if !File.directory?(unpacked_tmpdir)
24
+ workdir = unpacked_tmpdir + '.tmp'
25
+ begin
26
+ FileUtils.mkdir(workdir)
27
+ rescue Errno::EEXIST
28
+ FileUtils.rm_rf(workdir)
29
+ FileUtils.mkdir(workdir)
30
+ end
31
+ if inner.respond_to? :copy_to
32
+ inner.copy_to(workdir)
33
+ else
34
+ ex = Tar::Extractor.new(logger: logger)
35
+ tio = inner.tar_io
24
36
  begin
25
- FileUtils.mkdir(workdir)
26
- rescue Errno::EEXIST
27
- FileUtils.rm_rf(workdir)
28
- FileUtils.mkdir(workdir)
37
+ ex.extract(workdir, FPM::Fry::Tar::Reader.new(tio), chown: false)
38
+ ensure
39
+ tio.close
29
40
  end
30
- if inner.respond_to? :copy_to
31
- inner.copy_to(workdir)
32
- else
33
- ex = Tar::Extractor.new(logger: logger)
34
- tio = inner.tar_io
35
- begin
36
- ex.extract(workdir, ::Gem::Package::TarReader.new(tio), chown: false)
37
- ensure
38
- tio.close
41
+ end
42
+ base = workdir
43
+ if inner.respond_to? :prefix
44
+ base = File.expand_path(inner.prefix, base)
45
+ end
46
+ package.patches.each do |patch|
47
+ cmd = ['patch','-p1','-i',patch[:file]]
48
+ chdir = base
49
+ if patch.key? :chdir
50
+ given_chdir = File.expand_path(patch[:chdir],workdir)
51
+ if given_chdir != chdir
52
+ chdir = given_chdir
53
+ else
54
+ logger.hint("You can remove the chdir: #{patch[:chdir].inspect} option for #{patch[:file]}. The given value is the default.", documentation: 'https://github.com/xing/fpm-fry/wiki/Source-patching#chdir' )
39
55
  end
40
56
  end
41
- package.patches.each do |patch|
42
- cmd = ['patch','-p1','-i',patch[:file]]
43
- chdir = File.expand_path(patch.fetch(:chdir,'.'),workdir)
44
- begin
45
- Fry::Exec[*cmd, chdir: chdir, logger: logger]
46
- rescue Exec::Failed => e
47
- raise CacheFailed.new(e, patch: patch[:file])
57
+ begin
58
+ Fry::Exec[*cmd, chdir: chdir, logger: logger]
59
+ rescue Exec::Failed => e
60
+ raise CacheFailed.new(e, patch: patch[:file])
61
+ end
62
+ end
63
+ File.rename(workdir, unpacked_tmpdir)
64
+ else
65
+ #
66
+ base = unpacked_tmpdir
67
+ if inner.respond_to? :prefix
68
+ base = File.expand_path(inner.prefix, base)
69
+ end
70
+ package.patches.each do |patch|
71
+ if patch.key? :chdir
72
+ given_chdir = File.expand_path(patch[:chdir],unpacked_tmpdir)
73
+ if given_chdir == base
74
+ logger.hint("You can remove the chdir: #{patch[:chdir].inspect} option for #{patch[:file]}. The given value is the default.", documentation: 'https://github.com/xing/fpm-fry/wiki/Source-patching#chdir' )
48
75
  end
49
76
  end
50
- File.rename(workdir, unpacked_tmpdir)
51
77
  end
52
- true
53
78
  end
79
+ @updated = true
54
80
  end
55
81
  private :update!
56
82
 
@@ -119,4 +145,3 @@ module FPM; module Fry ; module Source
119
145
  end
120
146
 
121
147
  end ; end ; end
122
-
@@ -26,6 +26,11 @@ module FPM; module Fry ; module Source
26
26
  def self.cachekey
27
27
  return '0' * 32
28
28
  end
29
+
30
+ # @return [String] common path prefix of all files that should be stripped
31
+ def self.prefix
32
+ return ""
33
+ end
29
34
  end
30
35
 
31
36
  # @see FPM::Fry::Source::Null::Cache
@@ -45,5 +50,27 @@ module FPM; module Fry ; module Source
45
50
  end
46
51
  end
47
52
 
53
+ # @api private
54
+ # @param dir [String] directory
55
+ # @return [String] prefix
56
+ def prefix(dir)
57
+ e = ::Dir.entries(dir)
58
+ if e.size != 3
59
+ return ""
60
+ end
61
+ other = (e - ['.','..']).first
62
+ path = File.join(dir, other)
63
+ if File.directory?( path )
64
+ pf = prefix(path)
65
+ if pf == ""
66
+ return other
67
+ else
68
+ return File.join(other, pf)
69
+ end
70
+ else
71
+ return ""
72
+ end
73
+ end
74
+
48
75
  end
49
76
  end ; end ; end
data/lib/fpm/fry/tar.rb CHANGED
@@ -1,7 +1,70 @@
1
+ require "rubygems/package"
2
+ require 'fpm/fry/channel'
3
+
1
4
  module FPM; module Fry; end ; end
2
5
 
3
6
  class FPM::Fry::Tar
4
7
 
8
+ class Reader
9
+ include Enumerable
10
+
11
+ def initialize(io)
12
+ @reader = Gem::Package::TarReader.new(io)
13
+ end
14
+
15
+ def each
16
+ return to_enum(:each) unless block_given?
17
+
18
+ last_pax_header = nil
19
+ @reader.each do |entry|
20
+ if entry.header.typeflag == 'x'
21
+ last_pax_header = extract_pax_header(entry.read)
22
+ else
23
+ if last_pax_header && (path = last_pax_header["path"])
24
+ entry.header.instance_variable_set :@name, path
25
+ last_pax_header = nil
26
+ end
27
+ yield entry
28
+ end
29
+ end
30
+ end
31
+
32
+ def map
33
+ return to_enum(:map) unless block_given?
34
+
35
+ res = []
36
+ each do |entry|
37
+ res << yield(entry)
38
+ end
39
+ res
40
+ end
41
+
42
+ private
43
+
44
+ def extract_pax_header(string)
45
+ res = {}
46
+ s = StringIO.new(string)
47
+ while !s.eof?
48
+ total_len = 0
49
+ prefix_len = 0
50
+ # read number prefix and following blank
51
+ while (c = s.getc) && (c =~ /\d/)
52
+ total_len = 10 * total_len + c.to_i
53
+ prefix_len += 1
54
+ end
55
+ field = s.read(total_len - prefix_len - 2)
56
+ if field =~ /\A([^=]+)=(.+)\z/
57
+ res[$1] = $2
58
+ else
59
+ raise "malformed pax header: #{field}"
60
+ end
61
+ s.read(1) # read trailing newline
62
+ end
63
+ res
64
+ end
65
+
66
+ end
67
+
5
68
  class Extractor
6
69
 
7
70
  def initialize( options = {} )
@@ -1,5 +1,6 @@
1
1
  require 'fpm'
2
2
  require 'fpm/package'
3
+ require 'fpm/fry/channel'
3
4
 
4
5
  require 'fpm/fry/client'
5
6
 
metadata CHANGED
@@ -1,14 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fpm-fry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
+ - Maxime Lagresle
8
+ - Stefan Kaes
7
9
  - Hannes Georg
8
10
  autorequire:
9
11
  bindir: bin
10
12
  cert_chain: []
11
- date: 2016-10-19 00:00:00.000000000 Z
13
+ date: 2018-03-29 00:00:00.000000000 Z
12
14
  dependencies:
13
15
  - !ruby/object:Gem::Dependency
14
16
  name: excon
@@ -52,8 +54,84 @@ dependencies:
52
54
  - - "~>"
53
55
  - !ruby/object:Gem::Version
54
56
  version: '1.8'
57
+ - !ruby/object:Gem::Dependency
58
+ name: rake
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '12.0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: '12.0'
71
+ - !ruby/object:Gem::Dependency
72
+ name: rspec
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: '3.0'
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 3.0.0
81
+ type: :development
82
+ prerelease: false
83
+ version_requirements: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '3.0'
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 3.0.0
91
+ - !ruby/object:Gem::Dependency
92
+ name: webmock
93
+ requirement: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '3.0'
98
+ type: :development
99
+ prerelease: false
100
+ version_requirements: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '3.0'
105
+ - !ruby/object:Gem::Dependency
106
+ name: coveralls
107
+ requirement: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ type: :development
113
+ prerelease: false
114
+ version_requirements: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ - !ruby/object:Gem::Dependency
120
+ name: simplecov
121
+ requirement: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ type: :development
127
+ prerelease: false
128
+ version_requirements: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
55
133
  description: deep-fried package builder
56
- email: hannes.georg@xing.com
134
+ email: maxime.lagresle@xing.com
57
135
  executables:
58
136
  - fpm-fry
59
137
  extensions: []
@@ -75,14 +153,17 @@ files:
75
153
  - lib/fpm/fry/joined_io.rb
76
154
  - lib/fpm/fry/plugin.rb
77
155
  - lib/fpm/fry/plugin/alternatives.rb
156
+ - lib/fpm/fry/plugin/apt.rb
78
157
  - lib/fpm/fry/plugin/config.rb
79
158
  - lib/fpm/fry/plugin/edit_staging.rb
159
+ - lib/fpm/fry/plugin/env.rb
80
160
  - lib/fpm/fry/plugin/exclude.rb
81
161
  - lib/fpm/fry/plugin/init.rb
82
162
  - lib/fpm/fry/plugin/platforms.rb
83
163
  - lib/fpm/fry/plugin/same_version.rb
84
164
  - lib/fpm/fry/plugin/script_helper.rb
85
165
  - lib/fpm/fry/plugin/service.rb
166
+ - lib/fpm/fry/plugin/systemd.rb
86
167
  - lib/fpm/fry/plugin/user.rb
87
168
  - lib/fpm/fry/recipe.rb
88
169
  - lib/fpm/fry/recipe/builder.rb