diff --git a/ara/api/serializers.py b/ara/api/serializers.py
index f6656a01..bfbfd33e 100644
--- a/ara/api/serializers.py
+++ b/ara/api/serializers.py
@@ -74,8 +74,7 @@ class FileSha1Serializer(serializers.ModelSerializer):
#######
-# Simple serializers provide lightweight representations of objects without
-# nested or large fields.
+# Simple serializers provide lightweight representations of objects suitable for inclusion in other objects
#######
@@ -96,108 +95,27 @@ class SimplePlaybookSerializer(ItemCountSerializer):
class SimplePlaySerializer(ItemCountSerializer):
class Meta:
model = models.Play
- exclude = ("uuid", "created", "updated")
+ exclude = ("playbook", "uuid", "created", "updated")
class SimpleTaskSerializer(ItemCountSerializer, TaskPathSerializer):
class Meta:
model = models.Task
- exclude = ("tags", "created", "updated")
+ exclude = ("playbook", "play", "created", "updated")
-
-class SimpleResultSerializer(ResultStatusSerializer):
- class Meta:
- model = models.Result
- exclude = ("content", "created", "updated")
+ tags = ara_fields.CompressedObjectField(read_only=True)
class SimpleHostSerializer(serializers.ModelSerializer):
class Meta:
model = models.Host
- exclude = ("facts", "created", "updated")
+ exclude = ("playbook", "facts", "created", "updated")
class SimpleFileSerializer(FileSha1Serializer):
class Meta:
model = models.File
- exclude = ("content", "created", "updated")
-
-
-class SimpleRecordSerializer(serializers.ModelSerializer):
- class Meta:
- model = models.Record
- exclude = ("value", "created", "updated")
-
-
-#######
-# Nested serializers returns optimized data within the context of another object.
-# For example: when retrieving a playbook, we'll already have the playbook id
-# so it is not necessary to include it in nested objects.
-#######
-
-
-class NestedPlaybookFileSerializer(serializers.ModelSerializer):
- class Meta:
- model = models.File
- exclude = ("content", "created", "updated", "playbook")
-
-
-class NestedPlaybookHostSerializer(serializers.ModelSerializer):
- class Meta:
- model = models.Host
- fields = ("id", "name")
-
-
-class NestedPlaybookResultSerializer(ResultStatusSerializer):
- class Meta:
- model = models.Result
- exclude = ("content", "created", "updated", "playbook", "play", "task")
-
- host = NestedPlaybookHostSerializer(read_only=True)
-
-
-class NestedPlaybookTaskSerializer(serializers.ModelSerializer):
- class Meta:
- model = models.Task
- exclude = ("playbook", "created", "updated")
-
- tags = ara_fields.CompressedObjectField(read_only=True)
- file = NestedPlaybookFileSerializer(read_only=True)
- results = serializers.SerializerMethodField()
-
- @staticmethod
- def get_results(obj):
- results = obj.results.all().order_by("-id")
- return NestedPlaybookResultSerializer(results, many=True).data
-
-
-class NestedPlaybookRecordSerializer(serializers.ModelSerializer):
- class Meta:
- model = models.Record
- exclude = ("playbook", "value", "created", "updated")
-
-
-class NestedPlaybookPlaySerializer(serializers.ModelSerializer):
- class Meta:
- model = models.Play
- exclude = ("playbook", "uuid", "created", "updated")
-
- tasks = serializers.SerializerMethodField()
-
- @staticmethod
- def get_tasks(obj):
- tasks = obj.tasks.all().order_by("-id")
- return NestedPlaybookTaskSerializer(tasks, many=True).data
-
-
-class NestedPlayTaskSerializer(TaskPathSerializer):
- class Meta:
- model = models.Task
- exclude = ("playbook", "play", "created", "updated")
-
- tags = ara_fields.CompressedObjectField(read_only=True)
- results = NestedPlaybookResultSerializer(read_only=True, many=True)
- file = NestedPlaybookFileSerializer(read_only=True)
+ exclude = ("playbook", "content", "created", "updated")
#######
@@ -219,15 +137,6 @@ class DetailedPlaybookSerializer(ItemCountSerializer):
arguments = ara_fields.CompressedObjectField(default=ara_fields.EMPTY_DICT, read_only=True)
labels = SimpleLabelSerializer(many=True, read_only=True, default=[])
- hosts = SimpleHostSerializer(many=True, read_only=True, default=[])
- files = SimpleFileSerializer(many=True, read_only=True, default=[])
- records = NestedPlaybookRecordSerializer(many=True, read_only=True, default=[])
- plays = serializers.SerializerMethodField()
-
- @staticmethod
- def get_plays(obj):
- plays = obj.plays.all().order_by("-id")
- return NestedPlaybookPlaySerializer(plays, many=True).data
class DetailedPlaySerializer(ItemCountSerializer):
@@ -236,7 +145,6 @@ class DetailedPlaySerializer(ItemCountSerializer):
fields = "__all__"
playbook = SimplePlaybookSerializer(read_only=True)
- tasks = NestedPlayTaskSerializer(many=True, read_only=True, default=[])
class DetailedTaskSerializer(ItemCountSerializer, TaskPathSerializer):
@@ -247,7 +155,6 @@ class DetailedTaskSerializer(ItemCountSerializer, TaskPathSerializer):
playbook = SimplePlaybookSerializer(read_only=True)
play = SimplePlaySerializer(read_only=True)
file = SimpleFileSerializer(read_only=True)
- results = NestedPlaybookResultSerializer(many=True, read_only=True, default=[])
tags = ara_fields.CompressedObjectField(read_only=True)
diff --git a/ara/ui/templates/playbook.html b/ara/ui/templates/playbook.html
index a2d26f07..ac2b241c 100644
--- a/ara/ui/templates/playbook.html
+++ b/ara/ui/templates/playbook.html
@@ -7,7 +7,7 @@
Records
- {% if playbook.items.records %}
+ {% if records %}
{% for record in playbook.records %}
- {{ record.key }}
@@ -20,7 +20,7 @@
Files
- {% for file in playbook.files %}
+ {% for file in files %}
- {{ file.path }}
{% endfor %}
@@ -74,7 +74,7 @@
- {% for host in playbook.hosts %}
+ {% for host in hosts %}
{{ host.name }}
@@ -117,9 +117,7 @@
|
- {% for play in playbook.plays %}
- {% for task in play.tasks %}
- {% for result in task.results %}
+ {% for result in results %}
{% include "partials/result_status_icon.html" with status=result.status %}
@@ -128,10 +126,10 @@
{{ result.host.name }}
|
- {{ task.name }}
+ {{ result.task.name }}
|
- {{ task.action }}
+ {{ result.task.action }}
|
{{ result.duration | format_duration }}
@@ -141,8 +139,6 @@
|
{% endfor %}
- {% endfor %}
- {% endfor %}
diff --git a/ara/ui/views.py b/ara/ui/views.py
index e4d374d3..00fe5623 100644
--- a/ara/ui/views.py
+++ b/ara/ui/views.py
@@ -68,9 +68,35 @@ class Playbook(generics.RetrieveAPIView):
template_name = "playbook.html"
def get(self, request, *args, **kwargs):
- playbook = self.get_object()
- serializer = serializers.DetailedPlaybookSerializer(playbook)
- return Response({"playbook": serializer.data})
+ playbook = serializers.DetailedPlaybookSerializer(self.get_object())
+ hosts = serializers.ListHostSerializer(
+ models.Host.objects.filter(playbook=playbook.data["id"]).all(), many=True
+ )
+ files = serializers.ListFileSerializer(
+ models.File.objects.filter(playbook=playbook.data["id"]).all(), many=True
+ )
+ records = serializers.ListRecordSerializer(
+ models.Record.objects.filter(playbook=playbook.data["id"]).all(), many=True
+ )
+ results = serializers.ListResultSerializer(
+ models.Result.objects.filter(playbook=playbook.data["id"]).all(), many=True
+ )
+
+ for result in results.data:
+ task_id = result["task"]
+ result["task"] = serializers.SimpleTaskSerializer(models.Task.objects.get(pk=task_id)).data
+ host_id = result["host"]
+ result["host"] = serializers.SimpleHostSerializer(models.Host.objects.get(pk=host_id)).data
+
+ # fmt: off
+ return Response({
+ "playbook": playbook.data,
+ "hosts": hosts.data,
+ "files": files.data,
+ "records": records.data,
+ "results": results.data
+ })
+ # fmt: on
class Host(generics.RetrieveAPIView):
diff --git a/doc/source/api-usage.rst b/doc/source/api-usage.rst
index a58d4216..7ea721de 100644
--- a/doc/source/api-usage.rst
+++ b/doc/source/api-usage.rst
@@ -82,25 +82,30 @@ Here's a code example to help you get started:
# If there are any results from our query, get more information about the
# failure and print something helpful
template = "{timestamp}: {host} failed '{task}' ({task_file}:{lineno})"
- for playbook in playbooks["results"]:
- # Get a detailed version of the playbook that provides additional context
- detailed_playbook = client.get("/api/v1/playbooks/%s" % playbook["id"])
- # Iterate through the playbook to get the context
- # Playbook -> Play -> Task -> Result <- Host
- for play in detailed_playbook["plays"]:
- for task in play["tasks"]:
- for result in task["results"]:
- if result["status"] in ["failed", "unreachable"]:
- print(template.format(
- timestamp=result["ended"],
- host=result["host"]["name"],
- task=task["name"],
- task_file=task["file"]["path"],
- lineno=task["lineno"]
- ))
+ for playbook in playbooks["results"]:
+ # Get failed results for the playbook
+ results = client.get("/api/v1/results?playbook=%s" % playbook["id"])
+
+ # For each result, print the task and host information
+ for result in results["results"]:
+ task = client.get("/api/v1/tasks/%s" % result["task"])
+ host = client.get("/api/v1/hosts/%s" % result["host"])
+
+ print(template.format(
+ timestamp=result["ended"],
+ host=host["name"],
+ task=task["name"],
+ task_file=task["path"],
+ lineno=task["lineno"]
+ ))
Running this script would then provide an output that looks like the following::
- 2019-03-20T16:18:41.710765: localhost failed 'smoke-tests : Return false' (tests/integration/roles/smoke-tests/tasks/test-ops.yaml:25)
- 2019-03-20T16:19:17.332663: localhost failed 'fail' (tests/integration/failed.yaml:22)
+ 2020-04-18T17:16:13.394056Z: aio1_repo_container-0c92f7a2 failed 'repo_server : Install EPEL gpg keys' (/home/zuul/src/opendev.org/openstack/openstack-ansible-repo_server/tasks/repo_install.yml:16)
+ 2020-04-18T17:14:59.930995Z: aio1_repo_container-0c92f7a2 failed 'repo_server : File and directory setup (root user)' (/home/zuul/src/opendev.org/openstack/openstack-ansible-repo_server/tasks/repo_pre_install.yml:78)
+ 2020-04-18T17:14:57.909155Z: aio1_repo_container-0c92f7a2 failed 'repo_server : Git service data folder setup' (/home/zuul/src/opendev.org/openstack/openstack-ansible-repo_server/tasks/repo_pre_install.yml:70)
+ 2020-04-18T17:14:57.342091Z: aio1_repo_container-0c92f7a2 failed 'repo_server : Check if the git folder exists already' (/home/zuul/src/opendev.org/openstack/openstack-ansible-repo_server/tasks/repo_pre_install.yml:65)
+ 2020-04-18T17:14:56.793499Z: aio1_repo_container-0c92f7a2 failed 'repo_server : Drop repo pre/post command script' (/home/zuul/src/opendev.org/openstack/openstack-ansible-repo_server/tasks/repo_pre_install.yml:53)
+ 2020-04-18T17:14:54.507660Z: aio1_repo_container-0c92f7a2 failed 'repo_server : File and directory setup (non-root user)' (/home/zuul/src/opendev.org/openstack/openstack-ansible-repo_server/tasks/repo_pre_install.yml:32)
+ 2020-04-18T17:14:51.281530Z: aio1_repo_container-0c92f7a2 failed 'repo_server : Create the nginx system user' (/home/zuul/src/opendev.org/openstack/openstack-ansible-repo_server/tasks/repo_pre_install.yml:22)
diff --git a/tests/integration/lookups.yaml b/tests/integration/lookups.yaml
index 0b4ca780..9ea2c8d9 100644
--- a/tests/integration/lookups.yaml
+++ b/tests/integration/lookups.yaml
@@ -26,11 +26,6 @@
- playbook.ansible_version == ansible_version.full
- playbook_dir in playbook.path
- "'tests/integration/lookups.yaml' in playbook.path"
- - "playbook.files | length == playbook['items']['files']"
- - "playbook.hosts | length == playbook['items']['hosts']"
- - "playbook.plays | length == playbook['items']['plays']"
- - "tasks.results | length == playbook['items']['tasks']"
- - "results.results | length == playbook['items']['results']"
#####
# Examples taken from docs on Ansible plugins and use cases