Class: BjnInventory::ByKey

Inherits:
Hash
  • Object
show all
Defined in:
lib/bjn_inventory/bykey.rb,
lib/bjn_inventory/ansible.rb,
lib/bjn_inventory/service_map.rb

Instance Method Summary collapse

Methods inherited from Hash

#stringify_keys

Instance Method Details

#_deep_set_service(hsh, path, object) ⇒ Object

Take a path–a list of key components, and deeply set them into a hash of hashes



40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/bjn_inventory/service_map.rb', line 40

def _deep_set_service(hsh, path, object)
    car, *cdr = path
    if cdr.empty?
        # This is kind of weird, use-case-specific logic
        object.each do |key, value|
            hsh[car + '.' + key] = value
        end
    else
        hsh[car] = { } unless hsh.has_key? car
        _deep_set_service(hsh[car], cdr, object)
    end
end

#_endpoint(device) ⇒ Object



110
111
112
# File 'lib/bjn_inventory/service_map.rb', line 110

def _endpoint(device)
    @endpoint_fields.map { |field| device[field] }.reject { |e| e.nil? }.first
end

#_field_groups(fields, device, sep = '__') ⇒ Object



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
# File 'lib/bjn_inventory/bykey.rb', line 9

def _field_groups(fields, device, sep='__')
    fields = [fields] unless fields.respond_to? :inject
    value_map = fields.map do |field|
        values = device[field]
        values = [values] unless values.respond_to? :map
        values.map { |val| escape_name(val) }
    end
    #
    # So now we have an array of arrays, eg.:
    # fields='region' =>
    # [['dc2']]
    #
    # fields=['roles', 'region'] =>
    # [['web', ['dc2']]
    #   'db'],
    #
    groups =
        if fields.length == 1
            value_map.first
        else
            driving_array, *rest = value_map
            driving_array.product(*rest).map { |compound_value| compound_value.join(sep) }
        end
    groups
end

#_interpolate_path(path, device) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
# File 'lib/bjn_inventory/service_map.rb', line 81

def _interpolate_path(path, device)
    path.map do |component|
        if component.start_with? '$$'
            component[1 .. -1]
        elsif component.start_with? '$'
            device[component[1 .. -1]]
        else
            component
        end
    end
end

#_interpolate_pathlist(spec_keys, groups) ⇒ Object

Take uninterpolated paths and expand them with the groups of devices we have



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
# File 'lib/bjn_inventory/service_map.rb', line 55

def _interpolate_pathlist(spec_keys, groups)
    service_keylist = { }

    # Interpolate based on hosts_field values
    spec_keys.each do |path, service|
        if service[@hosts_field]
            device_names = service[@hosts_field].map { |group| groups[group] || [] }.flatten
            device_names.each do |device_name|
                if self[device_name]
                    interpolated_path = _interpolate_path(path, self[device_name])
                    next if interpolated_path.any? { |el| el.nil? }
                    unless service_keylist.has_key? interpolated_path
                        service_keylist[interpolated_path] = service.merge({ @hosts_field => [] })
                    end
                    service_keylist[interpolated_path][@hosts_field] << _endpoint(self[device_name])
                end
                if service[@hosts_override] && service_keylist[interpolated_path][@hosts_field]
                    service_keylist[interpolated_path][@hosts_field] = [_interpolate_value(path, self[device_name], service[@hosts_override])]
                    service_keylist[interpolated_path].delete(@hosts_override)
                end
            end
        end
    end
    service_keylist
end

#_interpolate_value(path, device, value) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/bjn_inventory/service_map.rb', line 93

def _interpolate_value(path, device, value)
    replacement_value = value
    path.each do |component, continued_path|
        if component.start_with? '$$'
            replacement_value = replacement_value.gsub(component, component[1 .. -1])
        elsif component.start_with? '$'
            replacement_value = replacement_value.gsub(component, device[component[1 .. -1]])
        else
            replacement_value = replacement_value
        end
        if path.is_a?(Hash)
            replacement_value = _interpolate_value(continued_path, device, replacement_value)
        end
    end
    replacement_value
end

#_join_endpoints(endpoints) ⇒ Object



114
115
116
117
118
119
120
121
# File 'lib/bjn_inventory/service_map.rb', line 114

def _join_endpoints(endpoints)
    case @join
    when :json
        endpoints.uniq.sort.to_json
    else
        endpoints.uniq.sort.join(@join)
    end
end

#_pathlist(this_map, prefix = [], cur = {}) ⇒ Object

Create a hash where the keys are paths (arrays of path components) and the values are service specs



125
126
127
128
129
130
131
132
133
134
# File 'lib/bjn_inventory/service_map.rb', line 125

def _pathlist(this_map, prefix=[], cur={})
    if this_map.any? { |k, v| ! v.respond_to? :keys }
        cur[prefix] = this_map
    else
        this_map.map do |key, value|
            _pathlist(value, prefix + [key], cur)
        end
    end
    cur
end

#escape_name(value) ⇒ Object



5
6
7
# File 'lib/bjn_inventory/bykey.rb', line 5

def escape_name(value)
    value.gsub(/[^a-zA-Z0-9_-]+/, '_')
end

#to_ansible(*args) ⇒ Object



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
# File 'lib/bjn_inventory/ansible.rb', line 8

def to_ansible(*args)
    if args[-1].respond_to? :to_hash
        kwargs = args.pop.stringify_keys
    else
        kwargs = { }
    end
    group_by = []
    if kwargs['group_by']
        group_by = kwargs['group_by']
        group_by = [group_by] unless group_by.respond_to? :each
    end
    group_by.concat(args)

    logger = kwargs['logger'] || BjnInventory::DefaultLogger.new
    separator = kwargs['separator'] || '__'

    ansible_inventory = self.to_groups(group_by: group_by, logger: logger, separator: separator).
        merge({
                  '_meta' => {
                      'hostvars' => self.to_hash
                  }
              })

    # Do my own groups
    if kwargs['groups']
        ansible_inventory.merge! Hash[kwargs['groups'].map do |group, children|
                                          [group, { "hosts" => [ ], "children" => children }]
                                      end]
    end

    ansible_inventory
end

#to_groups(kwargs) ⇒ Object

This basically builds an ansible inventory given a hash of hostvars



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
# File 'lib/bjn_inventory/bykey.rb', line 36

def to_groups(kwargs)
    group_by = kwargs[:group_by]

    if group_by.empty?
        raise ArgumentError, "Expected group_by either as keyword or as argument list"
    end

    logger = kwargs[:logger] || BjnInventory::DefaultLogger.new
    # We need at least one field to create groups
    separator = kwargs[:separator] || '__'

    groups = { }

    self.each do |name, device_hash|
        group_by.each do |group_field_spec|
            group_field_spec = [group_field_spec] unless group_field_spec.respond_to? :all?
            if group_field_spec.all? { |field| !device_hash[field].nil? && !device_hash[field].empty? }
                field_groups = _field_groups(group_field_spec, device_hash, separator)
                field_groups.each do |group_name|
                    groups[group_name] = [ ] unless groups.has_key? group_name
                    groups[group_name] << name
                end
            end
        end
    end

    # I can't really do children, they need to be generators and I'm not
    # about that right now. Save it for ansible.

    groups
end

#to_services(kwargs) ⇒ Object

Raises:

  • (ArgumentError)


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
# File 'lib/bjn_inventory/service_map.rb', line 8

def to_services(kwargs)
    @map = kwargs[:map]
    @group_by = kwargs[:group_by]
    @logger = kwargs[:logger] || BjnInventory::DefaultLogger.new

    @hosts_field = kwargs[:hosts]               || 'hosts'
    @hosts_override = kwargs[:hosts_override]   || 'hosts_override'
    @endpoint_fields = kwargs[:endpoint_fields] || ['endpoint', 'ip_address']
    @join = kwargs[:join]                       || ','
    separator = kwargs[:separator]              || '__'

    raise ArgumentError, "BjnInventory::ByKey#to_services requires a service map" unless @map
    raise ArgumentError, "BjnInventory::ByKey#to_services requires a group_by argument" unless @group_by

    # Build un-interpolated path => service correspondence
    spec_keys = _pathlist(@map)
    @logger.debug "paths in map: #{spec_keys.inspect}"

    groups = self.to_groups(group_by: @group_by, separator: separator)

    service_keylist = _interpolate_pathlist(spec_keys, groups)

    services = { }

    service_keylist.each do |path, service|
        _deep_set_service(services, path, service.merge({ @hosts_field => _join_endpoints(service[@hosts_field]) }))
    end

    services
end