fpm-fry 0.2.2 → 0.3.0

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.
@@ -1,7 +1,6 @@
1
1
  require 'fiber'
2
2
  require 'shellwords'
3
3
  require 'rubygems/package'
4
- require 'fpm/fry/os_db'
5
4
  require 'fpm/fry/source'
6
5
  require 'fpm/fry/joined_io'
7
6
  module FPM; module Fry
@@ -12,11 +11,7 @@ module FPM; module Fry
12
11
  class Source < Struct.new(:variables, :cache)
13
12
 
14
13
  def initialize(variables, cache = Source::Null::Cache)
15
- variables = variables.dup
16
- if variables[:distribution] && !variables[:flavour] && OsDb[variables[:distribution]]
17
- variables[:flavour] = OsDb[variables[:distribution]][:flavour]
18
- end
19
- variables.freeze
14
+ variables = variables.dup.freeze
20
15
  super(variables, cache)
21
16
  end
22
17
 
@@ -76,11 +71,8 @@ module FPM; module Fry
76
71
  private :options
77
72
 
78
73
  def initialize(base, variables, recipe, options = {})
79
- variables = variables.dup
80
- if variables[:distribution] && !variables[:flavour] && OsDb[variables[:distribution]]
81
- variables[:flavour] = OsDb[variables[:distribution]][:flavour]
82
- end
83
- variables.freeze
74
+ variables = variables.dup.freeze
75
+ raise Fry::WithData('unknown flavour', 'flavour' => variables[:flavour]) unless ['debian','redhat'].include? variables[:flavour]
84
76
  @options = options.dup.freeze
85
77
  super(base, variables, recipe)
86
78
  end
@@ -0,0 +1,76 @@
1
+ require 'fpm/fry/with_data'
2
+ require 'open3'
3
+ module FPM
4
+ module Fry
5
+
6
+ module Exec
7
+
8
+ # Raised when running a command failed.
9
+ class Failed < StandardError
10
+ include WithData
11
+
12
+ # @return [String] contents of stderr
13
+ def stderr
14
+ data[:stderr]
15
+ end
16
+
17
+ end
18
+
19
+ class << self
20
+
21
+ # @!method [](*cmd, options = {}) Runs a command and returns its stdout as string. This method is preferred if the expected output is short.
22
+ # @param [Array<String>] cmd command to run
23
+ # @param [Hash] options
24
+ # @option options [Cabin::Channel] :logger
25
+ # @option options [String] :description human readable string to describe what the command is doing
26
+ # @option options [String] :stdin_data data to write to stding
27
+ # @option options [String] :chdir directory to change to
28
+ # @return [String] stdout
29
+ # @raise [FPM::Fry::Exec::Failed] when exitcode != 0
30
+ def [](*args)
31
+ cmd, options, description = extract_options_and_log(args)
32
+ stdout, stderr, status = Open3.capture3(*cmd, options)
33
+ if status.exitstatus != 0
34
+ raise Exec.const_get("ExitCode#{status.exitstatus}").new("#{description} failed", exitstatus: status.exitstatus, stderr: stderr, stdout: stdout, command: cmd)
35
+ end
36
+ return stdout
37
+ end
38
+
39
+ alias exec []
40
+
41
+ # @!method popen(*cmd, options = {}) Runs a command and returns its stdout as IO.
42
+ # @param [Array<String>] cmd command to run
43
+ # @param [Hash] options
44
+ # @option options [Cabin::Channel] :logger
45
+ # @option options [String] :description human readable string to describe what the command is doing
46
+ # @option options [String] :chdir directory to change to
47
+ # @return [IO] stdout
48
+ def popen(*args)
49
+ cmd, options, _description = extract_options_and_log(args)
50
+ return IO.popen(cmd, options)
51
+ end
52
+ private
53
+ def extract_options_and_log(args)
54
+ options = args.last.kind_of?(Hash) ? args.pop.dup : {}
55
+ cmd = args
56
+ logger = options.delete(:logger)
57
+ description = options.delete(:description) || "Running #{cmd.join(' ')}"
58
+ if logger
59
+ logger.debug(description, command: args)
60
+ end
61
+ return cmd, options, description
62
+ end
63
+
64
+ def const_missing(name)
65
+ if name.to_s =~ /\AExitCode\d+\z/
66
+ klass = Class.new(Failed)
67
+ const_set(name, klass)
68
+ return klass
69
+ end
70
+ super
71
+ end
72
+
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,70 @@
1
+ module FPM::Fry
2
+ # An inspector allows a plugin to gather information about the image used to
3
+ # build a package.
4
+ class Inspector
5
+
6
+ # Gets the file content at path.
7
+ #
8
+ # @param [String] path path to a file
9
+ # @raise [FPM::Fry::Client::FileNotFound] when the given path doesn't exist
10
+ # @raise [FPM::Fry::Client::NotAFile] when the given path is not a file
11
+ # @return [String] file content as string
12
+ def read_content(path)
13
+ return client.read_content(container, path)
14
+ end
15
+
16
+ # Gets whatever is at path. This once if path is a file. And all subfiles
17
+ # if it's a directory. Usually read_content is better.
18
+ #
19
+ # @param [String] path path to a file
20
+ # @raise [FPM::Fry::Client::FileNotFound] when the given path doesn't exist
21
+ # @raise [FPM::Fry::Client::NotAFile] when the given path is not a file
22
+ # @yield [entry] tar file entry
23
+ # @yieldparam entry [Gem::Package::TarEntry]
24
+ def read(path, &block)
25
+ return client.read(container, path, &block)
26
+ end
27
+
28
+ # Determines the target of a link
29
+ #
30
+ # @param [String] path
31
+ # @raise [FPM::Fry::Client::FileNotFound] when the given path doesn't exist
32
+ # @return [String] target
33
+ # @return [nil] when file is not a link
34
+ def link_target(path)
35
+ return client.link_target(container, path)
36
+ end
37
+
38
+ # Checks if file exists at path
39
+ #
40
+ # @param [String] path
41
+ # @return [true] when path exists
42
+ # @return [false] otherwise
43
+ def exists?(path)
44
+ client.read(container,path) do
45
+ return true
46
+ end
47
+ rescue FPM::Fry::Client::FileNotFound
48
+ return false
49
+ end
50
+
51
+ def self.for_image(client, image)
52
+ container = client.create(image)
53
+ begin
54
+ yield new(client, container)
55
+ ensure
56
+ client.destroy(container)
57
+ end
58
+ end
59
+
60
+ private
61
+ def initialize(client, container)
62
+ @client, @container = client, container
63
+ end
64
+
65
+ attr :client
66
+ attr :container
67
+
68
+ end
69
+
70
+ end
@@ -11,7 +11,7 @@ module FPM; module Fry
11
11
  end
12
12
 
13
13
  # Reads length bytes or all if length is nil.
14
- # @param [Numeric, nil] length
14
+ # @param [Numeric, nil] len
15
15
  # @return [String] resulting bytes
16
16
  def read( len = nil )
17
17
  buf = []
@@ -4,7 +4,7 @@ require 'fileutils'
4
4
  # A plugin to edit the final build results.
5
5
  # @example Add a file
6
6
  # plugin 'edit_staging' do
7
- # add_file '/a_file'
7
+ # add_file '/a_file', 'some content'
8
8
  # end
9
9
  module FPM::Fry::Plugin::EditStaging
10
10
 
@@ -1,53 +1,82 @@
1
1
  require 'fpm/fry/plugin'
2
+ # A plugin that detects the init system of a docker image.
3
+ #
4
+ # This plugin is a low-level plugin and is used by other plugins such as "service".
5
+ #
6
+ # @example in a recipe when using the image "ubuntu:16.04"
7
+ # plugin 'init'
8
+ # init.systemd? #=> true
9
+ # init.sysv? #=> false
2
10
  module FPM::Fry::Plugin::Init
3
11
 
4
- def self.detect_init(variables)
5
- if variables[:init]
6
- return variables[:init]
7
- end
8
- d = variables[:distribution]
9
- v = variables[:distribution_version].split('.').map(&:to_i)
10
- case(d)
11
- when 'debian'
12
- if v[0] < 8
13
- return 'sysv'
14
- else
15
- return 'systemd'
16
- end
17
- when 'ubuntu'
18
- if v[0] <= 14 && v[1] < 10
19
- return 'upstart'
20
- else
21
- return 'systemd'
22
- end
23
- when 'centos','redhat'
24
- if v[0] <= 5
25
- return 'sysv'
26
- elsif v[0] == 6
27
- return 'upstart'
28
- else
29
- return 'systemd'
30
- end
31
- else
32
- raise "Unknown init system for #{d} #{v.join '.'}"
12
+ # Contains information about the init system in use.
13
+ class System
14
+ # @return [Hash<Symbol,Object>] features of the init system
15
+ attr :with
16
+
17
+ def with?(feature)
18
+ !!with[feature]
19
+ end
20
+ def sysv?
21
+ name == :sysv
22
+ end
23
+ def upstart?
24
+ name == :upstart
25
+ end
26
+ def systemd?
27
+ name == :systemd
28
+ end
29
+ private
30
+ attr :name
31
+ def initialize(name, with)
32
+ @name, @with = name, with
33
33
  end
34
34
  end
35
35
 
36
- def init(*inits)
37
- inits = inits.flatten.map(&:to_s)
38
- actual = FPM::Fry::Plugin::Init.detect_init(variables)
39
- if inits.none?
40
- return actual
41
- elsif inits.include? actual
42
- if block_given?
43
- yield
44
- else
45
- return true
46
- end
47
- else
48
- return false
36
+ # @return [System] initsystem in use
37
+ def init
38
+ return @init
39
+ end
40
+
41
+ private
42
+ def self.detect(inspector)
43
+ if inspector.link_target('/sbin/init') == '/lib/systemd/systemd'
44
+ return System.new(:systemd, {})
45
+ end
46
+ if inspector.exists?('/etc/init')
47
+ return detect_upstart(inspector)
48
+ end
49
+ if inspector.exists?('/etc/init.d')
50
+ return detect_sysv(inspector)
49
51
  end
52
+ return nil
50
53
  end
51
54
 
55
+ def self.detect_upstart(inspector)
56
+ features = {
57
+ sysvcompat: inspector.exists?('/lib/init/upstart-job') ? '/lib/init/upstart-job' : false
58
+ }
59
+ return System.new(:upstart,features)
60
+ end
61
+
62
+ def self.detect_sysv(inspector)
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')
67
+ }
68
+ return System.new(:sysv,features)
69
+ end
70
+
71
+ def self.extended(base)
72
+ base.instance_eval do
73
+ @init ||= FPM::Fry::Plugin::Init.detect(inspector)
74
+ end
75
+ end
76
+
77
+ def self.apply(builder)
78
+ builder.extend(self)
79
+ return builder.init
80
+ end
52
81
 
53
82
  end
@@ -24,11 +24,12 @@ module FPM::Fry::Plugin ; module Service
24
24
 
25
25
  class DSL
26
26
 
27
+ # @return [Hash<String,Tuple<Numeric,Numeric>]
27
28
  attr :limits
28
29
 
29
- def initialize(*_)
30
- super
31
- @name = nil
30
+ # @api private
31
+ def initialize(name)
32
+ @name = name
32
33
  @command = []
33
34
  @limits = {}
34
35
  @user = nil
@@ -36,20 +37,35 @@ module FPM::Fry::Plugin ; module Service
36
37
  @chdir = nil
37
38
  end
38
39
 
39
- def name( n = nil )
40
- if n
41
- @name = n
40
+ # @overload name
41
+ # @return [String] this service's name
42
+ # @overload name( name )
43
+ # @param [String] name new name for this service
44
+ # @return [String] this service's name
45
+ def name( name = nil )
46
+ if name
47
+ @name = name
42
48
  end
43
49
  return @name
44
50
  end
45
51
 
46
- def group( n = nil )
47
- if n
48
- @group = n
52
+ # @overload group
53
+ # @return [String] the linux user group this service should run as
54
+ # @overload group( name )
55
+ # @param [String] name new linux user group this service should run as
56
+ # @return [String] the linux user group this service should run as
57
+ def group( group = nil )
58
+ if group
59
+ @group = group
49
60
  end
50
61
  return @group
51
62
  end
52
63
 
64
+ # @overload user
65
+ # @return [String] the linux user this service should run as
66
+ # @overload user( name )
67
+ # @param [String] name new linx user this service should run as
68
+ # @return [String] the linux user this service should run as
53
69
  def user( n = nil )
54
70
  if n
55
71
  @user = n
@@ -57,13 +73,39 @@ module FPM::Fry::Plugin ; module Service
57
73
  return @user
58
74
  end
59
75
 
76
+ # Sets a limit for this service. Valid limits are:
77
+ #
78
+ # - core
79
+ # - cpu
80
+ # - data
81
+ # - fsize
82
+ # - memlock
83
+ # - msgqueue
84
+ # - nice
85
+ # - nofile
86
+ # - nproc
87
+ # - rss
88
+ # - rtprio
89
+ # - sigpending
90
+ # - stack
91
+ #
92
+ # @see http://linux.die.net/man/5/limits.conf Limits.conf manpage for limits and their meanings.
93
+ # @param [String] name see above list for valid limits
94
+ # @param [Numeric,"unlimited"] soft soft limit
95
+ # @param [Numeric,"unlimited"] hard hard limit
60
96
  def limit( name, soft, hard = soft )
61
97
  unless LIMITS.include? name
62
98
  raise ArgumentError, "Unknown limit #{name.inspect}. Known limits are: #{LIMITS.inspect}"
63
99
  end
64
100
  @limits[name] = [soft,hard]
101
+ return nil
65
102
  end
66
103
 
104
+ # @overload chdir
105
+ # @return [String,nil] working directory of the service
106
+ # @overload chdir( dir )
107
+ # @param [String] dir new working directory of the service
108
+ # @return [String] working directory of the service
67
109
  def chdir( dir = nil )
68
110
  if dir
69
111
  @chdir = dir
@@ -80,76 +122,93 @@ module FPM::Fry::Plugin ; module Service
80
122
 
81
123
  # @api private
82
124
  def add!(builder)
83
- name = self.name || builder.name || raise
84
- init = Init.detect_init(builder.variables)
125
+ init = builder.plugin('init')
126
+ if init.systemd?
127
+ add_systemd!(builder)
128
+ elsif init.upstart?
129
+ add_upstart!(builder)
130
+ elsif init.sysv?
131
+ add_sysv!(builder)
132
+ end
133
+ end
134
+ private
135
+ def add_upstart!(builder)
136
+ init = builder.plugin('init')
85
137
  edit = builder.plugin('edit_staging')
86
138
  env = Environment.new(name, command, "", @limits, @user, @group, @chdir)
87
- case(init)
88
- when 'upstart' then
89
- edit.add_file "/etc/init/#{name}.conf",StringIO.new( env.render('upstart.erb') )
90
- edit.ln_s '/lib/init/upstart-job', "/etc/init.d/#{name}"
91
- builder.plugin('script_helper') do |sh|
92
- sh.after_install_or_upgrade(<<BASH)
139
+ edit.add_file "/etc/init/#{name}.conf",StringIO.new( env.render('upstart.erb') )
140
+ if init.with? :sysvcompat
141
+ edit.ln_s init.with[:sysvcompat], "/etc/init.d/#{name}"
142
+ end
143
+ builder.plugin('script_helper') do |sh|
144
+ sh.after_install_or_upgrade(<<BASH)
93
145
  if status #{Shellwords.shellescape name} 2>/dev/null | grep -q ' start/'; then
94
- # It has to be stop+start because upstart doesn't pickup changes with restart.
95
- if which invoke-rc.d >/dev/null 2>&1; then
96
- invoke-rc.d #{Shellwords.shellescape name} stop
97
- else
98
- stop #{Shellwords.shellescape name}
99
- fi
146
+ # It has to be stop+start because upstart doesn't pickup changes with restart.
147
+ if which invoke-rc.d >/dev/null 2>&1; then
148
+ invoke-rc.d #{Shellwords.shellescape name} stop
149
+ else
150
+ stop #{Shellwords.shellescape name}
151
+ fi
100
152
  fi
101
153
  if which invoke-rc.d >/dev/null 2>&1; then
102
- invoke-rc.d #{Shellwords.shellescape name} start
154
+ invoke-rc.d #{Shellwords.shellescape name} start
103
155
  else
104
- start #{Shellwords.shellescape name}
156
+ start #{Shellwords.shellescape name}
105
157
  fi
106
158
  BASH
107
- sh.before_remove_entirely(<<BASH)
159
+ sh.before_remove_entirely(<<BASH)
108
160
  if status #{Shellwords.shellescape name} 2>/dev/null | grep -q ' start/'; then
109
- stop #{Shellwords.shellescape name}
161
+ stop #{Shellwords.shellescape name}
110
162
  fi
111
163
  BASH
112
- end
113
- builder.plugin('config', FPM::Fry::Plugin::Config::IMPLICIT => true) do |co|
114
- co.include "etc/init/#{name}.conf"
115
- end
116
- when 'sysv' then
117
- edit.add_file "/etc/init.d/#{name}",StringIO.new( env.render('sysv.erb') ), chmod: '750'
118
- builder.plugin('script_helper') do |sh|
119
- sh.after_install_or_upgrade(<<BASH)
164
+ end
165
+ builder.plugin('config', FPM::Fry::Plugin::Config::IMPLICIT => true) do |co|
166
+ co.include "etc/init/#{name}.conf"
167
+ end
168
+ end
169
+
170
+ def add_sysv!(builder)
171
+ edit = builder.plugin('edit_staging')
172
+ env = Environment.new(name, command, "", @limits, @user, @group, @chdir)
173
+ edit.add_file "/etc/init.d/#{name}",StringIO.new( env.render('sysv.erb') ), chmod: '750'
174
+ builder.plugin('script_helper') do |sh|
175
+ sh.after_install_or_upgrade(<<BASH)
120
176
  update-rc.d #{Shellwords.shellescape name} defaults
121
177
  /etc/init.d/#{Shellwords.shellescape name} restart
122
178
  BASH
123
- sh.before_remove_entirely(<<BASH)
179
+ sh.before_remove_entirely(<<BASH)
124
180
  /etc/init.d/#{Shellwords.shellescape name} stop
125
181
  update-rc.d -f #{Shellwords.shellescape name} remove
126
182
  BASH
127
- end
128
- builder.plugin('config', FPM::Fry::Plugin::Config::IMPLICIT => true) do |co|
129
- co.include "etc/init.d/#{name}"
130
- end
131
- when 'systemd' then
132
- edit.add_file "/lib/systemd/system/#{name}.service", StringIO.new( env.render('systemd.erb') ), chmod: '644'
133
- builder.plugin('script_helper') do |sh|
134
- sh.after_install_or_upgrade(<<BASH)
183
+ end
184
+ builder.plugin('config', FPM::Fry::Plugin::Config::IMPLICIT => true) do |co|
185
+ co.include "etc/init.d/#{name}"
186
+ end
187
+ end
188
+
189
+ def add_systemd!(builder)
190
+ edit = builder.plugin('edit_staging')
191
+ env = Environment.new(name, command, "", @limits, @user, @group, @chdir)
192
+ edit.add_file "/lib/systemd/system/#{name}.service", StringIO.new( env.render('systemd.erb') ), chmod: '644'
193
+ builder.plugin('script_helper') do |sh|
194
+ sh.after_install_or_upgrade(<<BASH)
135
195
  systemctl preset #{Shellwords.shellescape name}.service
136
196
  if systemctl is-enabled --quiet #{Shellwords.shellescape name}.service ; then
137
- systemctl --system daemon-reload
138
- systemctl try-reload-or-restart #{Shellwords.shellescape name}.service
197
+ systemctl --system daemon-reload
198
+ systemctl restart #{Shellwords.shellescape name}.service
139
199
  fi
140
200
  BASH
141
- sh.before_remove_entirely(<<BASH)
201
+ sh.before_remove_entirely(<<BASH)
142
202
  systemctl disable --now #{Shellwords.shellescape name}.service
143
203
  BASH
144
204
 
145
- end
146
205
  end
147
206
  end
148
207
 
149
208
  end
150
209
 
151
210
  def self.apply(builder, &block)
152
- d = DSL.new
211
+ d = DSL.new(builder.name)
153
212
  if !block
154
213
  raise ArgumentError, "service plugin requires a block"
155
214
  elsif block.arity == 1