fpm-fry 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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