Class: Terraspace::Dependency::Graph

Inherits:
Object
  • Object
show all
Includes:
Util::Logging
Defined in:
lib/terraspace/dependency/graph.rb

Constant Summary collapse

MAX_CYCLE_DEPTH =
Integer(ENV['TS_MAX_CYCLE_DEPTH'] || 100)

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Util::Logging

#logger

Constructor Details

#initialize(stack_names, dependencies, options = {}) ⇒ Graph

Returns a new instance of Graph.



6
7
8
9
10
# File 'lib/terraspace/dependency/graph.rb', line 6

def initialize(stack_names, dependencies, options={})
  @stack_names, @dependencies, @options = stack_names, dependencies, options
  @nodes = []
  @batches = []
end

Instance Attribute Details

#nodesObject (readonly)

Returns the value of attribute nodes.



5
6
7
# File 'lib/terraspace/dependency/graph.rb', line 5

def nodes
  @nodes
end

Instance Method Details

#apply_filter(parent, keep = false) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
# File 'lib/terraspace/dependency/graph.rb', line 116

def apply_filter(parent, keep=false)
  keep ||= @options[:stacks].blank?
  keep ||= @options[:stacks].include?(parent.name)  # apply filter
  if keep
    parent.filtered = true
    @filtered << parent
  end
  parent.children.sort_by(&:name).each do |child|
    apply_filter(child, keep)
  end
end

#buildObject



12
13
14
15
16
17
18
19
20
21
# File 'lib/terraspace/dependency/graph.rb', line 12

def build
  precreate_all_nodes
  build_nodes_with_dependencies # @nodes has dependency graph info afterwards
  check_circular_dependencies!
  @nodes = filter_nodes
  check_empty_nodes!
  build_batches
  clean_batches
  @batches
end

#build_batch(leaf, depth = 1) ⇒ Object



92
93
94
95
96
97
98
# File 'lib/terraspace/dependency/graph.rb', line 92

def build_batch(leaf, depth=1)
  @batches[depth] ||= Set.new
  @batches[depth] << leaf
  leaf.parents.each do |parent|
    build_batch(parent, depth+1)
  end
end

#build_batchesObject



69
70
71
72
73
74
75
76
# File 'lib/terraspace/dependency/graph.rb', line 69

def build_batches
  @batches[0] = Set.new(leaves)
  leaves.each do |leaf|
    leaf.parents.each do |parent|
      build_batch(parent)
    end
  end
end

#build_nodes_with_dependenciesObject



54
55
56
57
58
59
# File 'lib/terraspace/dependency/graph.rb', line 54

def build_nodes_with_dependencies
  @dependencies.each do |item|
    parent_name, child_name = item.split(':')
    save_node_parent(parent_name, child_name)
  end
end

#check_circular_dependencies!Object



30
31
32
33
34
# File 'lib/terraspace/dependency/graph.rb', line 30

def check_circular_dependencies!
  @nodes.each do |node|
    check_cycle(node)
  end
end

#check_cycle(node, depth = 0, list = []) ⇒ Object



37
38
39
40
41
42
43
44
45
# File 'lib/terraspace/dependency/graph.rb', line 37

def check_cycle(node, depth=0, list=[])
  if depth > MAX_CYCLE_DEPTH
    logger.error "ERROR: It seems like there is a circular dependency! Stacks involved: #{list.uniq}".color(:red)
    exit 1
  end
  node.parents.each do |parent|
    check_cycle(parent, depth+1, list += [parent])
  end
end

#check_empty_nodes!Object

Only check when stacks option is pass. Edge case: There can be app/modules but no app/stacks yet



24
25
26
27
28
# File 'lib/terraspace/dependency/graph.rb', line 24

def check_empty_nodes!
  return unless @nodes.empty? && @options[:stacks]
  logger.error "ERROR: No stacks were found that match: #{@options[:stacks].join(' ')}".color(:red)
  exit 1
end

#clean_batchesObject

So stack nodes dont get deployed more than once and too early



79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/terraspace/dependency/graph.rb', line 79

def clean_batches
  all = Set.new
  # batch is a set
  @batches.reverse.each do |batch|
    batch.each do |node|
      batch.delete(node) if all.include?(node)
    end
    all += batch
  end
  @batches.reject! { |batch| batch.empty? }
  @batches
end

#filter_nodesObject



100
101
102
103
104
105
106
# File 'lib/terraspace/dependency/graph.rb', line 100

def filter_nodes
  @filtered = []
  top_nodes.each { |node| apply_filter(node) }
  # draw_full_graph option is only used internally by All::Grapher
  update_parents! unless @options[:draw_full_graph]
  @options[:draw_full_graph] ? @nodes : @filtered
end

#leavesObject



128
129
130
# File 'lib/terraspace/dependency/graph.rb', line 128

def leaves
  @nodes.select { |n| n.children.empty? }.sort_by(&:name)
end

#precreate_all_nodesObject



47
48
49
50
51
52
# File 'lib/terraspace/dependency/graph.rb', line 47

def precreate_all_nodes
  @stack_names.each do |name|
    node = Node.find_or_create_by(name: name)
    save_node(node)
  end
end

#save_node(node) ⇒ Object



136
137
138
# File 'lib/terraspace/dependency/graph.rb', line 136

def save_node(node)
  @nodes << node unless @nodes.detect { |n| n.name == node.name }
end

#save_node_parent(parent_name, child_name) ⇒ Object



61
62
63
64
65
66
67
# File 'lib/terraspace/dependency/graph.rb', line 61

def save_node_parent(parent_name, child_name)
  parent = Node.find_by(name: parent_name)
  child = Node.find_by(name: child_name)
  child.parent!(parent)
  save_node(parent)
  save_node(child)
end

#top_nodesObject



132
133
134
# File 'lib/terraspace/dependency/graph.rb', line 132

def top_nodes
  @nodes.select { |n| n.parents.empty? }.sort_by(&:name)
end

#update_parents!Object

remove missing parents references since they will be filtered out



109
110
111
112
113
114
# File 'lib/terraspace/dependency/graph.rb', line 109

def update_parents!
  @filtered.each do |node|
    new_parents = node.parents & @filtered
    node.parents = new_parents
  end
end