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.
@@ -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