Class: Bolt::Inventory::Group

Inherits:
Object
  • Object
show all
Defined in:
lib/bolt/inventory/group.rb

Overview

Group is a specific implementation of Inventory based on nested structured data.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data) ⇒ Group

Returns a new instance of Group.



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
# File 'lib/bolt/inventory/group.rb', line 10

def initialize(data)
  @logger = Logging.logger[self]

  unless data.is_a?(Hash)
    raise ValidationError.new("Expected group to be a Hash, not #{data.class}", nil)
  end

  if data.key?('name')
    if data['name'].is_a?(String)
      @name = data['name']
    else
      raise ValidationError.new("Group name must be a String, not #{data['name'].inspect}", nil)
    end
  else
    raise ValidationError.new("Group does not have a name", nil)
  end

  @vars = fetch_value(data, 'vars', Hash)
  @facts = fetch_value(data, 'facts', Hash)
  @features = fetch_value(data, 'features', Array)
  @config = fetch_value(data, 'config', Hash)

  nodes = fetch_value(data, 'nodes', Array)
  groups = fetch_value(data, 'groups', Array)

  @nodes = {}
  nodes.each do |node|
    node = { 'name' => node } if node.is_a? String
    unless node.is_a?(Hash)
      raise ValidationError.new("Node entry must be a String or Hash, not #{node.class}", @name)
    end
    if @nodes.include? node['name']
      @logger.warn("Ignoring duplicate node in #{@name}: #{node}")
    else
      @nodes[node['name']] = node
    end
  end

  @groups = groups.map { |g| Group.new(g) }

  # this allows arbitrary info for the top level
  @rest = data.reject { |k, _| %w[name nodes config groups vars facts features].include? k }
end

Instance Attribute Details

#configObject

Returns the value of attribute config.



8
9
10
# File 'lib/bolt/inventory/group.rb', line 8

def config
  @config
end

#groupsObject

Returns the value of attribute groups.



8
9
10
# File 'lib/bolt/inventory/group.rb', line 8

def groups
  @groups
end

#nameObject

Returns the value of attribute name.



8
9
10
# File 'lib/bolt/inventory/group.rb', line 8

def name
  @name
end

#nodesObject

Returns the value of attribute nodes.



8
9
10
# File 'lib/bolt/inventory/group.rb', line 8

def nodes
  @nodes
end

#restObject

Returns the value of attribute rest.



8
9
10
# File 'lib/bolt/inventory/group.rb', line 8

def rest
  @rest
end

Instance Method Details

#check_deprecated_config(context, name, config) ⇒ Object



62
63
64
65
66
67
68
# File 'lib/bolt/inventory/group.rb', line 62

def check_deprecated_config(context, name, config)
  if config && config['transports']
    msg = "#{context} #{name} contains invalid config option 'transports', see " \
          "https://puppet.com/docs/bolt/0.x/inventory_file.html for the updated format"
    raise ValidationError.new(msg, @name)
  end
end

#collect_groupsObject

Return a mapping of group names to group.



173
174
175
176
177
# File 'lib/bolt/inventory/group.rb', line 173

def collect_groups
  @groups.inject(name => self) do |acc, g|
    acc.merge(g.collect_groups)
  end
end

#data_for(node_name) ⇒ Object

The data functions below expect and return nil or a hash of the schema { ‘config’ => Hash , ‘vars’ => Hash, ‘facts’ => Hash, ‘features’ => Array, groups => Array }



117
118
119
# File 'lib/bolt/inventory/group.rb', line 117

def data_for(node_name)
  data_merge(group_collect(node_name), node_collect(node_name))
end

#data_merge(data1, data2) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/bolt/inventory/group.rb', line 148

def data_merge(data1, data2)
  if data2.nil? || data1.nil?
    return data2 || data1
  end

  {
    'config' => Bolt::Util.deep_merge(data1['config'], data2['config']),
    # Shallow merge instead of deep merge so that vars with a hash value
    # are assigned a new hash, rather than merging the existing value
    # with the value meant to replace it
    'vars'   => data1['vars'].merge(data2['vars']),
    'facts'  => Bolt::Util.deep_merge(data1['facts'], data2['facts']),
    'features' => data1['features'] | data2['features'],
    'groups' => data2['groups'] + data1['groups']
  }
end

#empty_dataObject



140
141
142
143
144
145
146
# File 'lib/bolt/inventory/group.rb', line 140

def empty_data
  { 'config'   => {},
    'vars'     => {},
    'facts'    => {},
    'features' => [],
    'groups'   => [] }
end

#fetch_value(data, key, type) ⇒ Object



54
55
56
57
58
59
60
# File 'lib/bolt/inventory/group.rb', line 54

def fetch_value(data, key, type)
  value = data.fetch(key, type.new)
  unless value.is_a?(type)
    raise ValidationError.new("Expected #{key} to be of type #{type}, not #{value.class}", @name)
  end
  value
end

#group_collect(node_name) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/bolt/inventory/group.rb', line 195

def group_collect(node_name)
  data = @groups.inject(nil) do |acc, g|
    if (d = g.data_for(node_name))
      data_merge(d, acc)
    else
      acc
    end
  end

  if data
    data_merge(group_data, data)
  elsif @nodes.include?(node_name)
    group_data
  end
end

#group_dataObject



132
133
134
135
136
137
138
# File 'lib/bolt/inventory/group.rb', line 132

def group_data
  { 'config'   => @config,
    'vars'     => @vars,
    'facts'    => @facts,
    'features' => @features,
    'groups'   => [@name] }
end

#node_collect(node_name) ⇒ Object



184
185
186
187
188
189
190
191
192
193
# File 'lib/bolt/inventory/group.rb', line 184

def node_collect(node_name)
  data = @groups.inject(nil) do |acc, g|
    if (d = g.node_collect(node_name))
      data_merge(d, acc)
    else
      acc
    end
  end
  data_merge(node_data(node_name), data)
end

#node_data(node_name) ⇒ Object



121
122
123
124
125
126
127
128
129
130
# File 'lib/bolt/inventory/group.rb', line 121

def node_data(node_name)
  if (data = @nodes[node_name])
    { 'config' => data['config'] || {},
      'vars' => data['vars'] || {},
      'facts' => data['facts'] || {},
      'features' => data['features'] || [],
      # groups come from group_data
      'groups' => [] }
  end
end

#node_namesObject

Returns all nodes contained within the group, which includes nodes from subgroups.



166
167
168
169
170
# File 'lib/bolt/inventory/group.rb', line 166

def node_names
  @groups.inject(local_node_names) do |acc, g|
    acc.merge(g.node_names)
  end
end

#validate(used_names = Set.new, node_names = Set.new, depth = 0) ⇒ Object

Raises:



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
# File 'lib/bolt/inventory/group.rb', line 70

def validate(used_names = Set.new, node_names = Set.new, depth = 0)
  if used_names.include?(@name)
    raise ValidationError.new("Tried to redefine group #{@name}", @name)
  end
  raise ValidationError.new("Invalid group name #{@name}", @name) unless @name =~ /\A[a-z0-9_]+\Z/

  if node_names.include?(@name)
    raise ValidationError.new("Group #{@name} conflicts with node of the same name", @name)
  end

  check_deprecated_config('Group', @name, @config)

  used_names << @name

  @nodes.each_value do |n|
    # Require nodes to be parseable as a Target.
    begin
      Target.new(n['name'])
    rescue Addressable::URI::InvalidURIError => e
      @logger.debug(e)
      raise ValidationError.new("Invalid node name #{n['name']}", n['name'])
    end

    raise ValidationError.new("Node #{n['name']} does not have a name", n['name']) unless n['name']
    if used_names.include?(n['name'])
      raise ValidationError.new("Group #{n['name']} conflicts with node of the same name", n['name'])
    end

    check_deprecated_config('Node', n['name'], n['config'])

    node_names << n['name']
  end

  @groups.each do |g|
    begin
      g.validate(used_names, node_names, depth + 1)
    rescue ValidationError => e
      e.add_parent(@name)
      raise e
    end
  end

  nil
end