head_chef 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +38 -0
  8. data/Rakefile +1 -0
  9. data/bin/head-chef +5 -0
  10. data/features/commands/diff.feature +74 -0
  11. data/features/commands/list.feature +20 -0
  12. data/features/commands/sync.feature +67 -0
  13. data/features/step_definitions/berkshelf_steps.rb +40 -0
  14. data/features/step_definitions/chef_server_steps.rb +81 -0
  15. data/features/step_definitions/git_steps.rb +3 -0
  16. data/features/support/env.rb +40 -0
  17. data/head_chef.gemspec +36 -0
  18. data/lib/head_chef/cli.rb +28 -0
  19. data/lib/head_chef/cookbook.rb +115 -0
  20. data/lib/head_chef/cookbook_diff.rb +80 -0
  21. data/lib/head_chef/tasks/diff.rb +42 -0
  22. data/lib/head_chef/tasks/env.rb +48 -0
  23. data/lib/head_chef/tasks/list.rb +17 -0
  24. data/lib/head_chef/tasks/sync.rb +38 -0
  25. data/lib/head_chef/tasks.rb +3 -0
  26. data/lib/head_chef/ui.rb +23 -0
  27. data/lib/head_chef/version.rb +3 -0
  28. data/lib/head_chef.rb +63 -0
  29. data/spec/cookbook_diff_spec.rb +77 -0
  30. data/spec/cookbook_spec.rb +60 -0
  31. data/spec/diff_spec.rb +38 -0
  32. data/spec/env_spec.rb +94 -0
  33. data/spec/fixtures/Berksfiles/default +3 -0
  34. data/spec/fixtures/Berksfiles/template.erb +5 -0
  35. data/spec/fixtures/cookbooks/test_cookbook/metadata.rb +2 -0
  36. data/spec/fixtures/cookbooks/test_cookbook/recipes/default.rb +8 -0
  37. data/spec/fixtures/cookbooks/test_cookbook_file_content_conflict/metadata.rb +2 -0
  38. data/spec/fixtures/cookbooks/test_cookbook_file_content_conflict/recipes/default.rb +10 -0
  39. data/spec/fixtures/cookbooks/test_cookbook_file_list_conflict/chefignore +2 -0
  40. data/spec/fixtures/cookbooks/test_cookbook_file_list_conflict/metadata.rb +2 -0
  41. data/spec/fixtures/cookbooks/test_cookbook_file_list_conflict/recipes/default.rb +10 -0
  42. data/spec/fixtures/cookbooks/test_cookbook_file_list_conflict/recipes/not_default.rb +1 -0
  43. data/spec/fixtures/dot_chef/head_chef.pem +27 -0
  44. data/spec/fixtures/dot_chef/knife.rb +4 -0
  45. data/spec/head_chef_spec.rb +79 -0
  46. data/spec/list_spec.rb +26 -0
  47. data/spec/spec_helper.rb +15 -0
  48. data/spec/support/chef_server.rb +100 -0
  49. data/spec/support/path_helpers.rb +38 -0
  50. data/spec/sync_spec.rb +86 -0
  51. metadata +319 -0
@@ -0,0 +1,80 @@
1
+ module HeadChef
2
+ class CookbookDiff
3
+ attr_reader :diff_hash
4
+
5
+ def initialize
6
+ @diff_hash = { add: [],
7
+ update: [],
8
+ remove: [],
9
+ revert: [],
10
+ conflict: [] }
11
+ end
12
+
13
+ # @TODO: cleanup
14
+ # @TODO: switch statements
15
+ def add(cookbook)
16
+ # Removal is the only operation that does not require a diff, as no
17
+ # cookbook will be uploaded
18
+ if cookbook.chef_version && !cookbook.berkshelf_version
19
+ @diff_hash[:remove] << cookbook and return
20
+ end
21
+
22
+ unless cookbook.diff
23
+ @diff_hash[:conflict] << cookbook and return
24
+ end
25
+
26
+ if cookbook.berkshelf_version && !cookbook.chef_version
27
+ @diff_hash[:add] << cookbook and return
28
+ end
29
+
30
+ berkshelf_version = Semantic::Version.new(cookbook.berkshelf_version)
31
+ chef_version = Semantic::Version.new(cookbook.chef_version)
32
+
33
+ if berkshelf_version > chef_version
34
+ @diff_hash[:update] << cookbook and return
35
+ elsif berkshelf_version < chef_version
36
+ @diff_hash[:revert] << cookbook and return
37
+ end
38
+ end
39
+
40
+ def conflicts
41
+ @diff_hash[:conflict]
42
+ end
43
+
44
+ def conflicts?
45
+ !@diff_hash[:conflict].empty?
46
+ end
47
+
48
+ def empty?
49
+ [:add, :update, :remove, :revert, :conflict].each do |method|
50
+ return false if !@diff_hash[method].empty?
51
+ end
52
+
53
+ true
54
+ end
55
+
56
+ def pretty_print
57
+ if self.empty?
58
+ HeadChef.ui.say("Berksfile and Chef environment are identical", :green)
59
+ return
60
+ end
61
+
62
+ colors = { add: :green,
63
+ update: :green,
64
+ remove: :red,
65
+ revert: :red,
66
+ conflict: :red }
67
+
68
+ [:add, :update, :remove, :revert, :conflict].each do |method|
69
+ color = colors[method]
70
+
71
+ unless @diff_hash[method].empty?
72
+ HeadChef.ui.say("#{method.to_s.upcase}:", color)
73
+ diff_hash[method].each do |cookbook|
74
+ HeadChef.ui.say(" #{cookbook.to_s}", color)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,42 @@
1
+ module HeadChef
2
+ class Diff
3
+ def self.diff(environment)
4
+ HeadChef.ui.info("Loading environment #{environment} from chef server...")
5
+ chef_environment = HeadChef.chef_server.environment.find(environment)
6
+
7
+ if chef_environment
8
+ chef_versions = chef_environment.cookbook_versions
9
+ else
10
+ HeadChef.ui.error("Environment #{environment} not found on chef server.")
11
+ Kernel.exit(1337)
12
+ end
13
+
14
+ # Run berks install to populate cached cookbook list
15
+ # @NOTE: for now it is up to user to maintain Berksfile
16
+ HeadChef.ui.info('Loading cookbooks from berkshelf...')
17
+ cached_cookbooks = Berkshelf.ui.mute { HeadChef.berksfile.install }
18
+
19
+ HeadChef.ui.say('Calculating diff...', :cyan)
20
+ cookbook_diff = CookbookDiff.new
21
+
22
+ cached_cookbooks.each do |berkshelf_cookbook|
23
+ cookbook_name = berkshelf_cookbook.name.chomp("-#{berkshelf_cookbook.version}")
24
+
25
+ if chef_versions[cookbook_name]
26
+ chef_version = chef_versions[cookbook_name]
27
+ else
28
+ chef_version = nil
29
+ end
30
+
31
+ cookbook_diff.add(Cookbook.new(cookbook_name, berkshelf_cookbook.version, chef_version))
32
+ chef_versions.delete(cookbook_name)
33
+ end
34
+
35
+ chef_versions.each do |cookbook_name, cookbook_version|
36
+ cookbook_diff.add(Cookbook.new(cookbook_name, nil, cookbook_version))
37
+ end
38
+
39
+ cookbook_diff
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,48 @@
1
+ module HeadChef
2
+ class Env < Thor
3
+ class_option :environment,
4
+ aliases: '-e',
5
+ banner: '<environment>',
6
+ desc: 'Applies to the specified environment',
7
+ type: :string
8
+
9
+ desc 'diff', 'Shows cookbook diff between Berksfile and Chef <environment>'
10
+ long_desc <<-EOD
11
+ Shows cookbook version diff between Berksfile and Chef <environment>
12
+
13
+ By default, matches current git branch name against Chef environment.
14
+ EOD
15
+ def diff
16
+ environment = options[:environment] || HeadChef.current_branch
17
+
18
+ Diff.diff(environment).pretty_print
19
+ end
20
+
21
+ desc 'list', 'Lists cookbooks with versions from Chef <environment>.'
22
+ long_desc <<-EOD
23
+ Shows cookbook version diff between Berksfile and Chef <environment>
24
+
25
+ By default, matches current git branch name against Chef enviroment.
26
+ EOD
27
+ def list
28
+ environment = options[:environment] || HeadChef.current_branch
29
+
30
+ List.list(environment)
31
+ end
32
+
33
+ desc 'sync', 'Syncs Berksfile with Chef <environment>'
34
+ long_desc <<-EOD
35
+ Syncs Berksfile cookbook with Chef <environment>
36
+
37
+ By default, matches current git branch and against Chef enviroment. Chef
38
+ environment will be created if it does not exist.
39
+ EOD
40
+ option :force, banner: '', desc: 'Force upload of cookbooks to chef server'
41
+ def sync
42
+ environment = options[:environment] || HeadChef.current_branch
43
+ force = options[:force] ? true : false
44
+
45
+ Sync.sync(environment, force)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ module HeadChef
2
+ class List
3
+ def self.list(environment)
4
+ chef_environment = HeadChef.chef_server.environment.find(environment)
5
+
6
+ unless chef_environment
7
+ HeadChef.ui.error "Environment #{environment} not found on chef server."
8
+ Kernel.exit(1337)
9
+ end
10
+
11
+ HeadChef.ui.say("COOKBOOKS:")
12
+ chef_environment.cookbook_versions.sort.each do |cookbook, version|
13
+ HeadChef.ui.say(" #{cookbook}: #{version}")
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ module HeadChef
2
+ class Sync
3
+ def self.sync(environment, force)
4
+ # Check if environment exits, if not create it
5
+ # Perform first, if it fails no need to continue
6
+ unless HeadChef.chef_server.environment.find(environment)
7
+ HeadChef.chef_server.environment.create(name: environment)
8
+ end
9
+
10
+ # Diff now performs all Berkshelf/lockfile dependency operations
11
+ HeadChef.ui.say("Determing side effects of sync with chef environment "\
12
+ "#{environment}...", :cyan)
13
+ cookbook_diff = HeadChef.ui.mute { Diff.diff(environment) }
14
+
15
+ unless force
16
+ if cookbook_diff.conflicts?
17
+ HeadChef.ui.error 'The following cookbooks are in conflict:'
18
+ cookbook_diff.conflicts.each do |cookbook|
19
+ HeadChef.ui.error "#{cookbook.name}: #{cookbook.berkshelf_version}"
20
+ end
21
+ HeadChef.ui.error 'Use --force to sync environment'
22
+ Kernel.exit(1337)
23
+ end
24
+ end
25
+
26
+ # Retrieve berksfile
27
+ berksfile = HeadChef.berksfile
28
+
29
+ HeadChef.ui.say('Uploading cookbooks to chef server...', :cyan)
30
+ berksfile.upload({force: force})
31
+
32
+ # Apply without lock options argument
33
+ HeadChef.ui.say("Applying Berksfile.lock cookbook version to " \
34
+ "environment #{environment}...", :cyan)
35
+ berksfile.apply(environment, {})
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rb"].sort.each do |path|
2
+ require_relative "tasks/#{File.basename(path, '.rb')}"
3
+ end
@@ -0,0 +1,23 @@
1
+ module HeadChef
2
+ module UI
3
+ def mute!
4
+ @mute = true
5
+ end
6
+
7
+ def unmute!
8
+ @mute = false
9
+ end
10
+
11
+ def error(message, color = :red)
12
+ message = set_color(message, *color) if color
13
+ super(message)
14
+ end
15
+
16
+ def info(message, color = :cyan)
17
+ message = set_color(message, *color) if color
18
+ super(message)
19
+ end
20
+ end
21
+ end
22
+
23
+ Thor::Base.shell.send(:include, HeadChef::UI)
@@ -0,0 +1,3 @@
1
+ module HeadChef
2
+ VERSION = '0.1.0'
3
+ end
data/lib/head_chef.rb ADDED
@@ -0,0 +1,63 @@
1
+ # external requires
2
+ require 'berkshelf'
3
+ require 'ridley'
4
+ require 'grit'
5
+ require 'thor'
6
+ require 'semantic'
7
+ require 'pathname'
8
+
9
+ # internal requires
10
+ require_relative 'head_chef/tasks'
11
+ require_relative 'head_chef/cookbook'
12
+ require_relative 'head_chef/cookbook_diff'
13
+ require_relative 'head_chef/ui'
14
+ require_relative 'head_chef/version'
15
+
16
+ #@TODO: establish head_chef exit codes
17
+ #Create custom errors
18
+ module HeadChef
19
+
20
+ BERKSFILE_LOCATION = 'Berksfile'.freeze
21
+ BERKSFILE_COOKBOOK_DIR = '.head_chef'.freeze
22
+
23
+ class << self
24
+ def root
25
+ @root ||= Pathname.new(File.expand_path('../', File.dirname(__FILE__)))
26
+ end
27
+
28
+ def ui
29
+ @ui ||= Thor::Base.shell.new
30
+ end
31
+
32
+ def chef_server
33
+ @chef_server ||= Ridley.from_chef_config()
34
+ end
35
+
36
+ # @TODO: refactor?
37
+ # Is grit necessary to get current branch, is shell command sufficient?
38
+ # This can look up dir's until it finds .git dir
39
+ def master_cookbook
40
+ begin
41
+ @master_cookbook ||= Grit::Repo.new('.')
42
+ rescue Grit::InvalidGitRepositoryError
43
+ puts Dir.pwd
44
+ HeadChef.ui.error 'head_chef must be run in root of git repo'
45
+ Kernel.exit(1337)
46
+ end
47
+ end
48
+
49
+ def current_branch
50
+ master_cookbook.head.name
51
+ end
52
+
53
+ def berksfile
54
+ @berksfile ||= Berkshelf::Berksfile.from_file(BERKSFILE_LOCATION)
55
+ end
56
+
57
+ def cleanup
58
+ if Dir.exists?(BERKSFILE_COOKBOOK_DIR)
59
+ FileUtils.rm_rf(BERKSFILE_COOKBOOK_DIR)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe HeadChef::CookbookDiff do
4
+ let(:empty_diff_hash) do
5
+ { add: [],
6
+ update: [],
7
+ remove: [],
8
+ revert: [],
9
+ conflict: [] }
10
+ end
11
+
12
+ shared_examples_for 'correct add to CookbookDiff' do |cookbook, method|
13
+ it "appends #{cookbook.name} cookbook to :#{method} list" do
14
+ subject.add(cookbook)
15
+ expect(subject.diff_hash[method]).to include(cookbook)
16
+ end
17
+ end
18
+
19
+ describe 'ClassMethods' do
20
+ describe '#new' do
21
+ it 'initializes hash' do
22
+ expect(subject.diff_hash).to eq(empty_diff_hash)
23
+ end
24
+ end
25
+ end
26
+
27
+ describe 'InstanceMethods' do
28
+ describe '#add(HeadChef::Cookbook)' do
29
+
30
+ context 'with cookbook content conflict' do
31
+ before(:each) do
32
+ HeadChef::Cookbook.any_instance.stub(:diff).and_return(false)
33
+ end
34
+
35
+ it_should_behave_like 'correct add to CookbookDiff',
36
+ HeadChef::Cookbook.new('add_test', '0.0.1', nil), :conflict
37
+
38
+ it_should_behave_like 'correct add to CookbookDiff',
39
+ HeadChef::Cookbook.new('update_test', '0.0.2', '0.0.1'), :conflict
40
+
41
+ it_should_behave_like 'correct add to CookbookDiff',
42
+ HeadChef::Cookbook.new('remove_test', nil, '0.0.1'), :remove
43
+
44
+ it_should_behave_like 'correct add to CookbookDiff',
45
+ HeadChef::Cookbook.new('revert_test', '0.0.1', '0.0.2'), :conflict
46
+
47
+ it_should_behave_like 'correct add to CookbookDiff',
48
+ HeadChef::Cookbook.new('conflict_test', '0.0.1', '0.0.1'), :conflict
49
+ end
50
+
51
+ context 'without cookbook content conflict' do
52
+ before(:each) do
53
+ HeadChef::Cookbook.any_instance.stub(:diff).and_return(true)
54
+ end
55
+
56
+ it_should_behave_like 'correct add to CookbookDiff',
57
+ HeadChef::Cookbook.new('add_test', '0.0.1', nil), :add
58
+
59
+ it_should_behave_like 'correct add to CookbookDiff',
60
+ HeadChef::Cookbook.new('update_test', '0.0.2', '0.0.1'), :update
61
+
62
+ it_should_behave_like 'correct add to CookbookDiff',
63
+ HeadChef::Cookbook.new('remove_test', nil, '0.0.1'), :remove
64
+
65
+ it_should_behave_like 'correct add to CookbookDiff',
66
+ HeadChef::Cookbook.new('revert_test', '0.0.1', '0.0.2'), :revert
67
+
68
+ it 'does nothing when berkshelf version == chef version' do
69
+ cookbook = HeadChef::Cookbook.new('conflict_test', '0.0.1', '0.0.1')
70
+ subject.add(cookbook)
71
+ expect(subject.diff_hash).to eq(empty_diff_hash)
72
+ end
73
+
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe HeadChef::Cookbook do
4
+ let(:cookbook_resource) { double('Ridley::CookbookResouce') }
5
+ let(:cached_cookbooks) { [] }
6
+ let(:cached_cookbook) { double('Berkshelf::CachedCookbook') }
7
+
8
+ subject { HeadChef::Cookbook.new('test', '0.0.1', '0.0.2') }
9
+
10
+ describe 'ClassMethods' do
11
+ describe '::new' do
12
+ it 'reads name' do
13
+ expect(subject.name).to eq('test')
14
+ end
15
+
16
+ it 'reads berkshelf version' do
17
+ expect(subject.berkshelf_version).to eq('0.0.1')
18
+ end
19
+
20
+ it 'reads chef version' do
21
+ expect(subject.chef_version).to eq('0.0.2')
22
+ end
23
+ end
24
+ end
25
+
26
+ describe 'InstanceMethods' do
27
+ describe '#diff' do
28
+ before(:each) do
29
+ HeadChef.stub_chain(:chef_server, :cookbook).
30
+ and_return(cookbook_resource)
31
+
32
+ allow(cookbook_resource).to receive(:find).
33
+ with(subject.name, subject.berkshelf_version).
34
+ and_return(cookbook_resource)
35
+ allow(cookbook_resource).to receive(:manifest).and_return(Hashie::Mash.new)
36
+
37
+ HeadChef.stub_chain(:berksfile, :cached_cookbooks).
38
+ and_return(cached_cookbooks)
39
+ allow(cached_cookbooks).to receive(:find).and_return(cached_cookbook)
40
+
41
+ allow(cached_cookbook).to receive(:path)
42
+ allow(subject).to receive(:remove_ignored_files).and_return([])
43
+ end
44
+
45
+ after(:each) do
46
+ subject.diff
47
+ end
48
+
49
+ it 'retrieves cookbook checksums from chef server' do
50
+ expect(cookbook_resource).to receive(:find).
51
+ with(subject.name, subject.berkshelf_version)
52
+ end
53
+
54
+ it 'loads cookbook from berkshelf cache' do
55
+ expect(cached_cookbooks).to receive(:find)
56
+ end
57
+ end
58
+ end
59
+
60
+ end
data/spec/diff_spec.rb ADDED
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe HeadChef::Diff do
4
+ describe 'ClassMethods' do
5
+ let(:berksfile) { double('Berkshelf::Berksfile') }
6
+ let(:chef_environment) { double('Hashie::Mash') }
7
+ let(:environment) { 'test_env' }
8
+
9
+ describe '::diff' do
10
+ before(:each) do
11
+ HeadChef.stub_chain(:chef_server, :environment, :find).
12
+ with(environment).
13
+ and_return(chef_environment)
14
+
15
+ allow(chef_environment).to receive(:cookbook_versions).and_return({})
16
+
17
+ allow(HeadChef).to receive(:berksfile).and_return(berksfile)
18
+ allow(berksfile).to receive(:install).and_return([])
19
+ end
20
+
21
+ after(:each) do
22
+ described_class.diff(environment)
23
+ end
24
+
25
+ it 'loads chef environment' do
26
+ expect(chef_environment).to receive(:cookbook_versions)
27
+ end
28
+
29
+ it 'calls Berksfile#install to load berkshelf cookbooks into cache' do
30
+ expect(berksfile).to receive(:install)
31
+ end
32
+
33
+ it 'returns CookbookDiff' do
34
+ expect(described_class.diff(environment)).to be_an_instance_of(HeadChef::CookbookDiff)
35
+ end
36
+ end
37
+ end
38
+ end
data/spec/env_spec.rb ADDED
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ describe HeadChef::Env do
4
+ let(:current_branch) { 'test_branch' }
5
+ let(:environment) { 'option_environment' }
6
+
7
+ shared_examples_for "HeadChef::Env command" do |klass, method, return_value|
8
+
9
+ context 'defaults' do
10
+ it 'uses branch name for environment' do
11
+ expect(klass).to receive(method) do |*args|
12
+ args[0].should eq(current_branch)
13
+ end.and_return(return_value)
14
+ end
15
+ end
16
+
17
+ context 'with --environment' do
18
+ it 'uses environment option value' do
19
+ subject.options[:environment] = environment
20
+
21
+ expect(klass).to receive(method) do |*args|
22
+ args[0].should eq(environment)
23
+ end.and_return(return_value)
24
+ end
25
+ end
26
+ end
27
+
28
+ describe 'commands' do
29
+
30
+ before(:each) do
31
+ allow(HeadChef).to receive(:current_branch).and_return(current_branch)
32
+
33
+ # Unfreeze Thor::CoreExt::HashWithIndifferentAccess
34
+ subject.options = subject.options.dup
35
+ end
36
+
37
+
38
+ describe '::diff' do
39
+ let(:cookbook_diff) { HeadChef::CookbookDiff.new }
40
+
41
+ after(:each) do
42
+ subject.diff
43
+ end
44
+
45
+ it_should_behave_like 'HeadChef::Env command',
46
+ HeadChef::Diff, :diff, HeadChef::CookbookDiff.new
47
+
48
+ context 'defaults' do
49
+ it 'outputs CookbookDiff' do
50
+ allow(HeadChef::Diff).to receive(:diff).
51
+ with(any_args).and_return(cookbook_diff)
52
+
53
+ expect(cookbook_diff).to receive(:pretty_print)
54
+ end
55
+ end
56
+ end
57
+
58
+ describe '::list' do
59
+ after(:each) do
60
+ subject.list
61
+ end
62
+
63
+ it_should_behave_like 'HeadChef::Env command',
64
+ HeadChef::List, :list, nil
65
+ end
66
+
67
+ describe '::sync' do
68
+ after(:each) do
69
+ subject.sync
70
+ end
71
+
72
+ it_should_behave_like 'HeadChef::Env command',
73
+ HeadChef::Sync, :sync, nil
74
+
75
+ context 'defaults' do
76
+ it 'uses false for force option' do
77
+ expect(HeadChef::Sync).to receive(:sync) do |*args|
78
+ args[1].should eq(false)
79
+ end
80
+ end
81
+ end
82
+
83
+ context 'with --force' do
84
+ it 'sets force option to true' do
85
+ subject.options[:force] = true
86
+
87
+ expect(HeadChef::Sync).to receive(:sync) do |*args|
88
+ args[1].should eq(true)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,3 @@
1
+ site :opscode
2
+
3
+ cookbook 'java'
@@ -0,0 +1,5 @@
1
+ site :opscode
2
+
3
+ <% @cookbooks.each do |cookbook| %>
4
+ <%= "cookbook '#{cookbook[0]}', '#{cookbook[1]}', #{cookbook[2]}" %>
5
+ <% end %>
@@ -0,0 +1,2 @@
1
+ name 'test_cookbook'
2
+ version '0.1.0'
@@ -0,0 +1,8 @@
1
+ #
2
+ # Cookbook Name:: test_cookbook
3
+ # Recipe:: default
4
+ #
5
+ # Copyright 2013, YOUR_COMPANY_NAME
6
+ #
7
+ # All rights reserved - Do Not Redistribute
8
+ #
@@ -0,0 +1,2 @@
1
+ name 'test_cookbook'
2
+ version '0.1.0'
@@ -0,0 +1,10 @@
1
+ #
2
+ # Cookbook Name:: test_cookbook
3
+ # Recipe:: default
4
+ #
5
+ # Copyright 2013, YOUR_COMPANY_NAME
6
+ #
7
+ # All rights reserved - Do Not Redistribute
8
+ #
9
+ #
10
+ ### DIFFERENT CONTENT
@@ -0,0 +1,2 @@
1
+ chefignore
2
+ recipes/default.rb
@@ -0,0 +1,2 @@
1
+ name 'test_cookbook'
2
+ version '0.1.0'
@@ -0,0 +1,10 @@
1
+ #
2
+ # Cookbook Name:: test_cookbook
3
+ # Recipe:: default
4
+ #
5
+ # Copyright 2013, YOUR_COMPANY_NAME
6
+ #
7
+ # All rights reserved - Do Not Redistribute
8
+ #
9
+ #
10
+ ### DIFFERENT CONTENT