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



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

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



88
89
90
# File 'lib/bjn_inventory/service_map.rb', line 88

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



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/bjn_inventory/service_map.rb', line 76

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



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/bjn_inventory/service_map.rb', line 54

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
            end
        end
    end
    service_keylist
end

#_join_endpoints(endpoints) ⇒ Object



92
93
94
95
96
97
98
99
# File 'lib/bjn_inventory/service_map.rb', line 92

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



103
104
105
106
107
108
109
110
111
112
# File 'lib/bjn_inventory/service_map.rb', line 103

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
# 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'
    @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