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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 30f03302993885946a899feba8c6f6a63f9cc46a
4
- data.tar.gz: 6cf9d78c60aca34c1c8feab1b58ddd9234fe28b9
2
+ SHA256:
3
+ metadata.gz: 34c54b8fc6358464ab255cd2e1bcd3fa53ee43f93aa57cef0e141cdfee2bc4de
4
+ data.tar.gz: b1f7127bc3961865d622c3a97178b2b37c2218ccf78ac3128807b70c0ce58fee
5
5
  SHA512:
6
- metadata.gz: d832baed68a8f809a009bcdb7d8addb4847393475f1bc4ae12d5a562210ddbc3506f1e6e310d4798b68197b75a649152ea05449ce2ef18ad4e34f321094efb9d
7
- data.tar.gz: ca77a79b3aad02271b8dd9cf4e9c31d556c7125065ea7b1df0917d3677a433b64b8a32a7bd90e3a527a5f7016533dcd766393472cf5c30cb8b4d99e16fb6107f
6
+ metadata.gz: 9300250c5c7fbd92391b8a4698e34aba6b88840ff7bb6abc5b41c817ac6651d2480088c63edd663d2356ad0ec66ff01bd7733e961ac47412fc9a6a0849e1f51f
7
+ data.tar.gz: 5db79a234b215966c5ef5eadcd60fe8216c7a14291ca3bec42ff585bc01f6417182d235baef7c6d8b636d38d4992a0abb2ccbcecc3ed4878c7cc4a02df74c7b0
@@ -80,8 +80,23 @@ class Cabin::NiceOutput
80
80
  @io.flush
81
81
  end
82
82
 
83
+ private
84
+
83
85
  def pp(hash)
84
- hash.map{|k,v| ' '+k.to_s + ": " + v.inspect }.join("\n")
86
+ hash.map{|k,v| ' '+k.to_s + ": " + pp_value(v) }.join("\n")
87
+ end
88
+
89
+ def pp_value(value)
90
+ case(value)
91
+ when String
92
+ if value.include? "\n"
93
+ return "\n\t" + value.gsub("\n","\n\t")
94
+ else
95
+ return value
96
+ end
97
+ else
98
+ return value.inspect
99
+ end
85
100
  end
86
101
 
87
102
  end
@@ -1,10 +1,13 @@
1
1
  module FPM; module Fry
2
+ # Helper class that reads an IO in chunks.
3
+ #
4
+ # @api private
2
5
  class BlockEnumerator < Struct.new(:io, :blocksize)
3
6
  include Enumerable
4
7
 
5
- # @param io [IO]
6
- # @param blocksize [Numeric]
7
- def initialize(_, blocksize = 128)
8
+ # @param [IO] io
9
+ # @param [Numeric] blocksize
10
+ def initialize(io, blocksize = 128)
8
11
  super
9
12
  end
10
13
 
@@ -10,12 +10,17 @@ module FPM; module Fry
10
10
  end
11
11
 
12
12
  def call(chunk, *_)
13
- json = JSON.parse(chunk)
14
- stream = json['stream']
15
- if /\ASuccessfully built (\w+)\Z/.match(stream)
16
- images << $1
13
+ # new docker for Mac results in data like this:
14
+ # "{'stream':' ---\\u003e 3bc51d6a4c46\\n'}\r\n{'stream':'Step 2 : WORKDIR /tmp/build\\n'}\r\n"
15
+ # this isn't valid JSON, of course, so we process each part individually
16
+ chunk.split("\r\n").each do |sub_chunk|
17
+ json = JSON.parse(sub_chunk)
18
+ stream = json['stream']
19
+ if /\ASuccessfully built (\w+)\Z/.match(stream)
20
+ images << $1
21
+ end
22
+ out << stream
17
23
  end
18
- out << stream
19
24
  end
20
25
 
21
26
  end
@@ -1,17 +1,30 @@
1
1
  require 'cabin/channel'
2
2
  module FPM; module Fry
3
+ # A {Cabin::Channel} with two additional features:
4
+ #
5
+ # - There is a new log level 'hint' which can point users to improvements.
6
+ # - Logging an Exception that responds to #data will merge in the data from
7
+ # this exception. This is used together with {FPM::Fry::WithData}
8
+ #
9
+ # @api internal
3
10
  class Channel < Cabin::Channel
4
11
 
5
12
  module Hint
13
+ # Logs a message with level 'hint'
14
+ #
15
+ # @param [String] message
16
+ # @param [Hash] data
6
17
  def hint( message, data = {} )
7
18
  return unless hint?
8
19
  log(message, data.merge(level: :hint))
9
20
  end
10
21
 
22
+ # True if hints should be displayed
11
23
  def hint?
12
24
  !defined?(@hint) || @hint
13
25
  end
14
26
 
27
+ # Switched hints on or off
15
28
  def hint=( bool )
16
29
  @hint = !!bool
17
30
  end
@@ -14,7 +14,7 @@ module FPM ; module Fry
14
14
 
15
15
  # Returns all directory entries like Dir.entries.
16
16
  # @param [String] path
17
- # @result [Array<String>] entries
17
+ # @return [Array<String>] entries
18
18
  def entries(path)
19
19
  dir = rebase(path)
20
20
  return Dir.entries(dir)
@@ -34,7 +34,7 @@ module FPM ; module Fry
34
34
 
35
35
  # Yields all entries recursively like Find.find.
36
36
  # @param [String] path
37
- # @yields entry
37
+ # @yield entry
38
38
  # @yieldparam [String] entry
39
39
  def find(path, &block)
40
40
  if stat(path).directory?
@@ -1,15 +1,27 @@
1
+ require 'cabin'
1
2
  require 'excon'
2
3
  require 'rubygems/package'
3
4
  require 'json'
4
5
  require 'fileutils'
5
6
  require 'forwardable'
6
7
  require 'fpm/fry/tar'
7
-
8
- module FPM; module Fry; end ; end
9
-
8
+ require 'fpm/fry/with_data'
10
9
  class FPM::Fry::Client
11
10
 
11
+ # Raised when a file wasn't found inside a container
12
12
  class FileNotFound < StandardError
13
+ include FPM::Fry::WithData
14
+ end
15
+
16
+ # Raised when a container wasn't found.
17
+ class ContainerNotFound < StandardError
18
+ include FPM::Fry::WithData
19
+ end
20
+
21
+ # Raised when trying to read file that can't be read e.g. because it's a
22
+ # directory.
23
+ class NotAFile < StandardError
24
+ include FPM::Fry::WithData
13
25
  end
14
26
 
15
27
  extend Forwardable
@@ -41,6 +53,7 @@ class FPM::Fry::Client
41
53
  end
42
54
  end
43
55
 
56
+ # @return [String] docker server api version
44
57
  def server_version
45
58
  @server_version ||= begin
46
59
  res = agent.get(
@@ -51,6 +64,7 @@ class FPM::Fry::Client
51
64
  end
52
65
  end
53
66
 
67
+ # @return [String] docker cert path from environment
54
68
  def self.docker_cert_path
55
69
  ENV.fetch('DOCKER_CERT_PATH',File.join(Dir.home, '.docker'))
56
70
  end
@@ -70,22 +84,71 @@ class FPM::Fry::Client
70
84
 
71
85
  def read(name, resource)
72
86
  return to_enum(:read, name, resource) unless block_given?
73
- res = agent.get(
74
- path: url('containers',name,'archive'),
75
- query: URI.encode_www_form('path' => resource),
76
- headers: { 'Content-Type' => 'application/json' },
77
- expects: [200,404,500]
78
- )
79
- if res.status == 500
80
- raise FileNotFound, "File #{resource.inspect} not found: #{res.body}"
87
+ res = if (server_version['ApiVersion'] < "1.20")
88
+ agent.post(
89
+ path: url('containers', name, 'copy'),
90
+ headers: { 'Content-Type' => 'application/json' },
91
+ body: JSON.generate({'Resource' => resource}),
92
+ expects: [200,404,500]
93
+ )
94
+ else
95
+ agent.get(
96
+ path: url('containers', name, 'archive'),
97
+ headers: { 'Content-Type' => 'application/json' },
98
+ query: {:path => resource},
99
+ expects: [200,404,500]
100
+ )
101
+ end
102
+ if [404,500].include? res.status
103
+ body_message = Hash[JSON.load(res.body).map{|k,v| ["docker.#{k}",v] }] rescue {'docker.message' => res.body}
104
+ body_message['docker.container'] = name
105
+ if body_message['docker.message'] =~ /\ANo such container:/
106
+ raise ContainerNotFound.new("container not found", body_message)
107
+ end
108
+ raise FileNotFound.new("file not found", {'path' => resource}.merge(body_message))
81
109
  end
82
110
  sio = StringIO.new(res.body)
83
- tar = ::Gem::Package::TarReader.new( sio )
111
+ tar = FPM::Fry::Tar::Reader.new( sio )
84
112
  tar.each do |entry|
85
113
  yield entry
86
114
  end
87
115
  end
88
116
 
117
+ # Gets the file contents while following symlinks
118
+ # @param [String] name the container name
119
+ # @param [String] resource the file name
120
+ # @return [String] content
121
+ # @raise [NotAFile] when the file has no readable content
122
+ # @raise [FileNotFound] when the file does not exist
123
+ # @api docker
124
+ def read_content(name, resource)
125
+ read(name, resource) do |file|
126
+ if file.header.typeflag == "2"
127
+ return read_content(name, File.absolute_path(file.header.linkname,File.dirname(resource)))
128
+ end
129
+ if file.header.typeflag != "0"
130
+ raise NotAFile.new("not a file", {'path' => resource})
131
+ end
132
+ return file.read
133
+ end
134
+ end
135
+
136
+ # Gets the target of a symlink
137
+ # @param [String] name the container name
138
+ # @param [String] resource the file name
139
+ # @return [String] target
140
+ # @return [nil] if resource is not a symlink
141
+ # @api docker
142
+ def link_target(name, resource)
143
+ read(name, resource) do |file|
144
+ if file.header.typeflag == "2"
145
+ return File.absolute_path(file.header.linkname,File.dirname(resource))
146
+ end
147
+ return nil
148
+ end
149
+ return nil
150
+ end
151
+
89
152
  def copy(name, resource, map, options = {})
90
153
  ex = FPM::Fry::Tar::Extractor.new(logger: @logger)
91
154
  base = File.dirname(resource)
@@ -105,6 +168,27 @@ class FPM::Fry::Client
105
168
  return JSON.parse(res.body)
106
169
  end
107
170
 
171
+ def pull(image)
172
+ agent.post(path: url('images','create'), query: {'fromImage' => image})
173
+ end
174
+
175
+ def create(image)
176
+ res = agent.post(
177
+ headers: { 'Content-Type' => 'application/json' },
178
+ path: url('containers','create'),
179
+ expects: [201],
180
+ body: JSON.generate('Image' => image)
181
+ )
182
+ return JSON.parse(res.body)['Id']
183
+ end
184
+
185
+ def destroy(container)
186
+ agent.delete(
187
+ path: url('containers',container),
188
+ expects: [204]
189
+ )
190
+ end
191
+
108
192
  def agent
109
193
  @agent ||= agent_for(docker_url, tls)
110
194
  end
@@ -13,6 +13,9 @@ module FPM; module Fry
13
13
  option '--debug', :flag, 'Turns on debugging'
14
14
  option '--[no-]tls', :flag, 'Turns on tls ( default is false for schema unix, tcp and http and true for https )'
15
15
  option '--[no-]tlsverify', :flag, 'Turns off tls peer verification', default:true, environment_variable: 'DOCKER_TLS_VERIFY'
16
+ option ["-t", "--tmpdir"], "PATH", 'Write tmp data to PATH', default: '/tmp/fpm-fry', attribute_name: :dir do |s|
17
+ String(s)
18
+ end
16
19
 
17
20
  subcommand 'fpm', 'Works like fpm but with docker support', FPM::Command
18
21
 
@@ -22,7 +25,8 @@ module FPM; module Fry
22
25
 
23
26
  def initialize(invocation_path, ctx = {}, parent_attribute_values = {})
24
27
  super
25
- @ui = ctx.fetch(:ui){ UI.new }
28
+ @ui = ctx.fetch(:ui){ UI.new(tmpdir: dir) }
29
+ @client = ctx[:client]
26
30
  end
27
31
 
28
32
  def parse(attrs)
@@ -45,48 +49,27 @@ module FPM; module Fry
45
49
 
46
50
  attr_writer :client
47
51
 
48
- subcommand 'detect', 'Detects distribution from an image, a container or a given name' do
52
+ subcommand 'detect', 'Detects distribution from an image' do
49
53
 
50
- option '--image', 'image', 'Docker image to detect'
51
- option '--container', 'container', 'Docker container to detect'
52
- option '--distribution', 'distribution', 'Distribution name to detect'
54
+ parameter 'image', 'Docker image to detect'
53
55
 
54
56
  attr :ui
55
57
  extend Forwardable
56
58
  def_delegators :ui, :logger
57
59
 
58
60
  def execute
59
- require 'fpm/fry/os_db'
61
+ require 'fpm/fry/inspector'
60
62
  require 'fpm/fry/detector'
61
63
 
62
- if image
63
- d = Detector::Image.new(client, image)
64
- elsif distribution
65
- d = Detector::String.new(distribution)
66
- elsif container
67
- d = Detector::Container.new(client, container)
68
- else
69
- logger.error("Please supply either --image, --distribution or --container")
70
- return 1
71
- end
72
-
73
- begin
74
- if d.detect!
75
- data = {distribution: d.distribution, version: d.version}
76
- if i = OsDb[d.distribution]
77
- data[:flavour] = i[:flavour]
78
- else
79
- data[:flavour] = "unknown"
80
- end
81
- logger.info("Detected distribution",data)
64
+ Inspector.for_image(client, image) do | inspector |
65
+ begin
66
+ data = Detector.detect(inspector)
67
+ logger.info("Detected the following parameters",data)
82
68
  return 0
83
- else
84
- logger.error("Detection failed")
85
- return 2
69
+ rescue => e
70
+ logger.error(e)
71
+ return 1
86
72
  end
87
- rescue => e
88
- logger.error(e)
89
- return 3
90
73
  end
91
74
  end
92
75
 
@@ -2,7 +2,6 @@ require 'fpm/fry/command'
2
2
  module FPM; module Fry
3
3
  class Command::Cook < Command
4
4
 
5
- option '--distribution', 'distribution', 'Distribution like ubuntu-12.04'
6
5
  option '--keep', :flag, 'Keep the container after build'
7
6
  option '--overwrite', :flag, 'Overwrite package', default: true
8
7
 
@@ -22,40 +21,19 @@ module FPM; module Fry
22
21
  @tls = nil
23
22
  require 'digest'
24
23
  require 'fileutils'
24
+ require 'fpm/fry/with_data'
25
25
  require 'fpm/fry/recipe'
26
26
  require 'fpm/fry/recipe/builder'
27
27
  require 'fpm/fry/detector'
28
28
  require 'fpm/fry/docker_file'
29
29
  require 'fpm/fry/stream_parser'
30
- require 'fpm/fry/os_db'
31
30
  require 'fpm/fry/block_enumerator'
32
31
  require 'fpm/fry/build_output_parser'
32
+ require 'fpm/fry/inspector'
33
+ require 'fpm/fry/plugin/config'
33
34
  super
34
35
  end
35
36
 
36
- def detector
37
- @detector || begin
38
- if distribution
39
- d = Detector::String.new(distribution)
40
- else
41
- d = Detector::Image.new(client, image)
42
- end
43
- self.detector=d
44
- end
45
- end
46
-
47
- def detector=(d)
48
- unless d.detect!
49
- raise "Unable to detect distribution from given image"
50
- end
51
- @detector = d
52
- end
53
-
54
- def flavour
55
- @flavour ||= OsDb.fetch(detector.distribution,{flavour: "unknown"})[:flavour]
56
- end
57
- attr_writer :flavour
58
-
59
37
  def output_class
60
38
  @output_class ||= begin
61
39
  logger.debug("Autodetecting package type",flavour: flavour)
@@ -75,19 +53,22 @@ module FPM; module Fry
75
53
 
76
54
  def builder
77
55
  @builder ||= begin
78
- vars = {
79
- distribution: detector.distribution,
80
- distribution_version: detector.version,
81
- flavour: flavour
82
- }
83
- logger.debug("Loading recipe",variables: vars, recipe: recipe)
84
- b = Recipe::Builder.new(vars, Recipe.new, logger: ui.logger)
85
- b.load_file( recipe )
56
+ b = nil
57
+ Inspector.for_image(client, image) do |inspector|
58
+ variables = Detector.detect(inspector)
59
+ logger.debug("Loading recipe",variables: variables, recipe: recipe)
60
+ b = Recipe::Builder.new(variables, logger: ui.logger, inspector: inspector)
61
+ b.load_file( recipe )
62
+ end
86
63
  b
87
64
  end
88
65
  end
89
66
  attr_writer :builder
90
67
 
68
+ def flavour
69
+ builder.variables[:flavour]
70
+ end
71
+
91
72
  def cache
92
73
  @cache ||= builder.recipe.source.build_cache(tmpdir)
93
74
  end
@@ -141,6 +122,9 @@ module FPM; module Fry
141
122
  path: client.url("build?rm=1&dockerfile=#{DockerFile::NAME}&t=#{cachetag}"),
142
123
  request_block: BlockEnumerator.new(df.tar_io)
143
124
  )
125
+ else
126
+ # Hack to trigger hints/warnings even when the cache is valid.
127
+ DockerFile::Source.new(builder.variables.merge(image: image_id),cache).send(:file_map)
144
128
  end
145
129
 
146
130
  df = DockerFile::Build.new(cachetag, builder.variables.dup,builder.recipe, update: update?)
@@ -168,22 +152,16 @@ module FPM; module Fry
168
152
  if flavour == 'debian'
169
153
  case(update)
170
154
  when 'auto'
171
- body = JSON.generate({"Image" => image, "Cmd" => "exit 0"})
172
- res = client.post( path: client.url('containers','create'),
173
- headers: {'Content-Type' => 'application/json'},
174
- body: body,
175
- expects: [201]
176
- )
177
- body = JSON.parse(res.body)
178
- container = body.fetch('Id')
179
- begin
180
- client.read( container, '/var/lib/apt/lists') do |file|
181
- next if file.header.name == 'lists/'
182
- logger.hint("/var/lib/apt/lists is not empty, you could try to speed up builds with --update=never", documentation: 'https://github.com/xing/fpm-fry/wiki/The-update-parameter')
183
- return true
155
+ Inspector.for_image(client, image) do |inspector|
156
+ begin
157
+ inspector.read('/var/lib/apt/lists') do |file|
158
+ next if file.header.name == 'lists/'
159
+ logger.hint("/var/lib/apt/lists is not empty, you could try to speed up builds with --update=never", documentation: 'https://github.com/xing/fpm-fry/wiki/The-update-parameter')
160
+ break
161
+ end
162
+ rescue FPM::Fry::Client::FileNotFound
163
+ logger.hint("/var/lib/apt/lists does not exists, so we will autoupdate")
184
164
  end
185
- ensure
186
- client.delete(path: client.url('containers',container))
187
165
  end
188
166
  return true
189
167
  when 'always'
@@ -242,7 +220,7 @@ module FPM; module Fry
242
220
  return yield container
243
221
  ensure
244
222
  unless keep?
245
- client.delete(path: client.url('containers',container))
223
+ client.destroy(container)
246
224
  end
247
225
  end
248
226
  end
@@ -304,6 +282,7 @@ module FPM; module Fry
304
282
 
305
283
  out_map.each do |output, package|
306
284
  package.apply_output(output)
285
+ adjust_package_settings(output)
307
286
  adjust_config_files(output)
308
287
  end
309
288
 
@@ -320,18 +299,24 @@ module FPM; module Fry
320
299
 
321
300
  end
322
301
 
302
+ def adjust_package_settings( output )
303
+ # FPM ignores the file permissions on rpm packages.
304
+ output.attributes[:rpm_use_file_permissions?] = true
305
+ output.attributes[:rpm_user] = 'root'
306
+ output.attributes[:rpm_group] = 'root'
307
+ end
323
308
 
324
309
  def adjust_config_files( output )
325
310
  # FPM flags all files in /etc as config files but only for debian :/.
326
- # Actually this behavior makes sense to me for all packages because it's
327
- # the thing I usually want. By setting this attribute at least the
311
+ # Actually this behavior makes sense to me for all packages because it's
312
+ # the thing I usually want. By setting this attribute at least the
328
313
  # misleading warning goes away.
329
314
  output.attributes[:deb_no_default_config_files?] = true
330
315
  output.attributes[:deb_auto_config_files?] = false
331
316
 
332
317
  return if output.attributes[:fry_config_explicitly_used]
333
318
 
334
- # Now that we have disabled this for debian we have to reenable if it for
319
+ # Now that we have disabled this for debian we have to reenable if it for
335
320
  # all.
336
321
  etc = File.expand_path('etc', output.staging_path)
337
322
  if File.exists?( etc )
@@ -359,11 +344,8 @@ module FPM; module Fry
359
344
  def execute
360
345
  # force some eager loading
361
346
  lint_recipe_file!
362
- detector
363
- flavour
364
- output_class
365
- lint_output_class!
366
347
  builder
348
+ lint_output_class!
367
349
  lint_recipe!
368
350
  cache
369
351