fpm-fry 0.2.2 → 0.3.0

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
2
  SHA1:
3
- metadata.gz: 30f03302993885946a899feba8c6f6a63f9cc46a
4
- data.tar.gz: 6cf9d78c60aca34c1c8feab1b58ddd9234fe28b9
3
+ metadata.gz: de6bc3a9f29acd7c147a4016c4906fd0a35c6481
4
+ data.tar.gz: afece6f8aea44147df5e357015115f9c74614071
5
5
  SHA512:
6
- metadata.gz: d832baed68a8f809a009bcdb7d8addb4847393475f1bc4ae12d5a562210ddbc3506f1e6e310d4798b68197b75a649152ea05449ce2ef18ad4e34f321094efb9d
7
- data.tar.gz: ca77a79b3aad02271b8dd9cf4e9c31d556c7125065ea7b1df0917d3677a433b64b8a32a7bd90e3a527a5f7016533dcd766393472cf5c30cb8b4d99e16fb6107f
6
+ metadata.gz: 3fea3ee2114b957eca0789a06faab401d88ff9269bf95ffab42ce79f367716d4b962b964e44aa03ef58087c50c1df5c904195f1ac44cde705bc17c5c0a9d2e63
7
+ data.tar.gz: ccee1a75f08a186ff5a44e673d297ca41e2ce6faee2b4df57715542eafbf8640b605e0f7c619958f8b4a7967abd76b8192eadd6e72d5ffcecb07ab1ca27c9949
@@ -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
 
@@ -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
@@ -72,12 +86,17 @@ class FPM::Fry::Client
72
86
  return to_enum(:read, name, resource) unless block_given?
73
87
  res = agent.get(
74
88
  path: url('containers',name,'archive'),
75
- query: URI.encode_www_form('path' => resource),
89
+ query: {'path' => resource},
76
90
  headers: { 'Content-Type' => 'application/json' },
77
91
  expects: [200,404,500]
78
92
  )
79
- if res.status == 500
80
- raise FileNotFound, "File #{resource.inspect} not found: #{res.body}"
93
+ if [404,500].include? res.status
94
+ body_message = Hash[JSON.load(res.body).map{|k,v| ["docker.#{k}",v] }] rescue {'docker.message' => res.body}
95
+ body_message['docker.container'] = name
96
+ if body_message['docker.message'] =~ /\ANo such container:/
97
+ raise ContainerNotFound.new("container not found", body_message)
98
+ end
99
+ raise FileNotFound.new("file not found", {'path' => resource}.merge(body_message))
81
100
  end
82
101
  sio = StringIO.new(res.body)
83
102
  tar = ::Gem::Package::TarReader.new( sio )
@@ -86,6 +105,41 @@ class FPM::Fry::Client
86
105
  end
87
106
  end
88
107
 
108
+ # Gets the file contents while following symlinks
109
+ # @param [String] name the container name
110
+ # @param [String] resource the file name
111
+ # @return [String] content
112
+ # @raise [NotAFile] when the file has no readable content
113
+ # @raise [FileNotFound] when the file does not exist
114
+ # @api docker
115
+ def read_content(name, resource)
116
+ read(name, resource) do |file|
117
+ if file.header.typeflag == "2"
118
+ return read_content(name, File.absolute_path(file.header.linkname,File.dirname(resource)))
119
+ end
120
+ if file.header.typeflag != "0"
121
+ raise NotAFile.new("not a file", {'path' => resource})
122
+ end
123
+ return file.read
124
+ end
125
+ end
126
+
127
+ # Gets the target of a symlink
128
+ # @param [String] name the container name
129
+ # @param [String] resource the file name
130
+ # @return [String] target
131
+ # @return [nil] if resource is not a symlink
132
+ # @api docker
133
+ def link_target(name, resource)
134
+ read(name, resource) do |file|
135
+ if file.header.typeflag == "2"
136
+ return File.absolute_path(file.header.linkname,File.dirname(resource))
137
+ end
138
+ return nil
139
+ end
140
+ return nil
141
+ end
142
+
89
143
  def copy(name, resource, map, options = {})
90
144
  ex = FPM::Fry::Tar::Extractor.new(logger: @logger)
91
145
  base = File.dirname(resource)
@@ -105,6 +159,27 @@ class FPM::Fry::Client
105
159
  return JSON.parse(res.body)
106
160
  end
107
161
 
162
+ def pull(image)
163
+ agent.post(path: url('images','create'), query: {'fromImage' => image})
164
+ end
165
+
166
+ def create(image)
167
+ res = agent.post(
168
+ headers: { 'Content-Type' => 'application/json' },
169
+ path: url('containers','create'),
170
+ expects: [201],
171
+ body: JSON.generate('Image' => image)
172
+ )
173
+ return JSON.parse(res.body)['Id']
174
+ end
175
+
176
+ def destroy(container)
177
+ agent.delete(
178
+ path: url('containers',container),
179
+ expects: [204]
180
+ )
181
+ end
182
+
108
183
  def agent
109
184
  @agent ||= agent_for(docker_url, tls)
110
185
  end
@@ -23,6 +23,7 @@ module FPM; module Fry
23
23
  def initialize(invocation_path, ctx = {}, parent_attribute_values = {})
24
24
  super
25
25
  @ui = ctx.fetch(:ui){ UI.new }
26
+ @client = ctx[:client]
26
27
  end
27
28
 
28
29
  def parse(attrs)
@@ -45,48 +46,27 @@ module FPM; module Fry
45
46
 
46
47
  attr_writer :client
47
48
 
48
- subcommand 'detect', 'Detects distribution from an image, a container or a given name' do
49
+ subcommand 'detect', 'Detects distribution from an image' do
49
50
 
50
- option '--image', 'image', 'Docker image to detect'
51
- option '--container', 'container', 'Docker container to detect'
52
- option '--distribution', 'distribution', 'Distribution name to detect'
51
+ parameter 'image', 'Docker image to detect'
53
52
 
54
53
  attr :ui
55
54
  extend Forwardable
56
55
  def_delegators :ui, :logger
57
56
 
58
57
  def execute
59
- require 'fpm/fry/os_db'
58
+ require 'fpm/fry/inspector'
60
59
  require 'fpm/fry/detector'
61
60
 
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)
61
+ Inspector.for_image(client, image) do | inspector |
62
+ begin
63
+ data = Detector.detect(inspector)
64
+ logger.info("Detected the following parameters",data)
82
65
  return 0
83
- else
84
- logger.error("Detection failed")
85
- return 2
66
+ rescue => e
67
+ logger.error(e)
68
+ return 1
86
69
  end
87
- rescue => e
88
- logger.error(e)
89
- return 3
90
70
  end
91
71
  end
92
72
 
@@ -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
@@ -168,22 +149,12 @@ module FPM; module Fry
168
149
  if flavour == 'debian'
169
150
  case(update)
170
151
  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|
152
+ Inspector.for_image(client, image) do |inspector|
153
+ inspector.read('/var/lib/apt/lists') do |file|
181
154
  next if file.header.name == 'lists/'
182
155
  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
156
+ break
184
157
  end
185
- ensure
186
- client.delete(path: client.url('containers',container))
187
158
  end
188
159
  return true
189
160
  when 'always'
@@ -242,7 +213,7 @@ module FPM; module Fry
242
213
  return yield container
243
214
  ensure
244
215
  unless keep?
245
- client.delete(path: client.url('containers',container))
216
+ client.destroy(container)
246
217
  end
247
218
  end
248
219
  end
@@ -359,11 +330,8 @@ module FPM; module Fry
359
330
  def execute
360
331
  # force some eager loading
361
332
  lint_recipe_file!
362
- detector
363
- flavour
364
- output_class
365
- lint_output_class!
366
333
  builder
334
+ lint_output_class!
367
335
  lint_recipe!
368
336
  cache
369
337
 
@@ -1,117 +1,62 @@
1
- require 'fpm/fry/client'
1
+ require 'fpm/fry/detector'
2
2
  module FPM; module Fry
3
3
 
4
4
  module Detector
5
-
6
- class String < Struct.new(:value)
7
- attr :distribution
8
- attr :version
9
-
10
- def detect!
11
- @distribution, @version = value.split('-',2)
12
- return true
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
- end
16
-
17
- class Container < Struct.new(:client,:container)
18
- attr :distribution
19
- attr :version
20
-
21
- def detect!
22
- begin
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
- return false
27
+ rescue Client::FileNotFound
61
28
  end
62
29
 
63
- private
64
- def detect_redhat_release(file)
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 /\A(\w+) release ([\d\.]+)/ then
68
- @distribution = $1.strip.downcase
69
- @version = $2.strip
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
- end
74
-
75
- class Image < Struct.new(:client,:image,:factory)
76
-
77
- class ImageNotFound < StandardError
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
- body = JSON.parse(res.body)
99
- container = body.fetch('Id')
100
- begin
101
- d = factory.new(client,container)
102
- if d.detect!
103
- @distribution = d.distribution
104
- @version = d.version
105
- return true
106
- else
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