diff --git a/awx/api/serializers.py b/awx/api/serializers.py index e843627829..ef523a02d1 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1179,8 +1179,8 @@ class HostSerializer(BaseSerializerWithVariables): model = Host fields = ('*', 'inventory', 'enabled', 'instance_id', 'variables', 'has_active_failures', 'has_inventory_sources', 'last_job', - 'last_job_host_summary') - read_only_fields = ('last_job', 'last_job_host_summary') + 'last_job_host_summary', 'insights_system_id') + read_only_fields = ('last_job', 'last_job_host_summary', 'insights_system_id',) def build_relational_field(self, field_name, relation_info): field_class, field_kwargs = super(HostSerializer, self).build_relational_field(field_name, relation_info) diff --git a/awx/main/management/commands/run_fact_cache_receiver.py b/awx/main/management/commands/run_fact_cache_receiver.py index 1d03757eb2..4d00f5c157 100644 --- a/awx/main/management/commands/run_fact_cache_receiver.py +++ b/awx/main/management/commands/run_fact_cache_receiver.py @@ -12,6 +12,7 @@ from kombu.mixins import ConsumerMixin from django.core.management.base import NoArgsCommand from django.conf import settings from django.utils import timezone +from django.db import IntegrityError # AWX from awx.main.models.jobs import Job @@ -120,6 +121,14 @@ class FactBrokerWorker(ConsumerMixin): ret = self._do_fact_scan_create_update(host_obj, module_name, facts, self.timestamp) if job.store_facts is True: + if module_name == 'insights': + system_id = facts.get('system_id', None) + host_obj.insights_system_id = system_id + try: + host_obj.save() + except IntegrityError: + host_obj.insights_system_id = None + logger.warn('Inisghts system_id %s not assigned to host %s because it already exists.' % (system_id, host_obj.pk)) self._do_gather_facts_update(host_obj, module_name, facts, self.timestamp) message.ack() diff --git a/awx/main/migrations/0038_v320_release.py b/awx/main/migrations/0038_v320_release.py index 3ffcd98c81..d28a8cce0b 100644 --- a/awx/main/migrations/0038_v320_release.py +++ b/awx/main/migrations/0038_v320_release.py @@ -236,4 +236,15 @@ class Migration(migrations.Migration): migrations.DeleteModel( name='Permission', ), + + # Insights + migrations.AddField( + model_name='host', + name='insights_system_id', + field=models.TextField(default=None, help_text='Red Hat Insights host unique identifier.', null=True, db_index=True, blank=True), + ), + migrations.AlterUniqueTogether( + name='host', + unique_together=set([('insights_system_id', 'inventory'), ('name', 'inventory')]), + ), ] diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 7dcad06c64..129dbef196 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -353,7 +353,7 @@ class Host(CommonModelNameNotUnique): class Meta: app_label = 'main' - unique_together = (("name", "inventory"),) # FIXME: Add ('instance_id', 'inventory') after migration. + unique_together = (("name", "inventory"), ("insights_system_id", "inventory"),) # FIXME: Add ('instance_id', 'inventory') after migration. ordering = ('name',) inventory = models.ForeignKey( @@ -414,6 +414,13 @@ class Host(CommonModelNameNotUnique): default={}, help_text=_('Arbitrary JSON structure of most recent ansible_facts, per-host.'), ) + insights_system_id = models.TextField( + blank=True, + default=None, + null=True, + db_index=True, + help_text=_('Red Hat Insights host unique identifier.'), + ) objects = HostManager() diff --git a/awx/playbooks/scan_facts.yml b/awx/playbooks/scan_facts.yml index d24d07d6fa..56b649c3c8 100644 --- a/awx/playbooks/scan_facts.yml +++ b/awx/playbooks/scan_facts.yml @@ -17,6 +17,9 @@ get_checksum: '{{ scan_use_checksum }}' recursive: '{{ scan_use_recursive }}' when: scan_file_paths is defined and ansible_os_family != "Windows" + - name: "Scan Insights for Machine ID (Unix/Linux)" + scan_insights: + when: ansible_os_family != "Windows" - name: "Scan packages (Windows)" win_scan_packages: diff --git a/awx/plugins/library/scan_insights.py b/awx/plugins/library/scan_insights.py new file mode 100755 index 0000000000..b7e625ca6d --- /dev/null +++ b/awx/plugins/library/scan_insights.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +from ansible.module_utils.basic import * # noqa +import uuid + +DOCUMENTATION = ''' +--- +module: scan_insights +short_description: Return insights UUID as fact data +description: + - Inspects the /etc/redhat-access-insights/machine-id file for insights uuid and returns the found UUID as fact data +version_added: "2.3" +options: +requirements: [ ] +author: Chris Meyers +''' + +EXAMPLES = ''' +# Example fact output: +# host | success >> { +# "ansible_facts": { +# "insights": { +# "system_id": "4da7d1f8-14f3-4cdc-acd5-a3465a41f25d" +# }, ... } +''' + + +INSIGHTS_SYSTEM_ID_FILE='/etc/redhat-access-insights/machine-id' + + +def get_system_uuid(filname): + system_uuid = None + try: + f = open(INSIGHTS_SYSTEM_ID_FILE, "r") + except IOError: + return None + else: + try: + data = f.readline() + system_uuid = str(uuid.UUID(data)) + except (IOError, ValueError): + pass + finally: + f.close() + return system_uuid + + +def main(): + module = AnsibleModule( + argument_spec = dict() + ) + + system_uuid = get_system_uuid(INSIGHTS_SYSTEM_ID_FILE) + + results = { + 'ansible_facts': { + 'insights': { + 'system_id': system_uuid + } + } + } + module.exit_json(**results) + + +main()