fpm-cookery 0.32.0 → 0.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +5 -2
  4. data/CHANGELOG.md +19 -0
  5. data/Rakefile +34 -0
  6. data/docs/index.rst +1 -0
  7. data/docs/pages/using-hiera.rst +285 -0
  8. data/fpm-cookery.gemspec +6 -1
  9. data/lib/fpm/cookery/book.rb +29 -2
  10. data/lib/fpm/cookery/book_hook.rb +1 -0
  11. data/lib/fpm/cookery/chain_packager.rb +4 -2
  12. data/lib/fpm/cookery/cli.rb +45 -5
  13. data/lib/fpm/cookery/config.rb +2 -1
  14. data/lib/fpm/cookery/environment.rb +17 -8
  15. data/lib/fpm/cookery/exceptions.rb +3 -1
  16. data/lib/fpm/cookery/facts.rb +50 -35
  17. data/lib/fpm/cookery/hiera.rb +35 -0
  18. data/lib/fpm/cookery/hiera/defaults.rb +50 -0
  19. data/lib/fpm/cookery/hiera/scope.rb +35 -0
  20. data/lib/fpm/cookery/inheritable_attr.rb +222 -0
  21. data/lib/fpm/cookery/log/hiera.rb +21 -0
  22. data/lib/fpm/cookery/omnibus_packager.rb +4 -2
  23. data/lib/fpm/cookery/package/package.rb +1 -0
  24. data/lib/fpm/cookery/package/version.rb +11 -4
  25. data/lib/fpm/cookery/packager.rb +13 -11
  26. data/lib/fpm/cookery/recipe.rb +167 -105
  27. data/lib/fpm/cookery/source.rb +6 -8
  28. data/lib/fpm/cookery/source_handler.rb +18 -3
  29. data/lib/fpm/cookery/source_handler/curl.rb +2 -2
  30. data/lib/fpm/cookery/source_handler/directory.rb +10 -11
  31. data/lib/fpm/cookery/source_handler/noop.rb +1 -2
  32. data/lib/fpm/cookery/source_handler/svn.rb +1 -1
  33. data/lib/fpm/cookery/version.rb +1 -1
  34. data/lib/hiera/fpm_cookery_logger.rb +12 -0
  35. data/recipes/redis/config/common.yaml +11 -0
  36. data/recipes/redis/config/git_2.4.2_tag.yaml +4 -0
  37. data/recipes/redis/config/git_2.4.yaml +4 -0
  38. data/recipes/redis/config/git_sha_072a905.yaml +4 -0
  39. data/recipes/redis/config/svn_r2400.yaml +4 -0
  40. data/recipes/redis/config/svn_trunk.yaml +3 -0
  41. data/recipes/redis/recipe.rb +2 -27
  42. data/spec/book_spec.rb +34 -0
  43. data/spec/config_spec.rb +19 -0
  44. data/spec/environment_spec.rb +37 -0
  45. data/spec/facts_spec.rb +54 -31
  46. data/spec/fixtures/hiera_config/CentOS.yaml +1 -0
  47. data/spec/fixtures/hiera_config/common.yaml +12 -0
  48. data/spec/fixtures/hiera_config/custom.yaml +3 -0
  49. data/spec/fixtures/hiera_config/rpm.yaml +12 -0
  50. data/spec/hiera_spec.rb +158 -0
  51. data/spec/inheritable_attr_spec.rb +202 -0
  52. data/spec/package_dir_spec.rb +37 -0
  53. data/spec/package_maintainer_spec.rb +4 -1
  54. data/spec/package_version_spec.rb +50 -0
  55. data/spec/path_spec.rb +20 -0
  56. data/spec/recipe_spec.rb +161 -56
  57. data/spec/source_integrity_check_spec.rb +7 -6
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/support/shared_context.rb +71 -0
  60. metadata +108 -4
@@ -20,21 +20,19 @@ module FPM
20
20
  @url.scheme.to_s.downcase == 'file'
21
21
  end
22
22
 
23
+ # If the Addressable::URI is empty, there's nothing to fetch
24
+ def fetchable?
25
+ !@url.empty?
26
+ end
27
+
23
28
  def url
24
29
  @url.to_s
25
30
  end
31
+ [:to_s, :to_str].each { |m| alias_method m, :url }
26
32
 
27
33
  def path
28
34
  @url.path
29
35
  end
30
-
31
- def to_s
32
- @url.to_s
33
- end
34
-
35
- def to_str
36
- @url.to_s
37
- end
38
36
  end
39
37
  end
40
38
  end
@@ -7,15 +7,18 @@ require 'fpm/cookery/source_handler/local_path'
7
7
  require 'fpm/cookery/source_handler/noop'
8
8
  require 'fpm/cookery/source_handler/directory'
9
9
  require 'fpm/cookery/log'
10
+ require 'fpm/cookery/exceptions'
10
11
 
11
12
  module FPM
12
13
  module Cookery
13
14
  class SourceHandler
14
15
  DEFAULT_HANDLER = :curl
15
16
  LOCAL_HANDLER = :local_path
17
+ REQUIRED_METHODS = [:fetch, :extract]
16
18
 
17
19
  extend Forwardable
18
20
  def_delegators :@handler, :fetch, :extract, :local_path, :checksum?
21
+ def_delegators :@source, :fetchable?
19
22
 
20
23
  attr_reader :source_url
21
24
 
@@ -44,10 +47,22 @@ module FPM
44
47
 
45
48
  def handler_to_class(provider)
46
49
  begin
47
- self.class.const_get(provider.to_s.split('_').map(&:capitalize).join)
48
- rescue NameError
50
+ maybe_klass = self.class.const_get(provider.to_s.split('_').map(&:capitalize).join)
51
+
52
+ instance_method_map = Hash[maybe_klass.instance_methods.map { |m| [m, true] }]
53
+ missing_methods = REQUIRED_METHODS.find_all { |m| !instance_method_map.key?(m) }
54
+
55
+ unless missing_methods.empty?
56
+ formatted_missing = missing_methods.map { |m| "`#{m}'" }.join(', ')
57
+ message = %{#{maybe_klass} does not implement required method(s): #{formatted_missing}}
58
+ Log.error message
59
+ raise Error::Misconfiguration, message
60
+ end
61
+
62
+ maybe_klass
63
+ rescue NameError => e
49
64
  Log.error "Specified provider #{provider} does not exist."
50
- exit(1)
65
+ raise Error::Misconfiguration, e.message
51
66
  end
52
67
  end
53
68
  end
@@ -37,7 +37,7 @@ module FPM
37
37
 
38
38
  FileUtils.cp_r(local_path, local_path.basename)
39
39
  end
40
- extracted_source
40
+ (builddir/extracted_source).to_s
41
41
  end
42
42
  end
43
43
 
@@ -61,7 +61,7 @@ module FPM
61
61
  files = Dir['*'].select {|dir| File.file?(dir) }
62
62
 
63
63
  if files.size > 0
64
- builddir
64
+ ''
65
65
  else
66
66
  raise "Empty archive! (#{local_path})"
67
67
  end
@@ -1,5 +1,6 @@
1
1
  require 'fpm/cookery/source_handler/template'
2
2
  require 'fpm/cookery/log'
3
+ require 'fpm/cookery/path'
3
4
  require 'fileutils'
4
5
 
5
6
  module FPM
@@ -10,21 +11,19 @@ module FPM
10
11
  NAME = :directory
11
12
 
12
13
  def fetch(config = {})
13
- path = source.path
14
- cached_file = File.join(@cachedir, path)
15
- if File.exist? cached_file
16
- Log.info "Using cached file #{cached_file}"
17
- else
18
- # Exclude source directory
19
- path = File.join(path,'.')
20
- Log.info "Copying #{path} to cache"
21
- FileUtils.cp_r(path, cachedir)
22
- end
23
14
  cachedir
24
15
  end
25
16
 
26
17
  def extract(config = {})
27
- FileUtils.cp_r(File.join(cachedir,'.'), builddir)
18
+ path = FPM::Cookery::Path.new(source.path)
19
+
20
+ unless path.absolute?
21
+ Log.error("Source path needs to be absolute: #{source.path}")
22
+ raise "Source path needs to be absolute: #{source.path}"
23
+ end
24
+
25
+ Log.info("Copying files from #{path}")
26
+ FileUtils.cp_r(path, builddir)
28
27
  builddir
29
28
  end
30
29
  end
@@ -13,9 +13,8 @@ module FPM
13
13
 
14
14
  def extract(config = {})
15
15
  Log.info "Not extracting - noop source handler"
16
- builddir
16
+ builddir.to_s
17
17
  end
18
-
19
18
  end
20
19
  end
21
20
  end
@@ -19,7 +19,7 @@ module FPM
19
19
  def extract(config = {})
20
20
  Dir.chdir(builddir) do
21
21
  safesystem('cp', '-Rp', local_path, '.')
22
- extracted_source
22
+ (builddir/extracted_source).to_s
23
23
  end
24
24
  end
25
25
 
@@ -1,5 +1,5 @@
1
1
  module FPM
2
2
  module Cookery
3
- VERSION = '0.32.0'
3
+ VERSION = '0.33.0'
4
4
  end
5
5
  end
@@ -0,0 +1,12 @@
1
+ require 'fpm/cookery/log/hiera'
2
+
3
+ # Note: this file exists because hiera runs +require "hiera/#{logger}_logger",
4
+ # where +logger+ refers to the name passed as the +:logger+ option to the
5
+ # +Hiera+ class' constructor.
6
+
7
+ # Note: This weird name is due to the relationship between how Hiera looks for the
8
+ # module to load ("require "#{logger}_logger") and how it infers the class
9
+ # name of its logger (Hiera.const_get("#{logger.capitalize}_logger") This
10
+ # class name is what would be constructed if the hiera object were created
11
+ # as follows: Hiera.new(:config => {:logger => "fpm_cookery"}).
12
+ Hiera::Fpm_cookery_logger = FPM::Cookery::Log::Hiera
@@ -0,0 +1,11 @@
1
+ homepage: 'http://redis.io'
2
+
3
+ source: 'http://redis.googlecode.com/files/redis-%{hiera("version")}.tar.gz'
4
+ md5: 'c4b0b5e4953a11a503cb54cf6b09670e'
5
+ name: 'redis-server'
6
+ version: '2.4.2'
7
+ revision: '0' # => redis-server-2.2.5+fpm1
8
+ description: 'An advanced key-value store.'
9
+ conflicts: '%{hiera("name")}'
10
+ config_files: '/etc/redis/redis.conf'
11
+ patches: 'patches/test.patch'
@@ -0,0 +1,4 @@
1
+ source:
2
+ - 'https://github.com/antirez/redis'
3
+ - :with: :git
4
+ :tag: '2.4.2'
@@ -0,0 +1,4 @@
1
+ source:
2
+ - 'https://github.com/antirez/redis'
3
+ - :with: :git
4
+ :branch: '2.4'
@@ -0,0 +1,4 @@
1
+ source:
2
+ - 'https://github.com/antirez/redis'
3
+ - :with: :git
4
+ :sha: '072a905'
@@ -0,0 +1,4 @@
1
+ source:
2
+ - 'https://github.com/antirez/redis/trunk'
3
+ - :with :svn
4
+ :revision: '2400'
@@ -0,0 +1,3 @@
1
+ source:
2
+ - 'https://github.com/antirez/redis/trunk'
3
+ - :with: :svn
@@ -1,30 +1,4 @@
1
1
  class Redis < FPM::Cookery::Recipe
2
- homepage 'http://redis.io'
3
-
4
- # Different source methods.
5
- #
6
- #source 'https://github.com/antirez/redis/trunk', :with => :svn
7
- #source 'https://github.com/antirez/redis/trunk', :with => :svn, :revision => '2400'
8
- #
9
- #source 'https://github.com/antirez/redis', :with => :git, :tag => '2.4.2
10
- #source 'https://github.com/antirez/redis', :with => :git, :branch => '2.4'
11
- #source 'https://github.com/antirez/redis', :with => :git, :sha => '072a905'
12
-
13
- source 'http://redis.googlecode.com/files/redis-2.4.2.tar.gz'
14
- md5 'c4b0b5e4953a11a503cb54cf6b09670e'
15
-
16
- name 'redis-server'
17
- version '2.4.2'
18
- # revision '0' # => redis-server-2.2.5+fpm1
19
-
20
- description 'An advanced key-value store.'
21
-
22
- conflicts 'redis-server'
23
-
24
- config_files '/etc/redis/redis.conf'
25
-
26
- patches 'patches/test.patch'
27
-
28
2
  def build
29
3
  make
30
4
 
@@ -34,7 +8,8 @@ class Redis < FPM::Cookery::Recipe
34
8
  end
35
9
 
36
10
  def install
37
- # make :install, 'DESTDIR' => destdir
11
+ # C'mon, redis, what's up with not respecting DESTDIR?
12
+ make :install, 'PREFIX' => destdir / 'usr'
38
13
 
39
14
  var('lib/redis').mkdir
40
15
 
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ require 'fpm/cookery/book'
4
+
5
+ describe "Book" do
6
+ let(:book) { FPM::Cookery::Book.instance }
7
+ let(:config) do
8
+ attrs = {
9
+ :debug => false,
10
+ :hiera_config => {
11
+ # Silence logging
12
+ :logger => :noop
13
+ }
14
+ }
15
+
16
+ double('Config', attrs).as_null_object
17
+ end
18
+
19
+ describe ".instance" do
20
+ describe ".load_recipe" do
21
+ context "given an existing file" do
22
+ include_context "temporary recipe", "recipe.rb", <<-RECIPE
23
+ class FakeRecipe < FPM::Cookery::Recipe
24
+ print 'Hello, world!'
25
+ end
26
+ RECIPE
27
+
28
+ it "loads the file" do
29
+ expect { book.load_recipe('recipe.rb', config) { } }.to output('Hello, world!').to_stdout
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -131,10 +131,28 @@ describe 'Config' do
131
131
  common_tests(:cache_dir)
132
132
  end
133
133
 
134
+ describe '#data_dir' do
135
+ it 'defaults to nil' do
136
+ expect(default_config.data_dir).to be_nil
137
+ end
138
+
139
+ common_tests(:data_dir)
140
+ end
141
+
142
+ describe '#hiera_config' do
143
+ it 'defaults to nil' do
144
+ expect(default_config.hiera_config).to be_nil
145
+ end
146
+
147
+ common_tests(:hiera_config)
148
+ end
149
+
134
150
  describe '#to_hash' do
135
151
  it 'returns a hash representation of the object' do
136
152
  expect(default_config.to_hash).to eq({
137
153
  :color => true,
154
+ :data_dir => nil,
155
+ :hiera_config => nil,
138
156
  :debug => false,
139
157
  :target => nil,
140
158
  :platform => nil,
@@ -143,6 +161,7 @@ describe 'Config' do
143
161
  :pkg_dir => nil,
144
162
  :cache_dir => nil,
145
163
  :vendor => nil,
164
+ :vendor_delimiter => nil,
146
165
  :skip_package => false,
147
166
  :keep_destdir => false,
148
167
  :dependency_check => true,
@@ -38,11 +38,30 @@ describe FPM::Cookery::Environment do
38
38
  expect(env.to_hash).to_not have_key('foo')
39
39
  end
40
40
 
41
+ describe '.[]' do
42
+ context 'given a Hash' do
43
+ it 'returns an new instance with keys and values coerced to Strings' do
44
+ expect(described_class[:this => :that]).to eq({"this" => "that"})
45
+ end
46
+ end
47
+
48
+ context 'given an array of two-member arrays' do
49
+ it 'returns an new instance with keys and values coerced to Strings' do
50
+ expected = {"this" => "that", "foo" => "bar"}
51
+ expect(described_class[[[:this, :that], [:foo, :bar]]]).to eq(expected)
52
+ end
53
+ end
54
+ end
55
+
41
56
  describe '#to_hash' do
42
57
  it 'returns the data as a hash' do
43
58
  env['foo'] = 'bar'
44
59
 
45
60
  expect(env.to_hash).to eq({'foo' => 'bar'})
61
+ # Note: not using +be_a+ because +Environment+ inherits from +Hash+, and
62
+ # we want to test that +env.to_hash+ actually is an instance of +Hash+
63
+ # and not an instance of a subclass of +Hash+.
64
+ expect(env.to_hash.class).to eq(Hash)
46
65
  end
47
66
 
48
67
  it 'returns a copy of the data' do
@@ -56,6 +75,24 @@ describe FPM::Cookery::Environment do
56
75
  end
57
76
  end
58
77
 
78
+ describe '#merge' do
79
+ context 'given a Hash' do
80
+ it 'returns a new instance with merged contents' do
81
+ expect(env.merge({:this => :that})).to eq({"this" => "that"})
82
+ end
83
+ end
84
+ end
85
+
86
+ describe '#merge!' do
87
+ context 'given a Hash' do
88
+ let(:env_dup) { env.dup }
89
+ it 'merges their contents in-place' do
90
+ env.merge!({:this => :that})
91
+ expect(env).to eq({"this" => "that"})
92
+ end
93
+ end
94
+ end
95
+
59
96
  describe '#with_clean' do
60
97
  it 'returns the return value of the given block' do
61
98
  expect(env.with_clean { 'nice' }).to eq('nice')
@@ -1,35 +1,48 @@
1
1
  require 'spec_helper'
2
- require 'ostruct'
3
2
  require 'fpm/cookery/facts'
4
3
 
4
+ shared_context "mock facts" do |facts = {}|
5
+ before do
6
+ facts.each_pair do |k, v|
7
+ allow(Facter).to receive(:value).with(k).and_return(v)
8
+ end
9
+ end
10
+ end
11
+
5
12
  describe "Facts" do
6
13
  before do
7
14
  FPM::Cookery::Facts.reset!
8
15
  end
9
16
 
10
17
  describe "arch" do
11
- before do
12
- Facter.class_eval do
13
- def self.fact(v)
14
- v == :architecture ? OpenStruct.new(:value => 'x86_64') : nil
15
- end
16
- end
17
- end
18
+ include_context "mock facts", { :architecture => 'x86_64' }
18
19
 
19
- it "is returns the current platform" do
20
+ it "returns the current architecture" do
20
21
  expect(FPM::Cookery::Facts.arch).to eq(:x86_64)
21
22
  end
22
23
  end
23
24
 
24
- describe "platform" do
25
- before do
26
- Facter.class_eval do
27
- def self.fact(v)
28
- v == :operatingsystem ? OpenStruct.new(:value => 'CentOS') : nil
29
- end
25
+ describe "lsbcodename" do
26
+ context "where lsbcodename is present" do
27
+ include_context "mock facts", { :lsbcodename => 'trusty' }
28
+
29
+ it "returns the current platforms codename" do
30
+ expect(FPM::Cookery::Facts.lsbcodename).to eq :trusty
30
31
  end
31
32
  end
32
33
 
34
+ context "where lsbcodename is not present" do
35
+ include_context "mock facts", { :lsbcodename => nil }
36
+
37
+ it "returns nil" do
38
+ expect(FPM::Cookery::Facts.lsbcodename).to be_nil
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "platform" do
44
+ include_context "mock facts", { :operatingsystem => 'CentOS' }
45
+
33
46
  it "is using Facter to autodetect the platform" do
34
47
  expect(FPM::Cookery::Facts.platform).to eq(:centos)
35
48
  end
@@ -41,13 +54,7 @@ describe "Facts" do
41
54
  end
42
55
 
43
56
  describe "osrelease" do
44
- before do
45
- Facter.class_eval do
46
- def self.fact(v)
47
- v == :operatingsystemrelease ? OpenStruct.new(:value => '6.5') : nil
48
- end
49
- end
50
- end
57
+ include_context "mock facts", { :operatingsystemrelease => '6.5' }
51
58
 
52
59
  it "returns the operating system release version" do
53
60
  expect(FPM::Cookery::Facts.osrelease).to eq('6.5')
@@ -55,13 +62,7 @@ describe "Facts" do
55
62
  end
56
63
 
57
64
  describe "osmajorrelease" do
58
- before do
59
- Facter.class_eval do
60
- def self.fact(v)
61
- v == :operatingsystemmajrelease ? OpenStruct.new(:value => '6') : nil
62
- end
63
- end
64
- end
65
+ include_context "mock facts", { :operatingsystemmajrelease => '6' }
65
66
 
66
67
  it "returns the operating system major release version" do
67
68
  expect(FPM::Cookery::Facts.osmajorrelease).to eq('6')
@@ -76,6 +77,7 @@ describe "Facts" do
76
77
  expect(FPM::Cookery::Facts.target).to eq(:rpm)
77
78
  end
78
79
  end
80
+
79
81
  describe "with platform CentOS" do
80
82
  it "returns rpm" do
81
83
  FPM::Cookery::Facts.platform = 'CentOS'
@@ -97,15 +99,29 @@ describe "Facts" do
97
99
  end
98
100
  end
99
101
 
100
- describe "with platform Debian" do
102
+ describe "with platform Amazon" do
103
+ it "returns rpm" do
104
+ FPM::Cookery::Facts.platform = 'Amazon'
105
+ expect(FPM::Cookery::Facts.target).to eq(:rpm)
106
+ end
107
+ end
108
+
109
+ describe "with platform OracleLinux" do
101
110
  it "returns rpm" do
111
+ FPM::Cookery::Facts.platform = 'OracleLinux'
112
+ expect(FPM::Cookery::Facts.target).to eq(:rpm)
113
+ end
114
+ end
115
+
116
+ describe "with platform Debian" do
117
+ it "returns deb" do
102
118
  FPM::Cookery::Facts.platform = 'Debian'
103
119
  expect(FPM::Cookery::Facts.target).to eq(:deb)
104
120
  end
105
121
  end
106
122
 
107
123
  describe "with platform Ubuntu" do
108
- it "returns rpm" do
124
+ it "returns deb" do
109
125
  FPM::Cookery::Facts.platform = 'Ubuntu'
110
126
  expect(FPM::Cookery::Facts.target).to eq(:deb)
111
127
  end
@@ -118,6 +134,13 @@ describe "Facts" do
118
134
  end
119
135
  end
120
136
 
137
+ describe "with platform Alpine" do
138
+ it "returns apk" do
139
+ FPM::Cookery::Facts.platform = 'Alpine'
140
+ expect(FPM::Cookery::Facts.target).to eq(:apk)
141
+ end
142
+ end
143
+
121
144
  describe "with an unknown platform" do
122
145
  it "returns nil" do
123
146
  FPM::Cookery::Facts.platform = '___X___'