fpm-fry 0.2.2 → 0.4.6
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 +5 -5
- data/lib/cabin/nice_output.rb +16 -1
- data/lib/fpm/fry/block_enumerator.rb +6 -3
- data/lib/fpm/fry/build_output_parser.rb +10 -5
- data/lib/fpm/fry/channel.rb +13 -0
- data/lib/fpm/fry/chroot.rb +2 -2
- data/lib/fpm/fry/client.rb +96 -12
- data/lib/fpm/fry/command.rb +15 -32
- data/lib/fpm/fry/command/cook.rb +38 -56
- data/lib/fpm/fry/detector.rb +43 -98
- data/lib/fpm/fry/docker_file.rb +60 -26
- 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/apt.rb +52 -0
- data/lib/fpm/fry/plugin/edit_staging.rb +1 -1
- data/lib/fpm/fry/plugin/env.rb +45 -0
- data/lib/fpm/fry/plugin/init.rb +71 -42
- data/lib/fpm/fry/plugin/service.rb +108 -49
- data/lib/fpm/fry/plugin/systemd.rb +75 -0
- data/lib/fpm/fry/recipe.rb +64 -23
- data/lib/fpm/fry/recipe/builder.rb +53 -20
- data/lib/fpm/fry/source.rb +41 -12
- data/lib/fpm/fry/source/{package.rb → archive.rb} +115 -35
- data/lib/fpm/fry/source/dir.rb +13 -8
- data/lib/fpm/fry/source/git.rb +81 -45
- data/lib/fpm/fry/source/patched.rb +61 -32
- data/lib/fpm/fry/tar.rb +63 -0
- data/lib/fpm/fry/templates/debian/after_remove.erb +1 -1
- data/lib/fpm/fry/templates/debian/before_remove.erb +1 -1
- data/lib/fpm/fry/ui.rb +1 -1
- data/lib/fpm/fry/with_data.rb +34 -0
- data/lib/fpm/package/docker.rb +16 -0
- metadata +82 -13
- data/lib/fpm/fry/os_db.rb +0 -36
data/lib/fpm/fry/detector.rb
CHANGED
@@ -1,117 +1,62 @@
|
|
1
|
-
require 'fpm/fry/
|
1
|
+
require 'fpm/fry/detector'
|
2
2
|
module FPM; module Fry
|
3
3
|
|
4
4
|
module Detector
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
5
|
+
# Detects a set of basic properties about an image.
|
6
|
+
#
|
7
|
+
# @param [Inspector] inspector
|
8
|
+
# @return [Hash<Symbol, String>]
|
9
|
+
def self.detect(inspector)
|
10
|
+
found = {}
|
11
|
+
if inspector.exists? '/usr/bin/apt-get'
|
12
|
+
found[:flavour] = 'debian'
|
13
|
+
elsif inspector.exists? '/bin/rpm'
|
14
|
+
found[:flavour] = 'redhat'
|
13
15
|
end
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
client.read(container,'/etc/lsb-release') do |file|
|
24
|
-
file.read.each_line do |line|
|
25
|
-
case(line)
|
26
|
-
when /\ADISTRIB_ID=/ then
|
27
|
-
@distribution = $'.strip.downcase
|
28
|
-
when /\ADISTRIB_RELEASE=/ then
|
29
|
-
@version = $'.strip
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
return !!(@distribution and @version)
|
34
|
-
rescue Client::FileNotFound
|
35
|
-
end
|
36
|
-
begin
|
37
|
-
client.read(container,'/etc/debian_version') do |file|
|
38
|
-
content = file.read
|
39
|
-
if /\A\d+(?:\.\d+)+\Z/ =~ content
|
40
|
-
@distribution = 'debian'
|
41
|
-
@version = content.strip
|
42
|
-
end
|
43
|
-
end
|
44
|
-
return !!(@distribution and @version)
|
45
|
-
rescue Client::FileNotFound
|
46
|
-
end
|
47
|
-
begin
|
48
|
-
client.read(container,'/etc/redhat-release') do |file|
|
49
|
-
if file.header.typeflag == "2" # centos links this file
|
50
|
-
client.read(container,File.absolute_path(file.header.linkname,'/etc')) do |file|
|
51
|
-
detect_redhat_release(file)
|
52
|
-
end
|
53
|
-
else
|
54
|
-
detect_redhat_release(file)
|
55
|
-
end
|
16
|
+
begin
|
17
|
+
inspector.read_content('/etc/lsb-release').each_line do |line|
|
18
|
+
case(line)
|
19
|
+
when /\ADISTRIB_ID=/ then
|
20
|
+
found[:distribution] = $'.strip.downcase
|
21
|
+
when /\ADISTRIB_RELEASE=/ then
|
22
|
+
found[:release] = $'.strip
|
23
|
+
when /\ADISTRIB_CODENAME=/ then
|
24
|
+
found[:codename] = $'.strip
|
56
25
|
end
|
57
|
-
return !!(@distribution and @version)
|
58
|
-
rescue Client::FileNotFound
|
59
26
|
end
|
60
|
-
|
27
|
+
rescue Client::FileNotFound
|
61
28
|
end
|
62
29
|
|
63
|
-
|
64
|
-
|
65
|
-
file.read.each_line do |line|
|
30
|
+
begin
|
31
|
+
inspector.read_content('/etc/os-release').each_line do |line|
|
66
32
|
case(line)
|
67
|
-
when /\
|
68
|
-
|
69
|
-
|
33
|
+
when /\AVERSION=\"(\w+) \((\w+)\)\"/ then
|
34
|
+
found[:release] ||= $1
|
35
|
+
found[:codename] ||= $2
|
70
36
|
end
|
71
37
|
end
|
38
|
+
rescue Client::FileNotFound
|
72
39
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
attr :distribution
|
81
|
-
attr :version
|
82
|
-
|
83
|
-
def initialize(client, image, factory = Container)
|
84
|
-
super
|
85
|
-
end
|
86
|
-
|
87
|
-
def detect!
|
88
|
-
body = JSON.generate({"Image" => image, "Cmd" => "exit 0"})
|
89
|
-
begin
|
90
|
-
res = client.post( path: client.url('containers','create'),
|
91
|
-
headers: {'Content-Type' => 'application/json'},
|
92
|
-
body: body,
|
93
|
-
expects: [201]
|
94
|
-
)
|
95
|
-
rescue Excon::Errors::NotFound
|
96
|
-
raise ImageNotFound, "Image #{image.inspect} not found. Did you do a `docker pull #{image}` before?"
|
40
|
+
begin
|
41
|
+
content = inspector.read_content('/etc/debian_version')
|
42
|
+
if /\A\d+(?:\.\d+)+\Z/ =~ content
|
43
|
+
found[:distribution] ||= 'debian'
|
44
|
+
found[:release] = content.strip
|
97
45
|
end
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
return false
|
46
|
+
rescue Client::FileNotFound
|
47
|
+
end
|
48
|
+
begin
|
49
|
+
content = inspector.read_content('/etc/redhat-release')
|
50
|
+
content.each_line do |line|
|
51
|
+
case(line)
|
52
|
+
when /\A(\w+)(?: Linux)? release ([\d\.]+)/ then
|
53
|
+
found[:distribution] ||= $1.strip.downcase
|
54
|
+
found[:release] = $2.strip
|
108
55
|
end
|
109
|
-
ensure
|
110
|
-
client.delete(path: client.url('containers',container))
|
111
56
|
end
|
57
|
+
rescue Client::FileNotFound
|
112
58
|
end
|
59
|
+
return found
|
113
60
|
end
|
114
|
-
|
115
|
-
|
116
61
|
end
|
117
62
|
end ; end
|
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,12 +11,13 @@ 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)
|
16
|
+
if cache.respond_to? :logger
|
17
|
+
@logger = cache.logger
|
18
|
+
else
|
19
|
+
@logger = Cabin::Channel.get
|
20
|
+
end
|
21
21
|
end
|
22
22
|
|
23
23
|
def dockerfile
|
@@ -26,8 +26,8 @@ module FPM; module Fry
|
|
26
26
|
|
27
27
|
df << "RUN mkdir /tmp/build"
|
28
28
|
|
29
|
-
|
30
|
-
df << "
|
29
|
+
file_map.each do |from, to|
|
30
|
+
df << "COPY #{map_from(from)} #{map_to(to)}"
|
31
31
|
end
|
32
32
|
|
33
33
|
df << ""
|
@@ -52,6 +52,33 @@ module FPM; module Fry
|
|
52
52
|
return sio
|
53
53
|
end
|
54
54
|
|
55
|
+
private
|
56
|
+
|
57
|
+
attr :logger
|
58
|
+
|
59
|
+
def file_map
|
60
|
+
prefix = ""
|
61
|
+
to = ""
|
62
|
+
if cache.respond_to? :prefix
|
63
|
+
prefix = cache.prefix
|
64
|
+
end
|
65
|
+
if cache.respond_to? :to
|
66
|
+
to = cache.to || ""
|
67
|
+
end
|
68
|
+
fm = cache.file_map
|
69
|
+
if fm.nil?
|
70
|
+
return { prefix => to }
|
71
|
+
end
|
72
|
+
if fm.size == 1
|
73
|
+
key, value = fm.first
|
74
|
+
key = key.gsub(%r!\A\./|/\z!,'')
|
75
|
+
if ["",".","./"].include?(value) && key == prefix
|
76
|
+
logger.hint("You can remove the file_map: #{fm.inspect} option on source. The given value is the default")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
return fm
|
80
|
+
end
|
81
|
+
|
55
82
|
def map_to(dir)
|
56
83
|
if ['','.'].include? dir
|
57
84
|
return '/tmp/build'
|
@@ -76,27 +103,34 @@ module FPM; module Fry
|
|
76
103
|
private :options
|
77
104
|
|
78
105
|
def initialize(base, variables, recipe, options = {})
|
79
|
-
variables = variables.dup
|
80
|
-
|
81
|
-
variables[:flavour] = OsDb[variables[:distribution]][:flavour]
|
82
|
-
end
|
83
|
-
variables.freeze
|
106
|
+
variables = variables.dup.freeze
|
107
|
+
raise Fry::WithData('unknown flavour', 'flavour' => variables[:flavour]) unless ['debian','redhat'].include? variables[:flavour]
|
84
108
|
@options = options.dup.freeze
|
85
109
|
super(base, variables, recipe)
|
86
110
|
end
|
87
111
|
|
88
112
|
def dockerfile
|
89
|
-
df =
|
90
|
-
|
91
|
-
|
113
|
+
df = {
|
114
|
+
source: [],
|
115
|
+
dependencies: [],
|
116
|
+
build: []
|
117
|
+
}
|
118
|
+
df[:source] << "FROM #{base}"
|
119
|
+
workdir = '/tmp/build'
|
120
|
+
# TODO: get this from cache, not from the source itself
|
121
|
+
if recipe.source.respond_to? :to
|
122
|
+
to = recipe.source.to || ""
|
123
|
+
workdir = File.expand_path(to, workdir)
|
124
|
+
end
|
125
|
+
df[:source] << "WORKDIR #{workdir}"
|
92
126
|
|
93
127
|
# need to add external sources before running any command
|
94
128
|
recipe.build_mounts.each do |source, target|
|
95
|
-
df << "
|
129
|
+
df[:dependencies] << "COPY #{source} ./#{target}"
|
96
130
|
end
|
97
131
|
|
98
|
-
recipe.
|
99
|
-
df << "RUN #{step}"
|
132
|
+
recipe.before_dependencies_steps.each do |step|
|
133
|
+
df[:dependencies] << "RUN #{step.to_s}"
|
100
134
|
end
|
101
135
|
|
102
136
|
if build_dependencies.any?
|
@@ -106,22 +140,22 @@ module FPM; module Fry
|
|
106
140
|
if options[:update]
|
107
141
|
update = 'apt-get update && '
|
108
142
|
end
|
109
|
-
df << "RUN #{update}apt-get install --yes #{Shellwords.join(build_dependencies)}"
|
143
|
+
df[:dependencies] << "RUN #{update}apt-get install --yes #{Shellwords.join(build_dependencies)}"
|
110
144
|
when 'redhat'
|
111
|
-
df << "RUN yum -y install #{Shellwords.join(build_dependencies)}"
|
145
|
+
df[:dependencies] << "RUN yum -y install #{Shellwords.join(build_dependencies)}"
|
112
146
|
else
|
113
147
|
raise "Unknown flavour: #{variables[:flavour]}"
|
114
148
|
end
|
115
149
|
end
|
116
150
|
|
117
151
|
recipe.before_build_steps.each do |step|
|
118
|
-
df << "RUN #{step.to_s}"
|
152
|
+
df[:build] << "RUN #{step.to_s}"
|
119
153
|
end
|
120
154
|
|
121
|
-
df << "
|
122
|
-
df << "
|
123
|
-
df
|
124
|
-
return df.join("\n")
|
155
|
+
df[:build] << "COPY .build.sh #{workdir}/"
|
156
|
+
df[:build] << "CMD #{workdir}/.build.sh"
|
157
|
+
recipe.apply_dockerfile_hooks(df)
|
158
|
+
return [*df[:source],*df[:dependencies],*df[:build],""].join("\n")
|
125
159
|
end
|
126
160
|
|
127
161
|
def build_sh
|
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
|