Class: KnifeRoleSpaghetti::RoleSpaghetti

Inherits:
Chef::Knife
  • Object
show all
Defined in:
lib/chef/knife/spaghetti.rb

Overview

RoleSpaghetti is a Chef::Knife plugin It creates a new class, which is visibile when running the ‘knife` cli tool and provides a way to output a visualized dependency graph of Chef Roles.

Instance Method Summary collapse

Instance Method Details

#loaderObject

OPTIMIZE: Add an option to display cookbooks instead of recipes?



32
33
34
# File 'lib/chef/knife/spaghetti.rb', line 32

def loader
  @loader ||= Chef::Knife::Core::ObjectLoader.new(Chef::Role, ui)
end

#runObject



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
# File 'lib/chef/knife/spaghetti.rb', line 36

def run

  self.config = Chef::Config.merge!(config)

  # OPTIMIZE: Maybe instead of a flag, infer the graph format from fileext?

  # Parse the configuration options, provide defaults where needed
  if config[:graphformat]
    output_format = config[:graphformat].to_sym
  else
    output_format = :png
  end
  Chef::Log.debug("Output format is: #{output_format}")

  # Determine of a filename has been passed
  if @name_args.size >= 1
    filename = @name_args[0]
  else
    filename = "role-spaghetti.#{output_format}"
  end
  Chef::Log.debug("Output filename is: #{filename}")

  loaded_roles = loader.find_all_objects(config[:role_path])

  # If we can't find any roles, it's pointless to continue.
  if loaded_roles.size == 0
    ui.fatal("No roles were found in role_path: #{config[:role_path]}")
    ui.fatal("Ensure that your knife.rb has the correct path.")
    exit 1
  end

  g = GraphViz.new(:RoleDependencies, :type => :digraph,
    :fontname => "Verdana", :fontsize => 20,
    :label => "\n\n\nChef Role Dependencies\n",
    :rankdir => "LR",
    :overlap => "false",
    :compound => "true"
  )

  loaded_roles.each do |role_file|
    # Create an absolute path, since some file references include relative paths
    abspath = File.absolute_path(File.join(config[:role_path], role_file))

    # The object_from_file method figures out the ruby/json logic
    role = loader.object_from_file(abspath)

    Chef::Log.debug("Loaded role is: #{role}")

    # OPTIMIZE: Handle environment run_lists

    g.node[:shape] = "box"
    g.node[:style] = "rounded"
    g.node[:color] = "red"
    role_node = g.add_nodes("#{role.name}_role", :label => role.name)

    # This logic is to ensure that an embedded role doesn't change color
    role.run_list.each do |rli|

      if rli.role?
        g.node[:shape] = "box"
        g.node[:style] = "rounded"
        g.node[:color] = "red"
        rli_node = g.add_nodes("#{rli.name}_role", :label => rli.name)
      else
        g.node[:shape] = "component"
        g.node[:color] = "blue"
        rli_node = g.add_nodes(rli.name)
      end

      g.add_edges(role_node, rli_node)
    end

  end

  # Let's write out the graph to a file
  if config[:neatograph]
    g.output(output_format => "#{filename}", :use => "neato")
  else
    # default to dot
    g.output(output_format => "#{filename}")
  end

  ui.msg("A Role dependency graph has been written to #{filename}")
end