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