summaryrefslogtreecommitdiff
path: root/lib/chef/knife/deps.rb
blob: 4b2346896219e63ebd7054cff59a992a716e3db1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
require 'chef/chef_fs/knife'

class Chef
  class Knife
    class Deps < Chef::ChefFS::Knife
      banner "knife deps PATTERN1 [PATTERNn]"

      category "path-based"

      deps do
        require 'chef/chef_fs/file_system'
        require 'chef/run_list'
      end

      option :recurse,
        :long => '--[no-]recurse',
        :boolean => true,
        :description => "List dependencies recursively (default: true).  Only works with --tree."
      option :tree,
        :long => '--tree',
        :boolean => true,
        :description => "Show dependencies in a visual tree.  May show duplicates."
      option :remote,
        :long => '--remote',
        :boolean => true,
        :description => "List dependencies on the server instead of the local filesystem"

      attr_accessor :exit_code

      def run
        if config[:recurse] == false && !config[:tree]
          ui.error "--no-recurse requires --tree"
          exit(1)
        end
        config[:recurse] = true if config[:recurse].nil?

        @root = config[:remote] ? chef_fs : local_fs
        dependencies = {}
        pattern_args.each do |pattern|
          Chef::ChefFS::FileSystem.list(@root, pattern).each do |entry|
            if config[:tree]
              print_dependencies_tree(entry, dependencies)
            else
              print_flattened_dependencies(entry, dependencies)
            end
          end
        end
        exit exit_code if exit_code
      end

      def print_flattened_dependencies(entry, dependencies)
        if !dependencies[entry.path]
          dependencies[entry.path] = get_dependencies(entry)
          dependencies[entry.path].each do |child|
            child_entry = Chef::ChefFS::FileSystem.resolve_path(@root, child)
            print_flattened_dependencies(child_entry, dependencies)
          end
          output format_path(entry)
        end
      end

      def print_dependencies_tree(entry, dependencies, printed = {}, depth = 0)
        dependencies[entry.path] = get_dependencies(entry) if !dependencies[entry.path]
        output "#{'  '*depth}#{format_path(entry)}"
        if !printed[entry.path] && (config[:recurse] || depth == 0)
          printed[entry.path] = true
          dependencies[entry.path].each do |child|
            child_entry = Chef::ChefFS::FileSystem.resolve_path(@root, child)
            print_dependencies_tree(child_entry, dependencies, printed, depth+1)
          end
        end
      end

      def get_dependencies(entry)
        begin
          if entry.parent && entry.parent.path == '/cookbooks'
            return entry.chef_object.metadata.dependencies.keys.map { |cookbook| "/cookbooks/#{cookbook}" }

          elsif entry.parent && entry.parent.path == '/nodes'
            node = Chef::JSONCompat.parse(entry.read)
            result = []
            if node['chef_environment'] && node['chef_environment'] != '_default'
              result << "/environments/#{node['chef_environment']}.json"
            end
            if node['run_list']
              result += dependencies_from_runlist(node['run_list'])
            end
            result

          elsif entry.parent && entry.parent.path == '/roles'
            role = Chef::JSONCompat.parse(entry.read)
            result = []
            if role['run_list']
              dependencies_from_runlist(role['run_list']).each do |dependency|
                result << dependency if !result.include?(dependency)
              end
            end
            if role['env_run_lists']
              role['env_run_lists'].each_pair do |env,run_list|
                dependencies_from_runlist(run_list).each do |dependency|
                  result << dependency if !result.include?(dependency)
                end
              end
            end
            result

          elsif !entry.exists?
            raise Chef::ChefFS::FileSystem::NotFoundError.new(entry)

          else
            []
          end
        rescue Chef::ChefFS::FileSystem::NotFoundError => e
          ui.error "#{format_path(e.entry)}: No such file or directory"
          self.exit_code = 2
          []
        end
      end

      def dependencies_from_runlist(run_list)
        chef_run_list = Chef::RunList.new
        chef_run_list.reset!(run_list)
        chef_run_list.map do |run_list_item|
          case run_list_item.type
          when :role
            "/roles/#{run_list_item.name}.json"
          when :recipe
            if run_list_item.name =~ /(.+)::[^:]*/
              "/cookbooks/#{$1}"
            else
              "/cookbooks/#{run_list_item.name}"
            end
          else
            raise "Unknown run list item type #{run_list_item.type}"
          end
        end
      end
    end
  end
end