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