knife-essentials 0.8.2 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
1
  require 'chef_fs/knife'
2
2
  require 'chef_fs/file_system'
3
+ require 'chef/run_list'
3
4
 
4
5
  class Chef
5
6
  class Knife
@@ -71,42 +72,54 @@ class Chef
71
72
 
72
73
  def get_dependencies(entry)
73
74
  begin
74
- object = entry.chef_object
75
- rescue ChefFS::FileSystem::NotFoundError
76
- ui.error "#{format_path(entry.path)}: No such file or directory"
77
- self.exit_code = 2
78
- return []
79
- end
80
- if !object
81
- # If it's not a Chef object, it has no deps
82
- return []
83
- end
75
+ if entry.parent && entry.parent.path == '/cookbooks'
76
+ return entry.chef_object.metadata.dependencies.keys.map { |cookbook| "/cookbooks/#{cookbook}"}
84
77
 
85
- if object.is_a?(Chef::CookbookVersion)
86
- return object.metadata.dependencies.keys.map { |cookbook| "/cookbooks/#{cookbook}"}
87
- elsif object.is_a?(Chef::Node)
88
- result = []
89
- # /environments/_default.json is an annoying dependency, since you
90
- # can't upload it anyway
91
- if object.chef_environment != '_default'
92
- result << "/environments/#{object.chef_environment}.json"
93
- end
94
- return result + dependencies_from_runlist(object.run_list)
95
- elsif object.is_a?(Chef::Role)
96
- result = []
97
- object.env_run_lists.each_pair do |env,run_list|
98
- dependencies_from_runlist(run_list).each do |dependency|
99
- result << dependency if !result.include?(dependency)
78
+ elsif entry.parent && entry.parent.path == '/nodes'
79
+ node = JSON.parse(entry.read, :create_additions => false)
80
+ result = []
81
+ if node['chef_environment'] && node['chef_environment'] != '_default'
82
+ result << "/environments/#{node['chef_environment']}.json"
83
+ end
84
+ if node['run_list']
85
+ result += dependencies_from_runlist(node['run_list'])
86
+ end
87
+ result
88
+
89
+ elsif entry.parent && entry.parent.path == '/roles'
90
+ role = JSON.parse(entry.read, :create_additions => false)
91
+ result = []
92
+ if role['run_list']
93
+ dependencies_from_runlist(role['run_list']).each do |dependency|
94
+ result << dependency if !result.include?(dependency)
95
+ end
96
+ end
97
+ if role['env_run_lists']
98
+ role['env_run_lists'].each_pair do |env,run_list|
99
+ dependencies_from_runlist(run_list).each do |dependency|
100
+ result << dependency if !result.include?(dependency)
101
+ end
102
+ end
100
103
  end
104
+ result
105
+
106
+ elsif !entry.exists?
107
+ raise ChefFS::FileSystem::NotFoundError, "Nonexistent #{entry.path_for_printing}"
108
+
109
+ else
110
+ []
101
111
  end
102
- return result
103
- else
104
- return []
112
+ rescue ChefFS::FileSystem::NotFoundError
113
+ ui.error "#{format_path(entry.path)}: No such file or directory"
114
+ self.exit_code = 2
115
+ []
105
116
  end
106
117
  end
107
118
 
108
119
  def dependencies_from_runlist(run_list)
109
- result = run_list.map do |run_list_item|
120
+ chef_run_list = Chef::RunList.new
121
+ chef_run_list.reset!(run_list)
122
+ chef_run_list.map do |run_list_item|
110
123
  case run_list_item.type
111
124
  when :role
112
125
  "/roles/#{run_list_item.name}.json"
@@ -4,7 +4,6 @@ class Chef
4
4
  class Knife
5
5
  remove_const(:Raw) if const_defined?(:Raw) && Raw.name == 'Chef::Knife::Raw' # override Chef's version
6
6
  class Raw < Chef::Knife
7
- ChefFS = ::ChefFS
8
7
  banner "knife raw REQUEST_PATH"
9
8
 
10
9
  option :method,
@@ -18,19 +18,15 @@
18
18
 
19
19
  require 'chef_fs/file_system/file_system_entry'
20
20
  require 'chef/cookbook/cookbook_version_loader'
21
- require 'chef/node'
22
- require 'chef/role'
23
- require 'chef/environment'
24
- require 'chef/data_bag_item'
25
- require 'chef/client'
26
21
 
27
22
  module ChefFS
28
23
  module FileSystem
29
24
  # ChefRepositoryFileSystemEntry works just like FileSystemEntry,
30
25
  # except can inflate Chef objects
31
26
  class ChefRepositoryFileSystemEntry < FileSystemEntry
32
- def initialize(name, parent, file_path = nil, ignore_empty_directories = nil, chefignore = nil)
27
+ def initialize(name, parent, file_path = nil, json_class = nil)
33
28
  super(name, parent, file_path)
29
+ @json_class = json_class
34
30
  end
35
31
 
36
32
  def chefignore
@@ -41,6 +37,10 @@ module ChefFS
41
37
  parent.ignore_empty_directories?
42
38
  end
43
39
 
40
+ def json_class
41
+ @json_class || parent.json_class
42
+ end
43
+
44
44
  def chef_object
45
45
  begin
46
46
  if parent.path == '/cookbooks'
@@ -49,8 +49,10 @@ module ChefFS
49
49
  return loader.cookbook_version
50
50
  end
51
51
 
52
- # Otherwise the information to inflate the object, is in the file (json_class).
53
- return Chef::JSONCompat.from_json(read)
52
+ # Otherwise, inflate the file using the chosen JSON class (if any)
53
+ if json_class
54
+ return json_class.json_create(JSON.parse(read, :create_additions => false))
55
+ end
54
56
  rescue
55
57
  Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{$!}")
56
58
  end
@@ -20,6 +20,11 @@ require 'chef_fs/file_system/base_fs_dir'
20
20
  require 'chef_fs/file_system/chef_repository_file_system_entry'
21
21
  require 'chef_fs/file_system/chef_repository_file_system_cookbooks_dir'
22
22
  require 'chef_fs/file_system/multiplexed_dir'
23
+ require 'chef/api_client'
24
+ require 'chef/data_bag_item'
25
+ require 'chef/environment'
26
+ require 'chef/node'
27
+ require 'chef/role'
23
28
 
24
29
  module ChefFS
25
30
  module FileSystem
@@ -54,6 +59,10 @@ module ChefFS
54
59
  nil
55
60
  end
56
61
 
62
+ def json_class
63
+ nil
64
+ end
65
+
57
66
  private
58
67
 
59
68
  def make_child_entry(name)
@@ -66,7 +75,23 @@ module ChefFS
66
75
  if name == 'cookbooks'
67
76
  dirs = paths.map { |path| ChefRepositoryFileSystemCookbooksDir.new(name, self, path) }
68
77
  else
69
- dirs = paths.map { |path| ChefRepositoryFileSystemEntry.new(name, self, path) }
78
+ json_class = case name
79
+ when 'clients'
80
+ Chef::ApiClient
81
+ when 'data_bags'
82
+ Chef::DataBagItem
83
+ when 'environments'
84
+ Chef::Environment
85
+ when 'nodes'
86
+ Chef::Node
87
+ when 'roles'
88
+ Chef::Role
89
+ when 'users'
90
+ nil
91
+ else
92
+ raise "Unknown top level path #{name}"
93
+ end
94
+ dirs = paths.map { |path| ChefRepositoryFileSystemEntry.new(name, self, path, json_class) }
70
95
  end
71
96
  MultiplexedDir.new(dirs)
72
97
  end
@@ -33,7 +33,7 @@ module ChefFS
33
33
  attr_reader :file_path
34
34
 
35
35
  def path_for_printing
36
- ChefFS::PathUtils::relative_to(file_path, File.expand_path(Dir.pwd))
36
+ file_path
37
37
  end
38
38
 
39
39
  def children
@@ -23,7 +23,7 @@ module ChefFS
23
23
  multiplexed_dirs.each do |dir|
24
24
  dir.children.each do |child|
25
25
  if seen[child.name]
26
- Chef::Log.warn("Child with name '#{child.name}' found in multiple directories: #{child} and #{seen[child.name]}")
26
+ Chef::Log.warn("Child with name '#{child.name}' found in multiple directories: #{child.path_for_printing} and #{seen[child.name].path_for_printing}")
27
27
  else
28
28
  result << child
29
29
  seen[child.name] = child
data/lib/chef_fs/knife.rb CHANGED
@@ -112,22 +112,23 @@ module ChefFS
112
112
  # If the path does not reach into ANY specified directory, nil is returned.
113
113
  def server_path(file_path)
114
114
  pwd = File.expand_path(Dir.pwd)
115
- absolute_path = File.expand_path(file_path, pwd)
115
+ absolute_path = ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd))
116
116
 
117
117
  # Check all object paths (cookbooks_dir, data_bags_dir, etc.)
118
118
  object_paths.each_pair do |name, paths|
119
119
  paths.each do |path|
120
- if absolute_path[0,path.length] == path
121
- relative_path = ChefFS::PathUtils::relative_to(path, absolute_path)
120
+ realest_path = ChefFS::PathUtils.realest_path(path)
121
+ if absolute_path[0,realest_path.length] == realest_path
122
+ relative_path = ChefFS::PathUtils::relative_to(absolute_path, realest_path)
122
123
  return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}"
123
124
  end
124
125
  end
125
126
  end
126
127
 
127
128
  # Check chef_repo_path
128
- if chef_repo_path[0,absolute_path.length] == absolute_path
129
- relative_path = ChefFS::PathUtils::relative_to(chef_repo_path, absolute_path)
130
- return relative_path == '.' ? '/' : "/#{relative_path}"
129
+ realest_chef_repo_path = ChefFS::PathUtils.realest_path(chef_repo_path)
130
+ if absolute_path == realest_chef_repo_path
131
+ return '/'
131
132
  end
132
133
 
133
134
  nil
@@ -164,6 +165,10 @@ module ChefFS
164
165
  # TODO support absolute file paths and not just patterns? Too much?
165
166
  # Could be super useful in a world with multiple repo paths
166
167
  args.map do |arg|
168
+ if !base_path && !PathUtils.is_absolute?(arg)
169
+ ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path")
170
+ exit(1)
171
+ end
167
172
  ChefFS::FilePattern::relative_to(base_path, arg)
168
173
  end
169
174
  end
@@ -29,7 +29,7 @@ module ChefFS
29
29
  source_parts = ChefFS::PathUtils.split(source)
30
30
  dest_parts = ChefFS::PathUtils.split(dest)
31
31
  i = 0
32
- until i >= source_parts.length || i >= dest_parts.length || source_parts[i] != source_parts[i]
32
+ until i >= source_parts.length || i >= dest_parts.length || source_parts[i] != dest_parts[i]
33
33
  i+=1
34
34
  end
35
35
  # dot-dot up from 'source' to the common ancestor, then
@@ -58,5 +58,29 @@ module ChefFS
58
58
  ChefFS::windows? ? '[/\\]' : '/'
59
59
  end
60
60
 
61
+ # Given a path which may only be partly real (i.e. /x/y/z when only /x exists,
62
+ # or /x/y/*/blah when /x/y/z/blah exists), call File.realpath on the biggest
63
+ # part that actually exists.
64
+ #
65
+ # If /x is a symlink to /blarghle, and has no subdirectories, then:
66
+ # PathUtils.realest_path('/x/y/z') == '/blarghle/y/z'
67
+ # PathUtils.realest_path('/x/*/z') == '/blarghle/*/z'
68
+ # PathUtils.realest_path('/*/y/z') == '/*/y/z'
69
+ def self.realest_path(path)
70
+ begin
71
+ File.realpath(path)
72
+ rescue Errno::ENOENT
73
+ dirname = File.dirname(path)
74
+ if dirname
75
+ PathUtils.join(realest_path(dirname), File.basename(path))
76
+ else
77
+ path
78
+ end
79
+ end
80
+ end
81
+
82
+ def self.is_absolute?(path)
83
+ path =~ /^#{regexp_path_separator}/
84
+ end
61
85
  end
62
86
  end
@@ -1,4 +1,4 @@
1
1
  module ChefFS
2
- VERSION = "0.8.2"
2
+ VERSION = "0.8.3"
3
3
  end
4
4
 
@@ -597,7 +597,7 @@ EOM
597
597
  file 'cookbooks1/chefignore', "metadata.rb\n"
598
598
  file 'cookbooks2/chefignore', "x.json\n"
599
599
  it "chefignores apply only to the directories they are in" do
600
- knife('list --local -R /').should_succeed <<EOM
600
+ knife('list --local -R /').should_succeed(<<EOM, :stderr => "WARN: Child with name 'chefignore' found in multiple directories: #{Chef::Config.chef_repo_path}/cookbooks2/chefignore and #{Chef::Config.chef_repo_path}/cookbooks1/chefignore\n")
601
601
  /:
602
602
  cookbooks
603
603
 
@@ -621,7 +621,7 @@ EOM
621
621
  file 'cookbooks2/yourcookbook/onlyincookbooks2.rb', ''
622
622
 
623
623
  it "chefignores apply only to the winning cookbook" do
624
- knife('list --local -R /').should_succeed <<EOM
624
+ knife('list --local -R /').should_succeed(<<EOM, :stderr => "WARN: Child with name 'chefignore' found in multiple directories: #{Chef::Config.chef_repo_path}/cookbooks2/chefignore and #{Chef::Config.chef_repo_path}/cookbooks1/chefignore\nWARN: Child with name 'yourcookbook' found in multiple directories: #{Chef::Config.chef_repo_path}/cookbooks2/yourcookbook and #{Chef::Config.chef_repo_path}/cookbooks1/yourcookbook\n")
625
625
  /:
626
626
  cookbooks
627
627
 
@@ -644,7 +644,237 @@ EOM
644
644
  end
645
645
 
646
646
  # TODO alternate repo_path / *_path
647
- # TODO multiple *_path
647
+ context 'alternate *_path' do
648
+ when_the_repository 'has clients and clients2, cookbooks and cookbooks2, etc.' do
649
+ file 'clients/client1.json', {}
650
+ file 'cookbooks/cookbook1/metadata.rb', ''
651
+ file 'data_bags/bag/item.json', {}
652
+ file 'environments/env1.json', {}
653
+ file 'nodes/node1.json', {}
654
+ file 'roles/role1.json', {}
655
+ file 'users/user1.json', {}
656
+
657
+ file 'clients2/client1.json', {}
658
+ file 'cookbooks2/cookbook2/metadata.rb', ''
659
+ file 'data_bags2/bag2/item2.json', {}
660
+ file 'environments2/env2.json', {}
661
+ file 'nodes2/node2.json', {}
662
+ file 'roles2/role2.json', {}
663
+ file 'users2/user2.json', {}
664
+
665
+ directory 'chef_repo2' do
666
+ file 'clients/client3.json', {}
667
+ file 'cookbooks/cookbook3/metadata.rb', ''
668
+ file 'data_bags/bag3/item3.json', {}
669
+ file 'environments/env3.json', {}
670
+ file 'nodes/node3.json', {}
671
+ file 'roles/role3.json', {}
672
+ file 'users/user3.json', {}
673
+ end
674
+
675
+ context 'when all _paths are set to alternates' do
676
+ before :each do
677
+ %w(client cookbook data_bag environment node role user).each do |object_name|
678
+ Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, "#{object_name}s2")
679
+ end
680
+ Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, 'chef_repo2')
681
+ end
682
+
683
+ context 'when cwd is at the top level' do
684
+ cwd '.'
685
+ it 'knife list --local -R fails' do
686
+ knife('list --local -R').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
687
+ end
688
+ end
689
+
690
+ context 'when cwd is inside the data_bags directory' do
691
+ cwd 'data_bags'
692
+ it 'knife list --local -R fails' do
693
+ knife('list --local -R').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
694
+ end
695
+ end
696
+
697
+ context 'when cwd is inside chef_repo2' do
698
+ cwd 'chef_repo2'
699
+ it 'knife list --local -R lists everything' do
700
+ knife('list --local -R').should_succeed <<EOM
701
+ .:
702
+ cookbooks
703
+ data_bags
704
+ environments
705
+ roles
706
+
707
+ cookbooks:
708
+ cookbook2
709
+
710
+ cookbooks/cookbook2:
711
+ metadata.rb
712
+
713
+ data_bags:
714
+ bag2
715
+
716
+ data_bags/bag2:
717
+ item2.json
718
+
719
+ environments:
720
+ env2.json
721
+
722
+ roles:
723
+ role2.json
724
+ EOM
725
+ end
726
+ end
727
+
728
+ context 'when cwd is inside data_bags2' do
729
+ cwd 'data_bags2'
730
+ it 'knife list --local -R lists data bags' do
731
+ knife('list --local -R').should_succeed <<EOM
732
+ .:
733
+ bag2
734
+
735
+ bag2:
736
+ item2.json
737
+ EOM
738
+ end
739
+ it 'knife list --local -R ../roles lists roles' do
740
+ knife('list --local -R ../roles').should_succeed "/roles/role2.json\n"
741
+ end
742
+ end
743
+ end
744
+
745
+ context 'when all _paths except chef_repo_path are set to alternates' do
746
+ before :each do
747
+ %w(client cookbook data_bag environment node role user).each do |object_name|
748
+ Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, "#{object_name}s2")
749
+ end
750
+ end
751
+
752
+ context 'when cwd is at the top level' do
753
+ cwd '.'
754
+ it 'knife list --local -R lists everything' do
755
+ knife('list --local -R').should_succeed <<EOM
756
+ .:
757
+ cookbooks
758
+ data_bags
759
+ environments
760
+ roles
761
+
762
+ cookbooks:
763
+ cookbook2
764
+
765
+ cookbooks/cookbook2:
766
+ metadata.rb
767
+
768
+ data_bags:
769
+ bag2
770
+
771
+ data_bags/bag2:
772
+ item2.json
773
+
774
+ environments:
775
+ env2.json
776
+
777
+ roles:
778
+ role2.json
779
+ EOM
780
+ end
781
+ end
782
+
783
+ context 'when cwd is inside the data_bags directory' do
784
+ cwd 'data_bags'
785
+ it 'knife list --local -R fails' do
786
+ knife('list --local -R').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
787
+ end
788
+ end
789
+
790
+ context 'when cwd is inside chef_repo2' do
791
+ cwd 'chef_repo2'
792
+ it 'knife list -R fails' do
793
+ knife('list --local -R').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
794
+ end
795
+ end
796
+
797
+ context 'when cwd is inside data_bags2' do
798
+ cwd 'data_bags2'
799
+ it 'knife list --local -R lists data bags' do
800
+ knife('list --local -R').should_succeed <<EOM
801
+ .:
802
+ bag2
803
+
804
+ bag2:
805
+ item2.json
806
+ EOM
807
+ end
808
+ end
809
+ end
810
+
811
+ context 'when only chef_repo_path is set to its alternate' do
812
+ before :each do
813
+ %w(client cookbook data_bag environment node role user).each do |object_name|
814
+ Chef::Config["#{object_name}_path".to_sym] = nil
815
+ end
816
+ Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, 'chef_repo2')
817
+ end
818
+
819
+ context 'when cwd is at the top level' do
820
+ cwd '.'
821
+ it 'knife list --local -R fails' do
822
+ knife('list --local -R').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
823
+ end
824
+ end
825
+
826
+ context 'when cwd is inside the data_bags directory' do
827
+ cwd 'data_bags'
828
+ it 'knife list --local -R fails' do
829
+ knife('list --local -R').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
830
+ end
831
+ end
832
+
833
+ context 'when cwd is inside chef_repo2' do
834
+ cwd 'chef_repo2'
835
+ it 'knife list --local -R lists everything' do
836
+ knife('list --local -R').should_succeed <<EOM
837
+ .:
838
+ cookbooks
839
+ data_bags
840
+ environments
841
+ roles
842
+
843
+ cookbooks:
844
+ cookbook3
845
+
846
+ cookbooks/cookbook3:
847
+ metadata.rb
848
+
849
+ data_bags:
850
+ bag3
851
+
852
+ data_bags/bag3:
853
+ item3.json
854
+
855
+ environments:
856
+ env3.json
857
+
858
+ roles:
859
+ role3.json
860
+ EOM
861
+ end
862
+ end
863
+ end
864
+
865
+ context 'when paths are set to point to both versions of each' do
866
+ before :each do
867
+ %w(client cookbooks data_bag environment node role user).each do |object_name|
868
+ Chef::Config["#{object_name}_path".to_sym] = [
869
+ File.join(Chef::Config.chef_repo_path, "#{object_name}s"),
870
+ File.join(Chef::Config.chef_repo_path, "#{object_name}s2")
871
+ ]
872
+ end
873
+ Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, 'chef_repo_top')
874
+ end
875
+ end
876
+ end
877
+ end
878
+
648
879
  # TODO nonexistent repo_path / *_path
649
- # TODO empty *_path
650
880
  end
@@ -5,6 +5,323 @@ describe 'knife deps' do
5
5
  extend IntegrationSupport
6
6
  include KnifeSupport
7
7
 
8
+ context 'local' do
9
+ when_the_repository 'has a role with no run_list' do
10
+ file 'roles/starring.json', {}
11
+ it 'knife deps reports no dependencies' do
12
+ knife('deps /roles/starring.json').should_succeed "/roles/starring.json\n"
13
+ end
14
+ end
15
+
16
+ when_the_repository 'has a role with a default run_list' do
17
+ file 'roles/starring.json', { 'run_list' => %w(role[minor] recipe[quiche] recipe[soup::chicken]) }
18
+ file 'roles/minor.json', {}
19
+ file 'cookbooks/quiche/metadata.rb', ''
20
+ file 'cookbooks/quiche/recipes/default.rb', ''
21
+ file 'cookbooks/soup/metadata.rb', ''
22
+ file 'cookbooks/soup/recipes/chicken.rb', ''
23
+ it 'knife deps reports all dependencies' do
24
+ knife('deps /roles/starring.json').should_succeed <<EOM
25
+ /roles/minor.json
26
+ /cookbooks/quiche
27
+ /cookbooks/soup
28
+ /roles/starring.json
29
+ EOM
30
+ end
31
+ end
32
+
33
+ when_the_repository 'has a role with an env_run_list' do
34
+ file 'roles/starring.json', { 'env_run_lists' => { 'desert' => %w(role[minor] recipe[quiche] recipe[soup::chicken]) } }
35
+ file 'roles/minor.json', {}
36
+ file 'cookbooks/quiche/metadata.rb', ''
37
+ file 'cookbooks/quiche/recipes/default.rb', ''
38
+ file 'cookbooks/soup/metadata.rb', ''
39
+ file 'cookbooks/soup/recipes/chicken.rb', ''
40
+ it 'knife deps reports all dependencies' do
41
+ knife('deps /roles/starring.json').should_succeed <<EOM
42
+ /roles/minor.json
43
+ /cookbooks/quiche
44
+ /cookbooks/soup
45
+ /roles/starring.json
46
+ EOM
47
+ end
48
+ end
49
+
50
+ when_the_repository 'has a node with no environment or run_list' do
51
+ file 'nodes/mort.json', {}
52
+ it 'knife deps reports just the node' do
53
+ knife('deps --repo-mode=everything /nodes/mort.json').should_succeed "/nodes/mort.json\n"
54
+ end
55
+ end
56
+ when_the_repository 'has a node with an environment' do
57
+ file 'environments/desert.json', {}
58
+ file 'nodes/mort.json', { 'chef_environment' => 'desert' }
59
+ it 'knife deps reports just the node' do
60
+ knife('deps --repo-mode=everything /nodes/mort.json').should_succeed "/environments/desert.json\n/nodes/mort.json\n"
61
+ end
62
+ end
63
+ when_the_repository 'has a node with roles and recipes in its run_list' do
64
+ file 'roles/minor.json', {}
65
+ file 'cookbooks/quiche/metadata.rb', ''
66
+ file 'cookbooks/quiche/recipes/default.rb', ''
67
+ file 'cookbooks/soup/metadata.rb', ''
68
+ file 'cookbooks/soup/recipes/chicken.rb', ''
69
+ file 'nodes/mort.json', { 'run_list' => %w(role[minor] recipe[quiche] recipe[soup::chicken]) }
70
+ it 'knife deps reports just the node' do
71
+ knife('deps --repo-mode=everything /nodes/mort.json').should_succeed <<EOM
72
+ /roles/minor.json
73
+ /cookbooks/quiche
74
+ /cookbooks/soup
75
+ /nodes/mort.json
76
+ EOM
77
+ end
78
+ end
79
+ when_the_repository 'has a cookbook with no dependencies' do
80
+ file 'cookbooks/quiche/metadata.rb', ''
81
+ file 'cookbooks/quiche/recipes/default.rb', ''
82
+ it 'knife deps reports just the cookbook' do
83
+ knife('deps /cookbooks/quiche').should_succeed "/cookbooks/quiche\n"
84
+ end
85
+ end
86
+ when_the_repository 'has a cookbook with dependencies' do
87
+ file 'cookbooks/kettle/metadata.rb', ''
88
+ file 'cookbooks/quiche/metadata.rb', 'depends "kettle"'
89
+ file 'cookbooks/quiche/recipes/default.rb', ''
90
+ it 'knife deps reports just the cookbook' do
91
+ knife('deps /cookbooks/quiche').should_succeed "/cookbooks/kettle\n/cookbooks/quiche\n"
92
+ end
93
+ end
94
+ when_the_repository 'has a data bag' do
95
+ file 'data_bags/bag/item.json', {}
96
+ it 'knife deps reports just the data bag' do
97
+ knife('deps /data_bags/bag/item.json').should_succeed "/data_bags/bag/item.json\n"
98
+ end
99
+ end
100
+ when_the_repository 'has an environment' do
101
+ file 'environments/desert.json', {}
102
+ it 'knife deps reports just the environment' do
103
+ knife('deps /environments/desert.json').should_succeed "/environments/desert.json\n"
104
+ end
105
+ end
106
+ when_the_repository 'has a deep dependency tree' do
107
+ file 'roles/starring.json', { 'run_list' => %w(role[minor] recipe[quiche] recipe[soup::chicken]) }
108
+ file 'roles/minor.json', {}
109
+ file 'cookbooks/quiche/metadata.rb', ''
110
+ file 'cookbooks/quiche/recipes/default.rb', ''
111
+ file 'cookbooks/soup/metadata.rb', ''
112
+ file 'cookbooks/soup/recipes/chicken.rb', ''
113
+ file 'environments/desert.json', {}
114
+ file 'nodes/mort.json', { 'chef_environment' => 'desert', 'run_list' => [ 'role[starring]' ] }
115
+ file 'nodes/bart.json', { 'run_list' => [ 'role[minor]' ] }
116
+
117
+ it 'knife deps reports all dependencies' do
118
+ knife('deps --repo-mode=everything /nodes/mort.json').should_succeed <<EOM
119
+ /environments/desert.json
120
+ /roles/minor.json
121
+ /cookbooks/quiche
122
+ /cookbooks/soup
123
+ /roles/starring.json
124
+ /nodes/mort.json
125
+ EOM
126
+ end
127
+ it 'knife deps * reports all dependencies of all things' do
128
+ knife('deps --repo-mode=everything /nodes/*').should_succeed <<EOM
129
+ /roles/minor.json
130
+ /nodes/bart.json
131
+ /environments/desert.json
132
+ /cookbooks/quiche
133
+ /cookbooks/soup
134
+ /roles/starring.json
135
+ /nodes/mort.json
136
+ EOM
137
+ end
138
+ it 'knife deps a b reports all dependencies of a and b' do
139
+ knife('deps --repo-mode=everything /nodes/bart.json /nodes/mort.json').should_succeed <<EOM
140
+ /roles/minor.json
141
+ /nodes/bart.json
142
+ /environments/desert.json
143
+ /cookbooks/quiche
144
+ /cookbooks/soup
145
+ /roles/starring.json
146
+ /nodes/mort.json
147
+ EOM
148
+ end
149
+ it 'knife deps --tree /* shows dependencies in a tree' do
150
+ knife('deps --tree --repo-mode=everything /nodes/*').should_succeed <<EOM
151
+ /nodes/bart.json
152
+ /roles/minor.json
153
+ /nodes/mort.json
154
+ /environments/desert.json
155
+ /roles/starring.json
156
+ /roles/minor.json
157
+ /cookbooks/quiche
158
+ /cookbooks/soup
159
+ EOM
160
+ end
161
+ it 'knife deps --tree --no-recurse shows only the first level of dependencies' do
162
+ knife('deps --tree --no-recurse --repo-mode=everything /nodes/*').should_succeed <<EOM
163
+ /nodes/bart.json
164
+ /roles/minor.json
165
+ /nodes/mort.json
166
+ /environments/desert.json
167
+ /roles/starring.json
168
+ EOM
169
+ end
170
+ end
171
+
172
+ context 'circular dependencies' do
173
+ when_the_repository 'has cookbooks with circular dependencies' do
174
+ file 'cookbooks/foo/metadata.rb', 'depends "bar"'
175
+ file 'cookbooks/bar/metadata.rb', 'depends "baz"'
176
+ file 'cookbooks/baz/metadata.rb', 'depends "foo"'
177
+ file 'cookbooks/self/metadata.rb', 'depends "self"'
178
+ it 'knife deps prints each once' do
179
+ knife('deps /cookbooks/foo /cookbooks/self').should_succeed <<EOM
180
+ /cookbooks/baz
181
+ /cookbooks/bar
182
+ /cookbooks/foo
183
+ /cookbooks/self
184
+ EOM
185
+ end
186
+ it 'knife deps --tree prints each once' do
187
+ knife('deps --tree /cookbooks/foo /cookbooks/self').should_succeed <<EOM
188
+ /cookbooks/foo
189
+ /cookbooks/bar
190
+ /cookbooks/baz
191
+ /cookbooks/foo
192
+ /cookbooks/self
193
+ /cookbooks/self
194
+ EOM
195
+ end
196
+ end
197
+ when_the_repository 'has roles with circular dependencies' do
198
+ file 'roles/foo.json', { 'run_list' => [ 'role[bar]' ] }
199
+ file 'roles/bar.json', { 'run_list' => [ 'role[baz]' ] }
200
+ file 'roles/baz.json', { 'run_list' => [ 'role[foo]' ] }
201
+ file 'roles/self.json', { 'run_list' => [ 'role[self]' ] }
202
+ it 'knife deps prints each once' do
203
+ knife('deps /roles/foo.json /roles/self.json').should_succeed <<EOM
204
+ /roles/baz.json
205
+ /roles/bar.json
206
+ /roles/foo.json
207
+ /roles/self.json
208
+ EOM
209
+ end
210
+ it 'knife deps --tree prints each once' do
211
+ knife('deps --tree /roles/foo.json /roles/self.json') do
212
+ stdout.should == "/roles/foo.json\n /roles/bar.json\n /roles/baz.json\n /roles/foo.json\n/roles/self.json\n /roles/self.json\n"
213
+ stderr.should == "WARNING: No knife configuration file found\n"
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ context 'missing objects' do
220
+ when_the_repository 'is empty' do
221
+ it 'knife deps /blah reports an error' do
222
+ knife('deps /blah').should_fail(
223
+ :exit_code => 2,
224
+ :stdout => "/blah\n",
225
+ :stderr => "ERROR: /blah: No such file or directory\n"
226
+ )
227
+ end
228
+ it 'knife deps /roles/x.json reports an error' do
229
+ knife('deps /roles/x.json').should_fail(
230
+ :exit_code => 2,
231
+ :stdout => "/roles/x.json\n",
232
+ :stderr => "ERROR: /roles/x.json: No such file or directory\n"
233
+ )
234
+ end
235
+ it 'knife deps /nodes/x.json reports an error' do
236
+ knife('deps --repo-mode=everything /nodes/x.json').should_fail(
237
+ :exit_code => 2,
238
+ :stdout => "/nodes/x.json\n",
239
+ :stderr => "ERROR: /nodes/x.json: No such file or directory\n"
240
+ )
241
+ end
242
+ it 'knife deps /environments/x.json reports an error' do
243
+ knife('deps /environments/x.json').should_fail(
244
+ :exit_code => 2,
245
+ :stdout => "/environments/x.json\n",
246
+ :stderr => "ERROR: /environments/x.json: No such file or directory\n"
247
+ )
248
+ end
249
+ it 'knife deps /cookbooks/x reports an error' do
250
+ knife('deps /cookbooks/x').should_fail(
251
+ :exit_code => 2,
252
+ :stdout => "/cookbooks/x\n",
253
+ :stderr => "ERROR: /cookbooks/x: No such file or directory\n"
254
+ )
255
+ end
256
+ it 'knife deps /data_bags/bag/item reports an error' do
257
+ knife('deps /data_bags/bag/item').should_fail(
258
+ :exit_code => 2,
259
+ :stdout => "/data_bags/bag/item\n",
260
+ :stderr => "ERROR: /data_bags/bag/item: No such file or directory\n"
261
+ )
262
+ end
263
+ end
264
+ when_the_repository 'is missing a dependent cookbook' do
265
+ file 'roles/starring.json', { 'run_list' => [ 'recipe[quiche]'] }
266
+ it 'knife deps reports the cookbook, along with an error' do
267
+ knife('deps /roles/starring.json').should_fail(
268
+ :exit_code => 2,
269
+ :stdout => "/cookbooks/quiche\n/roles/starring.json\n",
270
+ :stderr => "ERROR: /cookbooks/quiche: No such file or directory\n"
271
+ )
272
+ end
273
+ end
274
+ when_the_repository 'is missing a dependent environment' do
275
+ file 'nodes/mort.json', { 'chef_environment' => 'desert' }
276
+ it 'knife deps reports the environment, along with an error' do
277
+ knife('deps --repo-mode=everything /nodes/mort.json').should_fail(
278
+ :exit_code => 2,
279
+ :stdout => "/environments/desert.json\n/nodes/mort.json\n",
280
+ :stderr => "ERROR: /environments/desert.json: No such file or directory\n"
281
+ )
282
+ end
283
+ end
284
+ when_the_repository 'is missing a dependent role' do
285
+ file 'roles/starring.json', { 'run_list' => [ 'role[minor]'] }
286
+ it 'knife deps reports the role, along with an error' do
287
+ knife('deps /roles/starring.json').should_fail(
288
+ :exit_code => 2,
289
+ :stdout => "/roles/minor.json\n/roles/starring.json\n",
290
+ :stderr => "ERROR: /roles/minor.json: No such file or directory\n"
291
+ )
292
+ end
293
+ end
294
+ end
295
+ context 'invalid objects' do
296
+ when_the_repository 'is empty' do
297
+ it 'knife deps / reports itself only' do
298
+ knife('deps /').should_succeed("/\n")
299
+ end
300
+ it 'knife deps /roles reports an error' do
301
+ knife('deps /roles').should_fail(
302
+ :exit_code => 2,
303
+ :stderr => "ERROR: /roles: No such file or directory\n",
304
+ :stdout => "/roles\n"
305
+ )
306
+ end
307
+ end
308
+ when_the_repository 'has a data bag' do
309
+ file 'data_bags/bag/item.json', ''
310
+ it 'knife deps /data_bags/bag shows no dependencies' do
311
+ knife('deps /data_bags/bag').should_succeed("/data_bags/bag\n")
312
+ end
313
+ end
314
+ when_the_repository 'has a cookbook' do
315
+ file 'cookbooks/blah/metadata.rb', ''
316
+ it 'knife deps on a cookbook file shows no dependencies' do
317
+ knife('deps /cookbooks/blah/metadata.rb').should_succeed(
318
+ "/cookbooks/blah/metadata.rb\n"
319
+ )
320
+ end
321
+ end
322
+ end
323
+ end
324
+
8
325
  context 'remote' do
9
326
  when_the_chef_server 'has a role with no run_list' do
10
327
  role 'starring', {}
@@ -306,9 +623,9 @@ EOM
306
623
  end
307
624
  end
308
625
  end
626
+ end
309
627
 
310
- it 'knife deps --no-recurse reports an error' do
311
- knife('deps --no-recurse /').should_fail("ERROR: --no-recurse requires --tree\n")
312
- end
628
+ it 'knife deps --no-recurse reports an error' do
629
+ knife('deps --no-recurse /').should_fail("ERROR: --no-recurse requires --tree\n")
313
630
  end
314
631
  end
@@ -225,7 +225,195 @@ EOM
225
225
  end
226
226
  end
227
227
 
228
- # TODO different cwd
228
+ context 'symlink tests' do
229
+ when_the_repository 'is empty' do
230
+ context 'when cwd is at the top of the repository' do
231
+ cwd '.'
232
+
233
+ it "knife list -Rp --flat returns everything" do
234
+ knife('list -Rp --flat').should_succeed <<EOM
235
+ cookbooks/
236
+ cookbooks/cookbook1/
237
+ cookbooks/cookbook1/metadata.rb
238
+ cookbooks/cookbook2/
239
+ cookbooks/cookbook2/metadata.rb
240
+ cookbooks/cookbook2/recipes/
241
+ cookbooks/cookbook2/recipes/default.rb
242
+ data_bags/
243
+ data_bags/bag1/
244
+ data_bags/bag1/item1.json
245
+ data_bags/bag1/item2.json
246
+ data_bags/bag2/
247
+ data_bags/bag2/item1.json
248
+ data_bags/bag2/item2.json
249
+ environments/
250
+ environments/_default.json
251
+ environments/environment1.json
252
+ environments/environment2.json
253
+ roles/
254
+ roles/role1.json
255
+ roles/role2.json
256
+ EOM
257
+ end
258
+ end
259
+ end
260
+
261
+ when_the_repository 'has a cookbooks directory' do
262
+ directory 'cookbooks'
263
+ context 'when cwd is in cookbooks/' do
264
+ cwd 'cookbooks'
265
+
266
+ it "knife list -Rp --flat / returns everything" do
267
+ knife('list -Rp --flat /').should_succeed <<EOM
268
+ ./
269
+ cookbook1/
270
+ cookbook1/metadata.rb
271
+ cookbook2/
272
+ cookbook2/metadata.rb
273
+ cookbook2/recipes/
274
+ cookbook2/recipes/default.rb
275
+ /data_bags/
276
+ /data_bags/bag1/
277
+ /data_bags/bag1/item1.json
278
+ /data_bags/bag1/item2.json
279
+ /data_bags/bag2/
280
+ /data_bags/bag2/item1.json
281
+ /data_bags/bag2/item2.json
282
+ /environments/
283
+ /environments/_default.json
284
+ /environments/environment1.json
285
+ /environments/environment2.json
286
+ /roles/
287
+ /roles/role1.json
288
+ /roles/role2.json
289
+ EOM
290
+ end
291
+
292
+ it "knife list -Rp --flat .. returns everything" do
293
+ knife('list -Rp --flat ..').should_succeed <<EOM
294
+ ./
295
+ cookbook1/
296
+ cookbook1/metadata.rb
297
+ cookbook2/
298
+ cookbook2/metadata.rb
299
+ cookbook2/recipes/
300
+ cookbook2/recipes/default.rb
301
+ /data_bags/
302
+ /data_bags/bag1/
303
+ /data_bags/bag1/item1.json
304
+ /data_bags/bag1/item2.json
305
+ /data_bags/bag2/
306
+ /data_bags/bag2/item1.json
307
+ /data_bags/bag2/item2.json
308
+ /environments/
309
+ /environments/_default.json
310
+ /environments/environment1.json
311
+ /environments/environment2.json
312
+ /roles/
313
+ /roles/role1.json
314
+ /roles/role2.json
315
+ EOM
316
+ end
317
+
318
+ it "knife list -Rp --flat returns cookbooks" do
319
+ knife('list -Rp --flat').should_succeed <<EOM
320
+ cookbook1/
321
+ cookbook1/metadata.rb
322
+ cookbook2/
323
+ cookbook2/metadata.rb
324
+ cookbook2/recipes/
325
+ cookbook2/recipes/default.rb
326
+ EOM
327
+ end
328
+ end
329
+ end
330
+
331
+ when_the_repository 'has a cookbooks/cookbook2 directory' do
332
+ directory 'cookbooks/cookbook2'
333
+
334
+ context 'when cwd is in cookbooks/cookbook2' do
335
+ cwd 'cookbooks/cookbook2'
336
+
337
+ it "knife list -Rp --flat returns cookbooks" do
338
+ knife('list -Rp --flat').should_succeed <<EOM
339
+ metadata.rb
340
+ recipes/
341
+ recipes/default.rb
342
+ EOM
343
+ end
344
+ end
345
+ end
346
+
347
+ when_the_repository 'has a cookbooks directory and a symlinked cookbooks directory' do
348
+ directory 'cookbooks'
349
+ symlink 'symlinked', 'cookbooks'
350
+
351
+ context 'when cwd is in cookbooks/' do
352
+ cwd 'cookbooks'
353
+
354
+ it "knife list -Rp --flat returns cookbooks" do
355
+ knife('list -Rp --flat').should_succeed <<EOM
356
+ cookbook1/
357
+ cookbook1/metadata.rb
358
+ cookbook2/
359
+ cookbook2/metadata.rb
360
+ cookbook2/recipes/
361
+ cookbook2/recipes/default.rb
362
+ EOM
363
+ end
364
+ end
365
+
366
+ context 'when cwd is in symlinked/' do
367
+ cwd 'symlinked'
368
+
369
+ it "knife list -Rp --flat returns cookbooks" do
370
+ knife('list -Rp --flat').should_succeed <<EOM
371
+ cookbook1/
372
+ cookbook1/metadata.rb
373
+ cookbook2/
374
+ cookbook2/metadata.rb
375
+ cookbook2/recipes/
376
+ cookbook2/recipes/default.rb
377
+ EOM
378
+ end
379
+ end
380
+ end
381
+
382
+ when_the_repository 'has a real_cookbooks directory and a cookbooks symlink to it' do
383
+ directory 'real_cookbooks'
384
+ symlink 'cookbooks', 'real_cookbooks'
385
+
386
+ context 'when cwd is in real_cookbooks/' do
387
+ cwd 'real_cookbooks'
388
+
389
+ it "knife list -Rp --flat returns cookbooks" do
390
+ knife('list -Rp --flat').should_succeed <<EOM
391
+ cookbook1/
392
+ cookbook1/metadata.rb
393
+ cookbook2/
394
+ cookbook2/metadata.rb
395
+ cookbook2/recipes/
396
+ cookbook2/recipes/default.rb
397
+ EOM
398
+ end
399
+ end
400
+
401
+ context 'when cwd is in cookbooks/' do
402
+ cwd 'cookbooks'
403
+
404
+ it "knife list -Rp --flat returns cookbooks" do
405
+ knife('list -Rp --flat').should_succeed <<EOM
406
+ cookbook1/
407
+ cookbook1/metadata.rb
408
+ cookbook2/
409
+ cookbook2/metadata.rb
410
+ cookbook2/recipes/
411
+ cookbook2/recipes/default.rb
412
+ EOM
413
+ end
414
+ end
415
+ end
416
+ end
229
417
  end
230
418
 
231
419
  context "--local" do
@@ -310,6 +498,5 @@ EOM
310
498
  end
311
499
  end
312
500
  end
313
- # TODO different cwd
314
501
  end
315
502
  end
@@ -73,10 +73,22 @@ module IntegrationSupport
73
73
  end
74
74
  end
75
75
 
76
+ def symlink(relative_path, relative_dest)
77
+ filename = path_to(relative_path)
78
+ dir = File.dirname(filename)
79
+ FileUtils.mkdir_p(dir) unless dir == '.'
80
+ dest_filename = path_to(relative_dest)
81
+ File.symlink(dest_filename, filename)
82
+ end
83
+
76
84
  def path_to(relative_path)
77
85
  File.expand_path(relative_path, (@parent_path || @repository_dir))
78
86
  end
79
87
 
88
+ def self.path_to(relative_path)
89
+ File.expand_path(relative_path, (@parent_path || @repository_dir))
90
+ end
91
+
80
92
  def self.directory(relative_path, &block)
81
93
  before :each do
82
94
  directory(relative_path, &block)
@@ -89,6 +101,22 @@ module IntegrationSupport
89
101
  end
90
102
  end
91
103
 
104
+ def self.symlink(relative_path, relative_dest)
105
+ before :each do
106
+ symlink(relative_path, relative_dest)
107
+ end
108
+ end
109
+
110
+ def self.cwd(relative_path)
111
+ before :each do
112
+ @old_cwd = Dir.pwd
113
+ Dir.chdir(path_to(relative_path))
114
+ end
115
+ after :each do
116
+ Dir.chdir(@old_cwd)
117
+ end
118
+ end
119
+
92
120
  instance_eval(&block)
93
121
  end
94
122
  end
@@ -1,5 +1,7 @@
1
1
  require 'chef/knife'
2
2
  require 'chef/application/knife'
3
+ require 'logger'
4
+ require 'chef/log'
3
5
 
4
6
  module KnifeSupport
5
7
  def knife(*args, &block)
@@ -16,6 +18,8 @@ module KnifeSupport
16
18
  # load stuff ourselves, thank you very much
17
19
  stdout = StringIO.new
18
20
  stderr = StringIO.new
21
+ old_loggers = Chef::Log.loggers
22
+ old_log_level = Chef::Log.level
19
23
  begin
20
24
  subcommand_class = Chef::Knife.subcommand_class_from(args)
21
25
  subcommand_class.options = Chef::Application::Knife.options.merge(subcommand_class.options)
@@ -27,6 +31,11 @@ module KnifeSupport
27
31
  # Don't print stuff
28
32
  Chef::Config[:verbosity] = 0
29
33
  instance.configure_chef
34
+ logger = Logger.new(stderr)
35
+ logger.formatter = proc { |severity, datetime, progname, msg| "#{severity}: #{msg}\n" }
36
+ Chef::Log.use_log_devices([logger])
37
+ Chef::Log.level = :warn
38
+ Chef::Log::Formatter.show_time = false
30
39
  instance.run
31
40
 
32
41
  exit_code = 0
@@ -34,6 +43,9 @@ module KnifeSupport
34
43
  # This is how rspec catches exit()
35
44
  rescue SystemExit => e
36
45
  exit_code = e.status
46
+ ensure
47
+ Chef::Log.use_log_devices(old_loggers)
48
+ Chef::Log.level = old_log_level
37
49
  end
38
50
 
39
51
  KnifeResult.new(stdout.string, stderr.string, exit_code)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-essentials
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.8.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-04 00:00:00.000000000 Z
12
+ date: 2013-01-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: chef-zero