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.
- checksums.yaml +4 -4
- data/lib/cabin/nice_output.rb +16 -1
- data/lib/fpm/fry/block_enumerator.rb +6 -3
- data/lib/fpm/fry/channel.rb +13 -0
- data/lib/fpm/fry/chroot.rb +2 -2
- data/lib/fpm/fry/client.rb +81 -6
- data/lib/fpm/fry/command.rb +11 -31
- data/lib/fpm/fry/command/cook.rb +19 -51
- data/lib/fpm/fry/detector.rb +43 -98
- data/lib/fpm/fry/docker_file.rb +3 -11
- data/lib/fpm/fry/exec.rb +76 -0
- data/lib/fpm/fry/inspector.rb +70 -0
- data/lib/fpm/fry/joined_io.rb +1 -1
- data/lib/fpm/fry/plugin/edit_staging.rb +1 -1
- data/lib/fpm/fry/plugin/init.rb +71 -42
- data/lib/fpm/fry/plugin/service.rb +108 -49
- data/lib/fpm/fry/recipe.rb +46 -21
- data/lib/fpm/fry/recipe/builder.rb +26 -13
- data/lib/fpm/fry/source.rb +14 -12
- data/lib/fpm/fry/source/{package.rb → archive.rb} +68 -31
- data/lib/fpm/fry/source/dir.rb +2 -5
- data/lib/fpm/fry/source/git.rb +70 -43
- data/lib/fpm/fry/source/patched.rb +14 -10
- data/lib/fpm/fry/with_data.rb +34 -0
- data/lib/fpm/package/docker.rb +15 -0
- metadata +5 -4
- data/lib/fpm/fry/os_db.rb +0 -36
data/lib/fpm/fry/docker_file.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/fpm/fry/exec.rb
ADDED
@@ -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
|
data/lib/fpm/fry/joined_io.rb
CHANGED
data/lib/fpm/fry/plugin/init.rb
CHANGED
@@ -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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
30
|
-
|
31
|
-
@name =
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
84
|
-
init
|
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
|
-
|
88
|
-
|
89
|
-
edit.
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
154
|
+
invoke-rc.d #{Shellwords.shellescape name} start
|
103
155
|
else
|
104
|
-
|
156
|
+
start #{Shellwords.shellescape name}
|
105
157
|
fi
|
106
158
|
BASH
|
107
|
-
|
159
|
+
sh.before_remove_entirely(<<BASH)
|
108
160
|
if status #{Shellwords.shellescape name} 2>/dev/null | grep -q ' start/'; then
|
109
|
-
|
161
|
+
stop #{Shellwords.shellescape name}
|
110
162
|
fi
|
111
163
|
BASH
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
138
|
-
|
197
|
+
systemctl --system daemon-reload
|
198
|
+
systemctl restart #{Shellwords.shellescape name}.service
|
139
199
|
fi
|
140
200
|
BASH
|
141
|
-
|
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
|