fpm-fry 0.1.3
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 +7 -0
- data/bin/fpm-fry +10 -0
- data/lib/cabin/nice_output.rb +70 -0
- data/lib/fpm/fry/block_enumerator.rb +25 -0
- data/lib/fpm/fry/build_output_parser.rb +22 -0
- data/lib/fpm/fry/client.rb +162 -0
- data/lib/fpm/fry/command/cook.rb +370 -0
- data/lib/fpm/fry/command.rb +90 -0
- data/lib/fpm/fry/detector.rb +109 -0
- data/lib/fpm/fry/docker_file.rb +149 -0
- data/lib/fpm/fry/joined_io.rb +63 -0
- data/lib/fpm/fry/os_db.rb +35 -0
- data/lib/fpm/fry/plugin/alternatives.rb +90 -0
- data/lib/fpm/fry/plugin/edit_staging.rb +66 -0
- data/lib/fpm/fry/plugin/exclude.rb +18 -0
- data/lib/fpm/fry/plugin/init.rb +53 -0
- data/lib/fpm/fry/plugin/platforms.rb +10 -0
- data/lib/fpm/fry/plugin/script_helper.rb +176 -0
- data/lib/fpm/fry/plugin/service.rb +100 -0
- data/lib/fpm/fry/plugin.rb +3 -0
- data/lib/fpm/fry/recipe/builder.rb +267 -0
- data/lib/fpm/fry/recipe.rb +141 -0
- data/lib/fpm/fry/source/dir.rb +56 -0
- data/lib/fpm/fry/source/git.rb +90 -0
- data/lib/fpm/fry/source/package.rb +202 -0
- data/lib/fpm/fry/source/patched.rb +118 -0
- data/lib/fpm/fry/source.rb +47 -0
- data/lib/fpm/fry/stream_parser.rb +98 -0
- data/lib/fpm/fry/tar.rb +71 -0
- data/lib/fpm/fry/templates/debian/after_install.erb +9 -0
- data/lib/fpm/fry/templates/debian/before_install.erb +13 -0
- data/lib/fpm/fry/templates/debian/before_remove.erb +13 -0
- data/lib/fpm/fry/templates/redhat/after_install.erb +2 -0
- data/lib/fpm/fry/templates/redhat/before_install.erb +6 -0
- data/lib/fpm/fry/templates/redhat/before_remove.erb +6 -0
- data/lib/fpm/fry/templates/sysv.erb +125 -0
- data/lib/fpm/fry/templates/upstart.erb +15 -0
- data/lib/fpm/fry/ui.rb +12 -0
- data/lib/fpm/package/docker.rb +186 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6d10781e6b5740b80f68e6a7130acd844c2b1d5c
|
4
|
+
data.tar.gz: 5dc23f36f547bd2359b433424ae8ef091dd2cf53
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ba95b38a5652f87439105db3c7430505695e5429ef90fdfadcb1b6f517556259c8add886a2c0b0cca72e0d7c7683a4584883d3e1263266ad5f4e162fa911b9f0
|
7
|
+
data.tar.gz: 9ded152eb45fbab21bdef04a651bee26689f0dd8a45a10d096ece7fe5cbc646f727fbc082e353f49bd62b6a303887d6ab86b6bb0e245625667d0529779f39f3e
|
data/bin/fpm-fry
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'cabin'
|
2
|
+
class Cabin::NiceOutput
|
3
|
+
|
4
|
+
CODEMAP = {
|
5
|
+
:normal => "\e[0m",
|
6
|
+
:red => "\e[1;31m",
|
7
|
+
:green => "\e[1;32m",
|
8
|
+
:yellow => "\e[0;33m",
|
9
|
+
:white => "\e[0;37m"
|
10
|
+
}
|
11
|
+
|
12
|
+
DIM_CODEMAP = {
|
13
|
+
red: "\e[0;31m",
|
14
|
+
green: "\e[0;32m",
|
15
|
+
white: "\e[1;30m",
|
16
|
+
yellow: "\e[33m"
|
17
|
+
}
|
18
|
+
|
19
|
+
LEVELMAP = {
|
20
|
+
:fatal => :red,
|
21
|
+
:error => :red,
|
22
|
+
:warn => :yellow,
|
23
|
+
:info => :green,
|
24
|
+
:debug => :white,
|
25
|
+
}
|
26
|
+
|
27
|
+
attr :io
|
28
|
+
|
29
|
+
def initialize(io)
|
30
|
+
@io = io
|
31
|
+
end
|
32
|
+
|
33
|
+
def <<(event)
|
34
|
+
data = event.clone
|
35
|
+
data.delete(:line)
|
36
|
+
data.delete(:file)
|
37
|
+
level = data.delete(:level) || :normal
|
38
|
+
data.delete(:message)
|
39
|
+
ts = data.delete(:timestamp)
|
40
|
+
|
41
|
+
color = data.delete(:color)
|
42
|
+
# :bold is expected to be truthy
|
43
|
+
bold = data.delete(:bold) ? :bold : nil
|
44
|
+
|
45
|
+
backtrace = data.delete(:backtrace)
|
46
|
+
|
47
|
+
# Make 'error' and other log levels have color
|
48
|
+
if color.nil?
|
49
|
+
color = LEVELMAP[level]
|
50
|
+
end
|
51
|
+
|
52
|
+
message = [event[:level] ? '====> ' : ' ',event[:message]]
|
53
|
+
message.unshift(CODEMAP[color.to_sym]) if !color.nil?
|
54
|
+
message << DIM_CODEMAP[color] if !color.nil?
|
55
|
+
if data.any?
|
56
|
+
message << "\n" << pp(data)
|
57
|
+
end
|
58
|
+
if backtrace
|
59
|
+
message << "\n\t--backtrace---------------\n\t" << backtrace.join("\n\t")
|
60
|
+
end
|
61
|
+
message << CODEMAP[:normal] if !color.nil?
|
62
|
+
@io.puts(message.join(""))
|
63
|
+
@io.flush
|
64
|
+
end
|
65
|
+
|
66
|
+
def pp(hash)
|
67
|
+
hash.map{|k,v| ' '+k.to_s + ": " + v.inspect }.join("\n")
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module FPM; module Fry
|
2
|
+
class BlockEnumerator < Struct.new(:io, :blocksize)
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(_, blocksize = 128)
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def each
|
10
|
+
return to_enum unless block_given?
|
11
|
+
# Reading bigger chunks is far more efficient that eaching over the
|
12
|
+
while chunk = io.read(blocksize)
|
13
|
+
yield chunk
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
while x = io.read(blocksize)
|
19
|
+
next if x == ""
|
20
|
+
return x
|
21
|
+
end
|
22
|
+
return ""
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end ; end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'json'
|
2
|
+
module FPM; module Fry
|
3
|
+
class BuildOutputParser < Struct.new(:out)
|
4
|
+
|
5
|
+
attr :images
|
6
|
+
|
7
|
+
def initialize(*_)
|
8
|
+
super
|
9
|
+
@images = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(chunk, *_)
|
13
|
+
json = JSON.parse(chunk)
|
14
|
+
stream = json['stream']
|
15
|
+
if /\ASuccessfully built (\w+)\Z/.match(stream)
|
16
|
+
images << $1
|
17
|
+
end
|
18
|
+
out << stream
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end ; end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'excon'
|
2
|
+
require 'rubygems/package'
|
3
|
+
require 'json'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'fpm/fry/tar'
|
7
|
+
|
8
|
+
module FPM; module Fry; end ; end
|
9
|
+
|
10
|
+
class FPM::Fry::Client
|
11
|
+
|
12
|
+
class LogInstrumentor < Struct.new(:logger)
|
13
|
+
|
14
|
+
def instrument(event, data = {})
|
15
|
+
if block_given?
|
16
|
+
logger.debug('Requesting HTTP', filtered(data))
|
17
|
+
r = yield
|
18
|
+
return r
|
19
|
+
else
|
20
|
+
logger.debug('Getting HTTP response', filtered(data))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def filtered(data)
|
25
|
+
filtered = {}
|
26
|
+
filtered[:path] = data[:path] if data[:path]
|
27
|
+
filtered[:verb] = data[:method] if data[:method]
|
28
|
+
filtered[:status] = data[:status] if data[:status]
|
29
|
+
filtered[:body] = data[:body][0..500] if data[:body]
|
30
|
+
filtered[:headers] = data[:headers]
|
31
|
+
return filtered
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
class FileNotFound < StandardError
|
37
|
+
end
|
38
|
+
|
39
|
+
extend Forwardable
|
40
|
+
def_delegators :agent, :post, :get, :delete
|
41
|
+
|
42
|
+
attr :docker_url, :logger, :tls
|
43
|
+
|
44
|
+
def initialize(options = {})
|
45
|
+
@docker_url = options.fetch(:docker_url){ self.class.docker_url }
|
46
|
+
@logger = options[:logger]
|
47
|
+
if @logger.nil?
|
48
|
+
@logger = Cabin::Channel.get
|
49
|
+
end
|
50
|
+
if options[:tls].nil? ? docker_url =~ %r!(\Ahttps://|:2376\z)! : options[:tls]
|
51
|
+
# enable tls
|
52
|
+
@tls = {
|
53
|
+
client_cert: File.join(self.class.docker_cert_path,'cert.pem'),
|
54
|
+
client_key: File.join(self.class.docker_cert_path, 'key.pem'),
|
55
|
+
ssl_ca_file: File.join(self.class.docker_cert_path, 'ca.pem'),
|
56
|
+
ssl_verify_peer: options.fetch(:tlsverify){ false }
|
57
|
+
}
|
58
|
+
[:client_cert, :client_key, :ssl_ca_file].each do |k|
|
59
|
+
if !File.exists?(@tls[k])
|
60
|
+
raise ArgumentError.new("#{k} #{@tls[k]} doesn't exist. Did you set DOCKER_CERT_PATH correctly?")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
else
|
64
|
+
@tls = {}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def server_version
|
69
|
+
@server_version ||= begin
|
70
|
+
res = agent.get(
|
71
|
+
expects: [200],
|
72
|
+
path: '/version'
|
73
|
+
)
|
74
|
+
JSON.parse(res.body)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.docker_cert_path
|
79
|
+
ENV.fetch('DOCKER_CERT_PATH',File.join(Dir.home, '.docker'))
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.docker_url
|
83
|
+
ENV.fetch('DOCKER_HOST'.freeze, 'unix:///var/run/docker.sock')
|
84
|
+
end
|
85
|
+
|
86
|
+
def tls?
|
87
|
+
tls.any?
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def url(*path)
|
92
|
+
['', "v"+server_version['ApiVersion'],*path].join('/')
|
93
|
+
end
|
94
|
+
|
95
|
+
def read(name, resource)
|
96
|
+
return to_enum(:read, name, resource) unless block_given?
|
97
|
+
body = JSON.generate({'Resource' => resource})
|
98
|
+
res = agent.post(
|
99
|
+
path: url('containers',name,'copy'),
|
100
|
+
headers: { 'Content-Type' => 'application/json' },
|
101
|
+
body: body,
|
102
|
+
expects: [200,500]
|
103
|
+
)
|
104
|
+
if res.status == 500
|
105
|
+
raise FileNotFound, "File #{resource.inspect} not found: #{res.body}"
|
106
|
+
end
|
107
|
+
sio = StringIO.new(res.body)
|
108
|
+
tar = ::Gem::Package::TarReader.new( sio )
|
109
|
+
tar.each do |entry|
|
110
|
+
yield entry
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def copy(name, resource, map, options = {})
|
115
|
+
ex = FPM::Fry::Tar::Extractor.new(logger: @logger)
|
116
|
+
base = File.dirname(resource)
|
117
|
+
read(name, resource) do | entry |
|
118
|
+
file = File.join(base, entry.full_name).chomp('/')
|
119
|
+
file = file.sub(%r"\A\./",'')
|
120
|
+
to = map[file]
|
121
|
+
next unless to
|
122
|
+
@logger.debug("Copy",name: file, to: to)
|
123
|
+
ex.extract_entry(to, entry, options)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def changes(name)
|
128
|
+
res = agent.get(path: url('containers',name,'changes'))
|
129
|
+
raise res.reason if res.status != 200
|
130
|
+
return JSON.parse(res.body)
|
131
|
+
end
|
132
|
+
|
133
|
+
def agent
|
134
|
+
@agent ||= agent_for(docker_url, tls)
|
135
|
+
end
|
136
|
+
|
137
|
+
def broken_symlinks?
|
138
|
+
return true
|
139
|
+
end
|
140
|
+
|
141
|
+
def agent_for( uri, tls )
|
142
|
+
proto, address = uri.split('://',2)
|
143
|
+
options = {
|
144
|
+
instrumentor: LogInstrumentor.new(logger),
|
145
|
+
read_timeout: 10000
|
146
|
+
}.merge( tls )
|
147
|
+
case(proto)
|
148
|
+
when 'unix'
|
149
|
+
uri = "unix:///"
|
150
|
+
options[:socket] = address
|
151
|
+
when 'tcp'
|
152
|
+
if tls.any?
|
153
|
+
return agent_for("https://#{address}", tls)
|
154
|
+
else
|
155
|
+
return agent_for("http://#{address}", tls)
|
156
|
+
end
|
157
|
+
when 'http', 'https'
|
158
|
+
end
|
159
|
+
logger.debug("Creating Agent", options.merge(uri: uri))
|
160
|
+
return Excon.new(uri, options)
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,370 @@
|
|
1
|
+
require 'fpm/fry/command'
|
2
|
+
module FPM; module Fry
|
3
|
+
class Command::Cook < Command
|
4
|
+
|
5
|
+
option '--distribution', 'distribution', 'Distribution like ubuntu-12.04'
|
6
|
+
option '--keep', :flag, 'Keep the container after build'
|
7
|
+
option '--overwrite', :flag, 'Overwrite package', default: true
|
8
|
+
|
9
|
+
UPDATE_VALUES = ['auto','never','always']
|
10
|
+
option '--update',"<#{UPDATE_VALUES.join('|')}>", 'Update image before installing packages ( only apt currently )',attribute_name: 'update', default: 'auto' do |value|
|
11
|
+
if !UPDATE_VALUES.include? value
|
12
|
+
raise "Unknown value for --update: #{value.inspect}\nPossible values are #{UPDATE_VALUES.join(', ')}"
|
13
|
+
else
|
14
|
+
value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
parameter 'image', 'Docker image to build from'
|
19
|
+
parameter '[recipe]', 'Recipe file to cook', default: 'recipe.rb'
|
20
|
+
|
21
|
+
attr :ui
|
22
|
+
extend Forwardable
|
23
|
+
def_delegators :ui, :out, :err, :logger, :tmpdir
|
24
|
+
|
25
|
+
def initialize(invocation_path, ctx = {}, parent_attribute_values = {})
|
26
|
+
@tls = nil
|
27
|
+
require 'digest'
|
28
|
+
require 'fileutils'
|
29
|
+
require 'fpm/fry/recipe'
|
30
|
+
require 'fpm/fry/recipe/builder'
|
31
|
+
require 'fpm/fry/detector'
|
32
|
+
require 'fpm/fry/docker_file'
|
33
|
+
require 'fpm/fry/stream_parser'
|
34
|
+
require 'fpm/fry/os_db'
|
35
|
+
require 'fpm/fry/block_enumerator'
|
36
|
+
require 'fpm/fry/build_output_parser'
|
37
|
+
super
|
38
|
+
@ui = ctx.fetch(:ui){ UI.new }
|
39
|
+
if debug?
|
40
|
+
ui.logger.level = :debug
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def detector
|
45
|
+
@detector || begin
|
46
|
+
if distribution
|
47
|
+
d = Detector::String.new(distribution)
|
48
|
+
else
|
49
|
+
d = Detector::Image.new(client, image)
|
50
|
+
end
|
51
|
+
self.detector=d
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def detector=(d)
|
56
|
+
begin
|
57
|
+
unless d.detect!
|
58
|
+
raise "Unable to detect distribution from given image"
|
59
|
+
end
|
60
|
+
rescue Excon::Errors::NotFound
|
61
|
+
raise "Image not found"
|
62
|
+
end
|
63
|
+
@detector = d
|
64
|
+
end
|
65
|
+
|
66
|
+
def flavour
|
67
|
+
@flavour ||= OsDb.fetch(detector.distribution,{flavour: "unknown"})[:flavour]
|
68
|
+
end
|
69
|
+
attr_writer :flavour
|
70
|
+
|
71
|
+
def output_class
|
72
|
+
@output_class ||= begin
|
73
|
+
logger.info("Autodetecting package type",flavour: flavour)
|
74
|
+
case(flavour)
|
75
|
+
when 'debian'
|
76
|
+
require 'fpm/package/deb'
|
77
|
+
FPM::Package::Deb
|
78
|
+
when 'redhat'
|
79
|
+
require 'fpm/package/rpm'
|
80
|
+
FPM::Package::RPM
|
81
|
+
else
|
82
|
+
raise "Cannot auto-detect package type."
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
attr_writer :output_class
|
87
|
+
|
88
|
+
def builder
|
89
|
+
@builder ||= begin
|
90
|
+
vars = {
|
91
|
+
distribution: detector.distribution,
|
92
|
+
distribution_version: detector.version,
|
93
|
+
flavour: flavour
|
94
|
+
}
|
95
|
+
logger.info("Loading recipe",variables: vars, recipe: recipe)
|
96
|
+
b = Recipe::Builder.new(vars, Recipe.new, logger: ui.logger)
|
97
|
+
b.load_file( recipe )
|
98
|
+
b
|
99
|
+
end
|
100
|
+
end
|
101
|
+
attr_writer :builder
|
102
|
+
|
103
|
+
def cache
|
104
|
+
@cache ||= builder.recipe.source.build_cache(tmpdir)
|
105
|
+
end
|
106
|
+
attr_writer :cache
|
107
|
+
|
108
|
+
def lint_output_class!
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
def lint_recipe_file!
|
113
|
+
File.exists?(recipe) || raise(Recipe::NotFound)
|
114
|
+
end
|
115
|
+
|
116
|
+
def lint_recipe!
|
117
|
+
problems = builder.recipe.lint
|
118
|
+
if problems.any?
|
119
|
+
problems.each do |p|
|
120
|
+
logger.error(p)
|
121
|
+
end
|
122
|
+
raise
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def image_id
|
127
|
+
@image_id ||= begin
|
128
|
+
res = client.get(
|
129
|
+
expects: [200],
|
130
|
+
path: client.url("images/#{image}/json")
|
131
|
+
)
|
132
|
+
body = JSON.parse(res.body)
|
133
|
+
body.fetch('id'){ body.fetch('Id') }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
attr_writer :image_id
|
137
|
+
|
138
|
+
def build_image
|
139
|
+
@build_image ||= begin
|
140
|
+
sum = Digest::SHA256.hexdigest( image_id + "\0" + cache.cachekey )
|
141
|
+
cachetag = "fpm-fry:#{sum[0..30]}"
|
142
|
+
res = client.get(
|
143
|
+
expects: [200,404],
|
144
|
+
path: client.url("images/#{cachetag}/json")
|
145
|
+
)
|
146
|
+
if res.status == 404
|
147
|
+
df = DockerFile::Source.new(builder.variables.merge(image: image_id),cache)
|
148
|
+
client.post(
|
149
|
+
headers: {
|
150
|
+
'Content-Type'=>'application/tar'
|
151
|
+
},
|
152
|
+
expects: [200],
|
153
|
+
path: client.url("build?rm=1&t=#{cachetag}"),
|
154
|
+
request_block: BlockEnumerator.new(df.tar_io)
|
155
|
+
)
|
156
|
+
end
|
157
|
+
|
158
|
+
df = DockerFile::Build.new(cachetag, builder.variables.dup,builder.recipe, update: update?)
|
159
|
+
parser = BuildOutputParser.new(out)
|
160
|
+
res = client.post(
|
161
|
+
headers: {
|
162
|
+
'Content-Type'=>'application/tar'
|
163
|
+
},
|
164
|
+
expects: [200],
|
165
|
+
path: client.url('build?rm=1'),
|
166
|
+
request_block: BlockEnumerator.new(df.tar_io),
|
167
|
+
response_block: parser
|
168
|
+
)
|
169
|
+
if parser.images.none?
|
170
|
+
raise "Unable to detect build image"
|
171
|
+
end
|
172
|
+
image = parser.images.last
|
173
|
+
logger.debug("Detected build image", image: image)
|
174
|
+
image
|
175
|
+
end
|
176
|
+
end
|
177
|
+
attr_writer :build_image
|
178
|
+
|
179
|
+
def update?
|
180
|
+
if flavour == 'debian'
|
181
|
+
case(update)
|
182
|
+
when 'auto'
|
183
|
+
body = JSON.generate({"Image" => image, "Cmd" => "exit 0"})
|
184
|
+
res = client.post( path: client.url('containers','create'),
|
185
|
+
headers: {'Content-Type' => 'application/json'},
|
186
|
+
body: body,
|
187
|
+
expects: [201]
|
188
|
+
)
|
189
|
+
body = JSON.parse(res.body)
|
190
|
+
container = body.fetch('Id')
|
191
|
+
begin
|
192
|
+
client.read( container, '/var/lib/apt/lists') do |file|
|
193
|
+
next if file.header.name == 'lists/'
|
194
|
+
logger.info("/var/lib/apt/lists is not empty, no update is required ( force update with --apt-update=always )")
|
195
|
+
return false
|
196
|
+
end
|
197
|
+
ensure
|
198
|
+
client.delete(path: client.url('containers',container))
|
199
|
+
end
|
200
|
+
logger.info("/var/lib/apt/lists is empty, update is required ( disable update with --apt-update=never )")
|
201
|
+
return true
|
202
|
+
when 'always'
|
203
|
+
return true
|
204
|
+
when 'never'
|
205
|
+
return false
|
206
|
+
end
|
207
|
+
else
|
208
|
+
return false
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def build!
|
213
|
+
res = client.post(
|
214
|
+
headers: {
|
215
|
+
'Content-Type' => 'application/json'
|
216
|
+
},
|
217
|
+
path: client.url('containers','create'),
|
218
|
+
expects: [201],
|
219
|
+
body: JSON.generate({"Image" => build_image})
|
220
|
+
)
|
221
|
+
|
222
|
+
body = JSON.parse(res.body)
|
223
|
+
container = body['Id']
|
224
|
+
begin
|
225
|
+
client.post(
|
226
|
+
headers: {
|
227
|
+
'Content-Type' => 'application/json'
|
228
|
+
},
|
229
|
+
path: client.url('containers',container,'start'),
|
230
|
+
expects: [204],
|
231
|
+
body: JSON.generate({})
|
232
|
+
)
|
233
|
+
|
234
|
+
client.post(
|
235
|
+
path: client.url('containers',container,'attach?stderr=1&stdout=1&stream=1'),
|
236
|
+
body: '',
|
237
|
+
expects: [200],
|
238
|
+
middlewares: [
|
239
|
+
StreamParser.new(out,err),
|
240
|
+
Excon::Middleware::Expects,
|
241
|
+
Excon::Middleware::Instrumentor,
|
242
|
+
Excon::Middleware::Mock
|
243
|
+
]
|
244
|
+
)
|
245
|
+
|
246
|
+
res = client.post(
|
247
|
+
path: client.url('containers',container,'wait'),
|
248
|
+
expects: [200],
|
249
|
+
body: ''
|
250
|
+
)
|
251
|
+
json = JSON.parse(res.body)
|
252
|
+
if json["StatusCode"] != 0
|
253
|
+
raise "Build failed"
|
254
|
+
end
|
255
|
+
return yield container
|
256
|
+
ensure
|
257
|
+
unless keep?
|
258
|
+
client.delete(path: client.url('containers',container))
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def input_package(container)
|
264
|
+
input = FPM::Package::Docker.new(logger: logger, client: client)
|
265
|
+
builder.recipe.apply_input(input)
|
266
|
+
begin
|
267
|
+
input.input(container)
|
268
|
+
return yield(input)
|
269
|
+
ensure
|
270
|
+
input.cleanup_staging
|
271
|
+
input.cleanup_build
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def write_output!(output)
|
276
|
+
package_file = File.expand_path(output.to_s(nil))
|
277
|
+
FileUtils.mkdir_p(File.dirname(package_file))
|
278
|
+
tmp_package_file = package_file + '.tmp'
|
279
|
+
begin
|
280
|
+
FileUtils.rm_rf tmp_package_file
|
281
|
+
rescue Errno::ENOENT
|
282
|
+
end
|
283
|
+
|
284
|
+
output.output(tmp_package_file)
|
285
|
+
|
286
|
+
begin
|
287
|
+
FileUtils.rm_rf package_file
|
288
|
+
rescue Errno::ENOENT
|
289
|
+
end
|
290
|
+
File.rename tmp_package_file, package_file
|
291
|
+
|
292
|
+
logger.info("Created package", :path => package_file)
|
293
|
+
end
|
294
|
+
|
295
|
+
def packages
|
296
|
+
dir_map = []
|
297
|
+
out_map = {}
|
298
|
+
|
299
|
+
package_map = builder.recipe.packages.map do | package |
|
300
|
+
output = output_class.new
|
301
|
+
output.instance_variable_set(:@logger,logger)
|
302
|
+
package.files.each do | pattern |
|
303
|
+
dir_map << [ pattern, output.staging_path ]
|
304
|
+
end
|
305
|
+
out_map[ output ] = package
|
306
|
+
end
|
307
|
+
|
308
|
+
dir_map = Hash[ dir_map.reverse ]
|
309
|
+
|
310
|
+
yield dir_map
|
311
|
+
|
312
|
+
out_map.each do |output, package|
|
313
|
+
package.apply_output(output)
|
314
|
+
end
|
315
|
+
|
316
|
+
out_map.each do |output, _|
|
317
|
+
write_output!(output)
|
318
|
+
end
|
319
|
+
|
320
|
+
ensure
|
321
|
+
|
322
|
+
out_map.each do |output, _|
|
323
|
+
output.cleanup_staging
|
324
|
+
output.cleanup_build
|
325
|
+
end
|
326
|
+
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
public
|
331
|
+
|
332
|
+
def execute
|
333
|
+
# force some eager loading
|
334
|
+
lint_recipe_file!
|
335
|
+
detector
|
336
|
+
flavour
|
337
|
+
output_class
|
338
|
+
lint_output_class!
|
339
|
+
builder
|
340
|
+
lint_recipe!
|
341
|
+
cache
|
342
|
+
|
343
|
+
image_id
|
344
|
+
build_image
|
345
|
+
|
346
|
+
packages do | dir_map |
|
347
|
+
|
348
|
+
build! do |container|
|
349
|
+
input_package(container) do |input|
|
350
|
+
input.split( container, dir_map )
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
end
|
355
|
+
|
356
|
+
return 0
|
357
|
+
rescue Recipe::NotFound => e
|
358
|
+
logger.error("Recipe not found", recipe: recipe, exeception: e)
|
359
|
+
return 1
|
360
|
+
rescue => e
|
361
|
+
logger.error(e)
|
362
|
+
return 1
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|
366
|
+
|
367
|
+
class Command
|
368
|
+
subcommand 'cook', 'Cooks a package', Cook
|
369
|
+
end
|
370
|
+
end ; end
|