fpm-fry 0.2.2 → 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|