fpm-fry 0.2.2 → 0.4.6

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.
@@ -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,26 +1,28 @@
1
1
  require 'fpm/fry/source'
2
- require 'fpm/fry/source/package'
2
+ require 'fpm/fry/source/archive'
3
3
  require 'fpm/fry/source/dir'
4
4
  require 'fpm/fry/source/patched'
5
5
  require 'fpm/fry/source/git'
6
6
  require 'fpm/fry/plugin'
7
- require 'fpm/fry/os_db'
7
+ require 'fpm/fry/exec'
8
8
  require 'shellwords'
9
9
  require 'cabin'
10
- require 'open3'
11
10
  module FPM; module Fry
12
11
 
12
+ # A FPM::Fry::Recipe contains all information needed to build a package.
13
+ #
14
+ # It is usually created by {FPM::Fry::Recipe::Builder}.
13
15
  class Recipe
14
16
 
17
+ # A FPM::Fry::Recipe::Step is a named build step.
18
+ #
19
+ # @see FPM::Fry::Recipe#steps
15
20
  class Step < Struct.new(:name, :value)
16
21
  def to_s
17
22
  value.to_s
18
23
  end
19
24
  end
20
25
 
21
- class DuplicateDependency < ArgumentError
22
- end
23
-
24
26
  class PackageRecipe
25
27
  attr_accessor :name,
26
28
  :iteration,
@@ -60,7 +62,12 @@ module FPM; module Fry
60
62
 
61
63
  alias dependencies depends
62
64
 
65
+ # Applies settings to output package
66
+ # @param [FPM::Package] package
67
+ # @return [FPM::Package] package
68
+ # @api private
63
69
  def apply_output( package )
70
+ output_hooks.each{|h| h.call(self, package) }
64
71
  package.name = name
65
72
  package.version = version
66
73
  package.iteration = iteration
@@ -81,14 +88,16 @@ module FPM; module Fry
81
88
  end
82
89
  end
83
90
  end
84
- output_hooks.each{|h| h.call(self, package) }
85
91
  return package
86
92
  end
87
93
 
88
94
  alias apply apply_output
89
95
 
96
+ # @api private
90
97
  SYNTAX_CHECK_SHELLS = ['/bin/sh','/bin/bash', '/bin/dash']
91
98
 
99
+ # Lints the settings for some common problems
100
+ # @return [Array<String>] problems
92
101
  def lint
93
102
  problems = []
94
103
  problems << "Name is empty." if name.to_s == ''
@@ -110,41 +119,58 @@ module FPM; module Fry
110
119
  problems << "#{type} script doesn't have a valid command in shebang"
111
120
  end
112
121
  if SYNTAX_CHECK_SHELLS.include? args[0]
113
- sin, sout, serr, th = Open3.popen3(args[0],'-n')
114
- sin.write(s)
115
- sin.close
116
- if th.value.exitstatus != 0
117
- problems << "#{type} script is not valid #{args[0]} code: #{serr.read.chomp}"
122
+ begin
123
+ Exec::exec(args[0],'-n', stdin_data: s)
124
+ rescue Exec::Failed => e
125
+ problems << "#{type} script is not valid #{args[0]} code: #{e.stderr.chomp}"
118
126
  end
119
- serr.close
120
- sout.close
121
127
  end
122
128
  end
123
129
  return problems
124
130
  end
125
131
  end
126
132
 
127
- attr_accessor :source,
128
- :build_mounts,
129
- :apt_setup,
130
- :before_build_steps,
131
- :steps,
132
- :packages,
133
- :build_depends,
134
- :input_hooks
133
+ # @return [FPM::Fry::Source] the source used for building
134
+ attr_accessor :source
135
+
136
+ attr_accessor :build_mounts
137
+
138
+ # @return [Array<#to_s>] steps that will be carried out before dependencies are installed
139
+ attr_accessor :before_dependencies_steps
140
+
141
+ # @return [Array<#to_s>] steps that will be carried out before build
142
+ attr_accessor :before_build_steps
143
+
144
+ # @return [Array<#to_s>] steps that will be carried out during build
145
+ attr_accessor :steps
146
+
147
+ # @return [Array<FPM::Fry::PackageRecipe>] a list of packages that will be created
148
+ attr_accessor :packages
149
+
150
+ # @return [Hash<String,Hash>] build dependencies
151
+ attr_accessor :build_depends
152
+
153
+ # @return [Array<#call>] hooks that will be called on the input package
154
+ attr_accessor :input_hooks
155
+
156
+ # @return [Array<#call>] hooks that will be called when building the Dockerfile
157
+ attr_accessor :dockerfile_hooks
135
158
 
136
159
  def initialize
137
160
  @source = Source::Null
161
+ @before_dependencies_steps = []
138
162
  @before_build_steps = []
139
163
  @steps = []
140
164
  @packages = [PackageRecipe.new]
141
165
  @packages[0].files << '**'
142
166
  @build_depends = {}
143
167
  @input_hooks = []
168
+ @dockerfile_hooks = []
144
169
  @build_mounts = []
145
- @apt_setup = []
146
170
  end
147
171
 
172
+ # Calculates all dependencies of this recipe
173
+ # @return [Hash<String,Hash>] the dependencies
148
174
  def depends
149
175
  depends = @packages.map(&:depends).inject(:merge)
150
176
  @packages.map(&:name).each do | n |
@@ -153,15 +179,30 @@ module FPM; module Fry
153
179
  return depends
154
180
  end
155
181
 
182
+ # Checks all packages for common errors
183
+ # @return [Array<String>] problems
156
184
  def lint
157
185
  packages.flat_map(&:lint)
158
186
  end
159
187
 
188
+ # Applies input settings to package
189
+ # @param [FPM::Package] package
190
+ # @return [FPM::Package]
160
191
  def apply_input( package )
161
192
  input_hooks.each{|h| h.call(self, package) }
162
193
  return package
163
194
  end
164
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
+
165
206
  end
166
207
 
167
208
  end ; end
@@ -1,21 +1,38 @@
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
 
7
9
  class NotFound < StandardError
8
10
  end
9
11
 
10
- class PackageBuilder < Struct.new(:variables, :package_recipe)
12
+ class PackageBuilder
13
+
14
+ # @return [Hash<Symbol,Object>]
15
+ attr :variables
16
+
17
+ # @return [FPM::Fry::PackageRecipe]
18
+ attr :package_recipe
11
19
 
20
+ # @return [Cabin::Channel]
12
21
  attr :logger
13
22
 
14
- def initialize( variables, recipe = PackageRecipe.new, options = {})
15
- super(variables, recipe)
23
+ # @return [FPM::Fry::Inspector,nil]
24
+ attr :inspector
25
+
26
+ # @api private
27
+ def initialize( variables, package_recipe, options = {})
28
+ @variables = variables
29
+ @package_recipe = package_recipe
16
30
  @logger = options.fetch(:logger){ Cabin::Channel.get }
31
+ @inspector = options[:inspector]
17
32
  end
18
33
 
34
+ # Returns the package type ( e.g. "debian" or "redhat" ).
35
+ # @return [String]
19
36
  def flavour
20
37
  variables[:flavour]
21
38
  end
@@ -25,9 +42,13 @@ module FPM::Fry
25
42
  end
26
43
  alias platform distribution
27
44
 
28
- def distribution_version
29
- 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]
30
49
  end
50
+
51
+ alias distribution_version release
31
52
  alias platform_version distribution_version
32
53
 
33
54
  def codename
@@ -174,20 +195,20 @@ module FPM::Fry
174
195
 
175
196
  class Builder < PackageBuilder
176
197
 
198
+ # @return [FPM::Fry::Recipe]
177
199
  attr :recipe
178
200
 
179
- def initialize( variables, recipe = Recipe.new, options = {})
201
+ # @param [Hash<Symbol,Object>] variables
202
+ # @param [Hash] options
203
+ # @option options [FPM::Fry::Recipe] :recipe (Recipe.new)
204
+ # @option options [Cabin::Channel] :logger (default cabin channel)
205
+ # @option options [FPM::Fry::Inspector] :inspector
206
+ def initialize( variables, options = {} )
207
+ recipe = options.fetch(:recipe){ Recipe.new }
180
208
  variables = variables.dup
181
- if variables[:distribution] && !variables[:flavour] && OsDb[variables[:distribution]]
182
- variables[:flavour] = OsDb[variables[:distribution]][:flavour]
183
- end
184
- if !variables[:codename] && OsDb[variables[:distribution]] && variables[:distribution_version]
185
- codename = OsDb[variables[:distribution]][:codenames].find{|name,version| variables[:distribution_version].start_with? version }
186
- variables[:codename] = codename[0] if codename
187
- end
188
209
  variables.freeze
189
210
  @recipe = recipe
190
- @before_build = false
211
+ @steps = :steps
191
212
  register_default_source_types!
192
213
  super(variables, recipe.packages[0], options)
193
214
  end
@@ -218,7 +239,9 @@ module FPM::Fry
218
239
  end
219
240
 
220
241
  def apt_setup(cmd)
221
- recipe.apt_setup << cmd
242
+ before_dependencies do
243
+ bash cmd
244
+ end
222
245
  end
223
246
 
224
247
  def run(*args)
@@ -237,7 +260,10 @@ module FPM::Fry
237
260
  code = Recipe::Step.new(name, code)
238
261
  end
239
262
  # Don't do this at home
240
- if @before_build
263
+ case(@steps)
264
+ when :before_dependencies
265
+ recipe.before_dependencies_steps << code
266
+ when :before_build
241
267
  recipe.before_build_steps << code
242
268
  else
243
269
  recipe.steps << code
@@ -245,10 +271,17 @@ module FPM::Fry
245
271
  end
246
272
 
247
273
  def before_build
248
- @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
249
282
  yield
250
283
  ensure
251
- @before_build = false
284
+ @steps = steps
252
285
  end
253
286
 
254
287
  def build_depends( name , options = {} )
@@ -266,7 +299,7 @@ module FPM::Fry
266
299
  pr.version = package_recipe.version
267
300
  pr.iteration = package_recipe.iteration
268
301
  recipe.packages << pr
269
- PackageBuilder.new(variables, pr).instance_eval(&block)
302
+ PackageBuilder.new(variables, pr, logger: logger, inspector: inspector).instance_eval(&block)
270
303
  end
271
304
 
272
305
  protected
@@ -289,7 +322,7 @@ module FPM::Fry
289
322
 
290
323
  def register_default_source_types!
291
324
  register_source_type Source::Git
292
- register_source_type Source::Package
325
+ register_source_type Source::Archive
293
326
  register_source_type Source::Dir
294
327
  end
295
328
 
@@ -1,34 +1,40 @@
1
+ require 'fpm/fry/with_data'
1
2
  module FPM; module Fry ; module Source
2
3
 
4
+ # Raised when building a cache failed.
3
5
  class CacheFailed < StandardError
4
-
5
- attr :data
6
-
7
- def initialize(e, data = {})
8
- if e.kind_of? Exception
9
- @data = {reason: e}.merge data
10
- super(e.message)
11
- else
12
- @data = data.dup
13
- super(e.to_s)
14
- end
15
- end
6
+ include WithData
16
7
  end
17
8
 
9
+ # A special source that is empty.
18
10
  module Null
19
11
 
12
+ # A special cache that is empty.
20
13
  module Cache
14
+
15
+ # @return [IO] an empty tar
21
16
  def self.tar_io
22
17
  StringIO.new("\x00"*1024)
23
18
  end
19
+
20
+ # @return [Hash] an empty hash
24
21
  def self.file_map
25
22
  return {}
26
23
  end
24
+
25
+ # @return [String] 32 times zero
27
26
  def self.cachekey
28
27
  return '0' * 32
29
28
  end
29
+
30
+ # @return [String] common path prefix of all files that should be stripped
31
+ def self.prefix
32
+ return ""
33
+ end
30
34
  end
31
35
 
36
+ # @see FPM::Fry::Source::Null::Cache
37
+ # @return {FPM::Fry::Source::Null::Cache}
32
38
  def self.build_cache(*_)
33
39
  return Cache
34
40
  end
@@ -37,11 +43,34 @@ module FPM; module Fry ; module Source
37
43
 
38
44
  class << self
39
45
 
46
+ # @api private
40
47
  def guess_regex(rx, url)
41
48
  if m = rx.match(url.to_s)
42
49
  return m[0].size
43
50
  end
44
51
  end
45
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
+
46
75
  end
47
76
  end ; end ; end