New version of salt-formula from Saltstack
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

116 lines
3.3KB

  1. #!/usr/bin/env python
  2. """
  3. salt-state-graph
  4. A tool that ingests the YAML representing the Salt highstate (or sls state) for
  5. a single minion and produces a program written in DOT.
  6. The tool is useful for visualising the dependency graph of a Salt highstate.
  7. """
  8. from pydot import Dot, Node, Edge
  9. import yaml
  10. import sys
  11. def find(obj, find_key):
  12. """
  13. Takes a list and a set. Returns a list of all matching objects.
  14. Uses find_inner to recursively traverse the data structure, finding objects
  15. with keyed by find_key.
  16. """
  17. all_matches = [find_inner(item, find_key) for item in obj]
  18. final = [item for sublist in all_matches for item in sublist]
  19. return final
  20. def find_inner(obj, find_key):
  21. """
  22. Recursively search through the data structure to find objects
  23. keyed by find_key.
  24. """
  25. results = []
  26. if not hasattr(obj, '__iter__'):
  27. # not a sequence type - return nothing
  28. # this excludes strings
  29. return results
  30. if isinstance(obj, dict):
  31. # a dict - check each key
  32. for key, prop in obj.iteritems():
  33. if key == find_key:
  34. results.extend(prop)
  35. else:
  36. results.extend(find_inner(prop, find_key))
  37. else:
  38. # a list / tuple - check each item
  39. for i in obj:
  40. results.extend(find_inner(i, find_key))
  41. return results
  42. def make_node_name(state_type, state_label):
  43. return "{0} - {1}".format(state_type.upper(), state_label)
  44. def find_edges(states, relname):
  45. """
  46. Use find() to recursively find objects at keys matching
  47. relname, yielding a node name for every result.
  48. """
  49. try:
  50. deps = find(states, relname)
  51. for dep in deps:
  52. for dep_type, dep_name in dep.iteritems():
  53. yield make_node_name(dep_type, dep_name)
  54. except AttributeError as e:
  55. sys.stderr.write("Bad state: {0}\n".format(str(states)))
  56. raise e
  57. def main(input):
  58. state_obj = yaml.load(input)
  59. graph = Dot("states", graph_type='digraph')
  60. rules = {
  61. 'require': {'color': 'blue'},
  62. 'require_in': {'color': 'blue', 'reverse': True},
  63. 'watch': {'color': 'red'},
  64. 'watch_in': {'color': 'red', 'reverse': True},
  65. }
  66. for top_key, props in state_obj.iteritems():
  67. # Add a node for each state type embedded in this state
  68. # keys starting with underscores are not candidates
  69. if top_key == '__extend__':
  70. # TODO - merge these into the main states and remove them
  71. sys.stderr.write(
  72. "Removing __extend__ states:\n{0}\n".format(str(props)))
  73. continue
  74. for top_key_type, states in props.iteritems():
  75. if top_key_type[:2] == '__':
  76. continue
  77. node_name = make_node_name(top_key_type, top_key)
  78. graph.add_node(Node(node_name))
  79. for edge_type, ruleset in rules.iteritems():
  80. for relname in find_edges(states, edge_type):
  81. if 'reverse' in ruleset and ruleset['reverse']:
  82. graph.add_edge(Edge(
  83. node_name, relname, color=ruleset['color']))
  84. else:
  85. graph.add_edge(Edge(
  86. relname, node_name, color=ruleset['color']))
  87. graph.write('/dev/stdout')
  88. if __name__ == '__main__':
  89. main(sys.stdin)