Module: ForemanInventoryUpload::Generators::FactHelpers

Extended by:
ActiveSupport::Concern
Included in:
Metadata, Slice
Defined in:
lib/foreman_inventory_upload/generators/fact_helpers.rb

Constant Summary collapse

CLOUD_AMAZON =
'aws'
CLOUD_GOOGLE =
'gcp'
CLOUD_AZURE =
'azure'
CLOUD_ALIBABA =
'alibaba'
UUID_REGEX =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i

Instance Method Summary collapse

Instance Method Details

#account_id(organization) ⇒ Object



26
27
28
29
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 26

def (organization)
  @organization_accounts ||= {}
  @organization_accounts[organization.id] ||= organization.pools.where.not(account_number: nil).pluck(:account_number).first
end

#bios_uuid(host) ⇒ Object



188
189
190
191
192
193
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 188

def bios_uuid(host)
  return nil if host.nil?

  value = fact_value(host, 'dmi::system::uuid') || ''
  uuid_value(value)
end

#cloud_provider(host) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 31

def cloud_provider(host)
  bios_version = fact_value(host, 'dmi::bios::version')

  if bios_version
    return CLOUD_AMAZON if bios_version.downcase['amazon']
    return CLOUD_GOOGLE if bios_version.downcase['google']
  end

  chassis_asset_tag = fact_value(host, 'dmi::chassis::asset_tag')
  return CLOUD_AZURE if chassis_asset_tag && chassis_asset_tag['7783-7084-3265-9085-8269-3286-77']

  system_manufacturer = fact_value(host, 'dmi::system::manufacturer')
  return CLOUD_ALIBABA if system_manufacturer && system_manufacturer.downcase['alibaba cloud']

  product_name = fact_value(host, 'dmi::system::product_name')
  return CLOUD_ALIBABA if product_name && product_name.downcase['alibaba cloud ecs']

  nil
end

#fact_value(host, fact_name) ⇒ Object



15
16
17
18
19
20
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 15

def fact_value(host, fact_name)
  value_record = host.fact_values.find do |fact_value|
    fact_value.fact_name_id == ForemanInventoryUpload::Generators::Queries.fact_names[fact_name]
  end
  value_record&.value
end

#fqdn(host) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 64

def fqdn(host)
  if obfuscate_hostname?(host)
    # If obfuscation is enabled, attempt to retrieve an already obfuscated hostname
    # from the 'insights_client::obfuscated_hostname' fact.
    # Example format of `parsed_insights_array`:
    # [{"original"=>"host.example.com", "obfuscated"=>"0dd449d0a027.example.com"},
    #  {"original"=>"satellite.example.com", "obfuscated"=>"host2.example.com"}]
    begin
      parsed_insights_array = JSON.parse(fact_value(host, 'insights_client::obfuscated_hostname') || '[]')
    rescue JSON::ParserError
      parsed_insights_array = []
    end
    # Obfuscate using the following hierarchy:
    # 1. the obfuscated_hostname fact sent by insights_client
    parsed_insights_item = parsed_insights_array.find { |item| item['original'] == host.fqdn }
    # 2. our own helper method
    parsed_insights_item&.[]('obfuscated') || obfuscate_fqdn(host.fqdn)
  else
    # If hostname obfuscation is not enabled for this host, return the host's original FQDN.
    host.fqdn
  end
end

#host_ips(host) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 107

def host_ips(host)
  # Determines and returns the IP addresses associated with a host, applying obfuscation if enabled.
  return {} if host.nil?

  # If IP obfuscation is enabled for the host return a representation of obfuscated IP addresses.
  return obfuscated_ips(host) if obfuscate_ips?(host)

  # If IP obfuscation is NOT needed, return a special kind of Hash.
  # where when you try to access a key in it
  # if the key doesn't exist, it simply returns the key itself.
  # This is useful because it means if you try to get an IP from this hash,
  # you'll just get the original IP back. It allows the calling code to
  # use the same interface whether obfuscation is applied or not.
  Hash.new { |h, k| k }
end

#hostname_matchObject



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 157

def hostname_match
  bash_hostname = `uname -n`.chomp
  foreman_host = ForemanRhCloud.foreman_host

  # If bash hostname matches foreman_host, use fqdn
  if foreman_host && bash_hostname == foreman_host.name
    return fqdn(foreman_host)
  end

  # If no foreman_host, try foreman_host_name from Setting[:foreman_url]
  unless foreman_host
    foreman_hostname_from_setting = ForemanRhCloud.foreman_host_name
    if foreman_hostname_from_setting
      # Apply obfuscation if enabled
      return obfuscate_fqdn(foreman_hostname_from_setting) if Setting[:obfuscate_inventory_hostnames]

      return foreman_hostname_from_setting
    end
    # Otherwise fall through to bash_hostname below
    # NOTE: Containerized foremanctl setups must configure Setting[:foreman_url]
    # as bash hostname may not be available or meaningful in containers
  end

  # Fallback to bash hostname (with obfuscation if enabled)
  if Setting[:obfuscate_inventory_hostnames]
    obfuscate_fqdn(bash_hostname)
  else
    bash_hostname
  end
end

#kilobytes_to_bytes(kilobytes) ⇒ Object



22
23
24
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 22

def kilobytes_to_bytes(kilobytes)
  kilobytes * 1024
end

#obfuscate_fqdn(fqdn) ⇒ Object



87
88
89
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 87

def obfuscate_fqdn(fqdn)
  "#{Digest::SHA1.hexdigest(fqdn)}.example.com"
end

#obfuscate_hostname?(host) ⇒ Boolean

Returns:

  • (Boolean)


51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 51

def obfuscate_hostname?(host)
  # Returns true if hostname obfuscation should be applied for a given host, based on hierarchy:
  # 1. Global setting for hostname obfuscation.
  return true if Setting[:obfuscate_inventory_hostnames] && !ForemanRhCloud.with_iop_smart_proxy?

  insights_client_setting = fact_value(host, 'insights_client::obfuscate_hostname_enabled')
  insights_client_setting = ActiveModel::Type::Boolean.new.cast(insights_client_setting)

  # 2. host fact reported by insights_client
  # 3. if neither of the above, don't obfuscate.
  insights_client_setting.nil? ? false : insights_client_setting
end

#obfuscate_ip(ip, ips_dict) ⇒ Object



149
150
151
152
153
154
155
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 149

def obfuscate_ip(ip, ips_dict)
  # Produce a new, unique obfuscated IP that is
  # numerically one greater than the highest existing obfuscated IP
  max_obfuscated = ips_dict.values.map { |v| IPAddr.new(v).to_i }.max || IPAddr.new('10.230.230.0').to_i

  IPAddr.new(max_obfuscated + 1, Socket::AF_INET).to_s
end

#obfuscate_ips?(host) ⇒ Boolean

Returns:

  • (Boolean)


91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 91

def obfuscate_ips?(host)
  # Returns true if IP obfuscation should be applied for a given host, based on hierarchy:
  # 1. Global setting for IP obfuscation.
  return true if Setting[:obfuscate_inventory_ips] && !ForemanRhCloud.with_iop_smart_proxy?

  insights_client_ipv4_setting = fact_value(host, 'insights_client::obfuscate_ipv4_enabled')
  insights_client_ipv6_setting = fact_value(host, 'insights_client::obfuscate_ipv6_enabled')

  cast_ipv4_setting = ActiveModel::Type::Boolean.new.cast(insights_client_ipv4_setting)
  cast_ipv6_setting = ActiveModel::Type::Boolean.new.cast(insights_client_ipv6_setting)

  # 2. The host's IPv4 or IPv6 obfuscation fact value is true
  # 3. If neither of the above, don't obfuscate.
  cast_ipv4_setting || cast_ipv6_setting || false
end

#obfuscated_ips(host) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 123

def obfuscated_ips(host)
  # Example format of `parsed_insights_array`:
  # [{"original": "192.168.1.10", "obfuscated": "10.230.230.1"},
  #  {"original": "192.168.1.11", "obfuscated": "10.230.230.2"}]
  begin
    parsed_insights_array = JSON.parse(fact_value(host, 'insights_client::obfuscated_ipv4') || '[]')
  rescue JSON::ParserError
    parsed_insights_array = []
  end

  # Create a new Hash to store the mapping from original IP addresses to their obfuscated versions.
  # where the 'original' IP is the key and the 'obfuscated' IP is the value.
  obfuscated_ips = Hash[
    parsed_insights_array.map { |ip_record| [ip_record['original'], ip_record['obfuscated']] }
  ]

  # Sets a default proc for the obfuscated_ips hash.
  # When a key is accessed that does not exist in the hash, this proc is called.
  # It assigns the result of obfuscate_ip(key, hash) to the missing key in the hash.
  # This ensures that any missing IP address key will be obfuscated and stored automatically.
  obfuscated_ips.default_proc = proc do |hash, key|
    hash[key] = obfuscate_ip(key, hash)
  end
  obfuscated_ips
end

#uuid_value(value) ⇒ Object



195
196
197
198
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 195

def uuid_value(value)
  uuid_match = UUID_REGEX.match(value)
  uuid_match&.to_s
end

#uuid_value!(value) ⇒ Object

Raises:

  • (Foreman::Exception)


200
201
202
203
204
205
# File 'lib/foreman_inventory_upload/generators/fact_helpers.rb', line 200

def uuid_value!(value)
  uuid = uuid_value(value)
  raise Foreman::Exception.new(N_('Value %{value} is not a valid UUID') % { value: value }) if value && uuid.empty?

  uuid
end