knife-essentials 0.8.2 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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